]> git.proxmox.com Git - pve-cluster.git/commitdiff
re-organize source hierachy
authorThomas Lamprecht <t.lamprecht@proxmox.com>
Fri, 19 May 2023 16:04:20 +0000 (18:04 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Fri, 19 May 2023 16:04:56 +0000 (18:04 +0200)
data/src was a bit odd, and git is good enough to resolve such things
for history (blame) or applying patches, so don't bother enduring
this.

The process was basically:

 git mv data/src/ data/pmxcfs
 git mv data/ src
 git mv cpgtest.c src/test
 git mv src/cts/* src/test/scripts

And a few Makefile fixups.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
137 files changed:
Makefile
cpgtest.c [deleted file]
data/Makefile [deleted file]
data/PVE/API2/ClusterConfig.pm [deleted file]
data/PVE/API2/Makefile [deleted file]
data/PVE/CLI/Makefile [deleted file]
data/PVE/CLI/pvecm.pm [deleted file]
data/PVE/Cluster.pm [deleted file]
data/PVE/Cluster/IPCConst.pm.awk [deleted file]
data/PVE/Cluster/Makefile [deleted file]
data/PVE/Cluster/Setup.pm [deleted file]
data/PVE/Corosync.pm [deleted file]
data/PVE/DataCenterConfig.pm [deleted file]
data/PVE/IPCC.pm [deleted file]
data/PVE/IPCC.xs [deleted file]
data/PVE/Makefile [deleted file]
data/PVE/RRD.pm [deleted file]
data/PVE/SSHInfo.pm [deleted file]
data/PVE/ppport.h [deleted file]
data/PVE/pvecm [deleted file]
data/PVE/test.pl [deleted file]
data/PVE/test1.pl [deleted file]
data/README [deleted file]
data/cts/create_large_files.pl [deleted file]
data/cts/create_many_files.pl [deleted file]
data/cts/create_vm_test.pl [deleted file]
data/src/Makefile [deleted file]
data/src/cfs-ipc-ops.h [deleted file]
data/src/cfs-plug-func.c [deleted file]
data/src/cfs-plug-link.c [deleted file]
data/src/cfs-plug-memdb.c [deleted file]
data/src/cfs-plug-memdb.h [deleted file]
data/src/cfs-plug.c [deleted file]
data/src/cfs-plug.h [deleted file]
data/src/cfs-utils.c [deleted file]
data/src/cfs-utils.h [deleted file]
data/src/check_memdb.c [deleted file]
data/src/confdb.c [deleted file]
data/src/confdb.h [deleted file]
data/src/create_pmxcfs_db.c [deleted file]
data/src/database.c [deleted file]
data/src/dcdb.c [deleted file]
data/src/dcdb.h [deleted file]
data/src/dfsm.c [deleted file]
data/src/dfsm.h [deleted file]
data/src/logger.c [deleted file]
data/src/logger.h [deleted file]
data/src/logtest.c [deleted file]
data/src/logtest2.c [deleted file]
data/src/loop.c [deleted file]
data/src/loop.h [deleted file]
data/src/memdb.c [deleted file]
data/src/memdb.h [deleted file]
data/src/pmxcfs.c [deleted file]
data/src/quorum.c [deleted file]
data/src/quorum.h [deleted file]
data/src/server.c [deleted file]
data/src/server.h [deleted file]
data/src/status.c [deleted file]
data/src/status.h [deleted file]
data/test/Makefile [deleted file]
data/test/corosync_configs/multiple-nodes.conf [deleted file]
data/test/corosync_configs/old.conf [deleted file]
data/test/corosync_configs/single-node.conf [deleted file]
data/test/corosync_parser_test.pl [deleted file]
data/test/scripts/benchmark-config-get-property.pl [deleted file]
data/test/scripts/test-broadcast-kv.pl [deleted file]
data/test/scripts/test-config-get-property.pl [deleted file]
data/test/scripts/test-verify-token.pl [deleted file]
src/Makefile [new file with mode: 0644]
src/PVE/API2/ClusterConfig.pm [new file with mode: 0644]
src/PVE/API2/Makefile [new file with mode: 0644]
src/PVE/CLI/Makefile [new file with mode: 0644]
src/PVE/CLI/pvecm.pm [new file with mode: 0755]
src/PVE/Cluster.pm [new file with mode: 0644]
src/PVE/Cluster/IPCConst.pm.awk [new file with mode: 0644]
src/PVE/Cluster/Makefile [new file with mode: 0644]
src/PVE/Cluster/Setup.pm [new file with mode: 0644]
src/PVE/Corosync.pm [new file with mode: 0644]
src/PVE/DataCenterConfig.pm [new file with mode: 0644]
src/PVE/IPCC.pm [new file with mode: 0644]
src/PVE/IPCC.xs [new file with mode: 0644]
src/PVE/Makefile [new file with mode: 0644]
src/PVE/RRD.pm [new file with mode: 0644]
src/PVE/SSHInfo.pm [new file with mode: 0644]
src/PVE/ppport.h [new file with mode: 0644]
src/PVE/pvecm [new file with mode: 0755]
src/PVE/test.pl [new file with mode: 0755]
src/PVE/test1.pl [new file with mode: 0755]
src/README [new file with mode: 0644]
src/pmxcfs/Makefile [new file with mode: 0644]
src/pmxcfs/cfs-ipc-ops.h [new file with mode: 0644]
src/pmxcfs/cfs-plug-func.c [new file with mode: 0644]
src/pmxcfs/cfs-plug-link.c [new file with mode: 0644]
src/pmxcfs/cfs-plug-memdb.c [new file with mode: 0644]
src/pmxcfs/cfs-plug-memdb.h [new file with mode: 0644]
src/pmxcfs/cfs-plug.c [new file with mode: 0644]
src/pmxcfs/cfs-plug.h [new file with mode: 0644]
src/pmxcfs/cfs-utils.c [new file with mode: 0644]
src/pmxcfs/cfs-utils.h [new file with mode: 0644]
src/pmxcfs/check_memdb.c [new file with mode: 0644]
src/pmxcfs/confdb.c [new file with mode: 0644]
src/pmxcfs/confdb.h [new file with mode: 0644]
src/pmxcfs/create_pmxcfs_db.c [new file with mode: 0644]
src/pmxcfs/database.c [new file with mode: 0644]
src/pmxcfs/dcdb.c [new file with mode: 0644]
src/pmxcfs/dcdb.h [new file with mode: 0644]
src/pmxcfs/dfsm.c [new file with mode: 0644]
src/pmxcfs/dfsm.h [new file with mode: 0644]
src/pmxcfs/logger.c [new file with mode: 0644]
src/pmxcfs/logger.h [new file with mode: 0644]
src/pmxcfs/logtest.c [new file with mode: 0644]
src/pmxcfs/logtest2.c [new file with mode: 0644]
src/pmxcfs/loop.c [new file with mode: 0644]
src/pmxcfs/loop.h [new file with mode: 0644]
src/pmxcfs/memdb.c [new file with mode: 0644]
src/pmxcfs/memdb.h [new file with mode: 0644]
src/pmxcfs/pmxcfs.c [new file with mode: 0644]
src/pmxcfs/quorum.c [new file with mode: 0644]
src/pmxcfs/quorum.h [new file with mode: 0644]
src/pmxcfs/server.c [new file with mode: 0644]
src/pmxcfs/server.h [new file with mode: 0644]
src/pmxcfs/status.c [new file with mode: 0644]
src/pmxcfs/status.h [new file with mode: 0644]
src/test/Makefile [new file with mode: 0644]
src/test/corosync_configs/multiple-nodes.conf [new file with mode: 0644]
src/test/corosync_configs/old.conf [new file with mode: 0644]
src/test/corosync_configs/single-node.conf [new file with mode: 0644]
src/test/corosync_parser_test.pl [new file with mode: 0755]
src/test/cpgtest.c [new file with mode: 0644]
src/test/scripts/benchmark-config-get-property.pl [new file with mode: 0755]
src/test/scripts/create_large_files.pl [new file with mode: 0755]
src/test/scripts/create_many_files.pl [new file with mode: 0755]
src/test/scripts/create_vm_test.pl [new file with mode: 0755]
src/test/scripts/test-broadcast-kv.pl [new file with mode: 0755]
src/test/scripts/test-config-get-property.pl [new file with mode: 0755]
src/test/scripts/test-verify-token.pl [new file with mode: 0755]

index ea1a2ca58b25473f2ebcc6cfb63fb095d1d0210d..9ed5f7c50852e02bf101221a02a6626ba07c02dd 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -15,12 +15,9 @@ DEBS = $(DEB) $(DBG_DEB) $(LIB_DEB)
 
 all: $(DEB) $(DBG_DEB)
 
-cpgtest: cpgtest.c
-       gcc -Wall cpgtest.c $(shell pkg-config --cflags --libs libcpg libqb) -o cpgtest
-
 $(BUILDDIR):
        rm -rf $@ $@.tmp
-       cp -a data $@.tmp
+       cp -a src $@.tmp
        cp -a debian $@.tmp/
        echo "git clone git://git.proxmox.com/git/pve-cluster.git\\ngit checkout $(GITVERSION)" > $@.tmp/debian/SOURCE
        mv $@.tmp $@
diff --git a/cpgtest.c b/cpgtest.c
deleted file mode 100644 (file)
index 2cffe83..0000000
--- a/cpgtest.c
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
-  Copyright (C) 2009 Proxmox Server Solutions GmbH
-
-  Copyright: This program is under GNU GPL, the GNU General Public License.
-
-  This program is free software; you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; version 2 dated June, 1991.
-  
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-  02111-1307, USA.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <corosync/corotypes.h>
-#include <corosync/cpg.h>
-
-static int cpg_mode_leave;
-
-static void my_cpg_deliver_callback (
-        cpg_handle_t handle,
-        const struct cpg_name *groupName,
-        uint32_t nodeid,
-        uint32_t pid,
-        void *msg,
-        size_t msg_len)
-{
-       printf("got message form %d/%d\n", nodeid, pid);
-
-       cpg_mode_leave = 1;
-
-       return;
-}
-
-static void my_cpg_confchg_callback (
-        cpg_handle_t handle,
-        const struct cpg_name *groupName,
-        const struct cpg_address *member_list, size_t member_list_entries,
-        const struct cpg_address *left_list, size_t left_list_entries,
-        const struct cpg_address *joined_list, size_t joined_list_entries)
-{
-        int i;
-
-       printf("cpg_confchg_callback %ld joined, %ld left, %ld members\n",
-              joined_list_entries, left_list_entries, member_list_entries);
-
-       for (i = 0; i < member_list_entries; i++) {
-               printf("cpg member %d/%d\n", member_list[i].nodeid, member_list[i].pid);
-       }
-
-       /* send update message */
-       char *inbuf = "This is just a test message\n";
-       struct iovec iov;
-       iov.iov_base = inbuf;
-       iov.iov_len = strlen(inbuf)+1;
-
-       cs_error_t result;
-loop:
-       result = cpg_mcast_joined(handle, CPG_TYPE_AGREED, &iov, 1);
-       if (result == CS_ERR_TRY_AGAIN) {
-               usleep(1000);
-               printf("cpg_send_message retry");
-               goto loop;
-       }
-
-       if (result != CS_OK) 
-               printf("cpg_send_message failed: %d\n", result);
-
-}
-
-static cpg_callbacks_t callbacks = {
-        .cpg_deliver_fn =            my_cpg_deliver_callback,
-        .cpg_confchg_fn =            my_cpg_confchg_callback,
-};
-
-
-int main(int argc, char *argv[])
-{
-       struct cpg_name group_name;
-       char *gn = "TESTGROUP";
-       strcpy(group_name.value, gn);
-       group_name.length = strlen(gn) + 1;
-
-       cs_error_t result;
-       cpg_handle_t handle;
-
-start:
-       printf("starting cpgtest\n");
-
-       cpg_mode_leave = 0;
-       handle = 0;
-
-       printf("calling cpg_initialize\n");
-       result = cpg_initialize(&handle, &callbacks);
-       if (result != CS_OK) {
-               printf("cpg_initialize failed: %d\n", result);
-               goto retry;
-       }
-
-       printf("calling cpg_join\n");
-       while ((result = cpg_join(handle, &group_name)) == CS_ERR_TRY_AGAIN) { 
-               printf("cpg_join returned %d\n", result);
-               sleep (1);
-       }
-
-       if (result != CS_OK) {
-               printf("cpg_join failed: %d\n", result);
-               exit(-1);               
-       }
-
-       fd_set read_fds;
-       FD_ZERO(&read_fds);
-       int cpg_fd;
-
-       cpg_fd_get(handle, &cpg_fd);
-
-       printf("starting main loop\n");
-
-       do {
-               FD_SET(cpg_fd, &read_fds);
-               struct timeval timeout = { 1, 0};
-               result = select(cpg_fd + 1, &read_fds, 0, 0, &timeout);
-
-               if (result == -1) {
-                       printf("select error: %d\n", result);
-                       break;
-               } 
-               if (result > 0) {
-
-                       if (FD_ISSET(cpg_fd, &read_fds)) {                      
-                               cs_error_t res = cpg_dispatch(handle, CS_DISPATCH_ALL);
-                               if (res != CS_OK) {
-                                       printf("cpg_dispatch failed: %d\n", res);
-                                       break;
-                               }
-                       }
-               }
-
-               if (cpg_mode_leave)
-                       break;
-
-       } while(1);
-
-retry:
-
-       printf("end loop - trying to restart\n");
-
-       usleep (1000);
-
-       if (handle) {
-
-               result = cpg_finalize(handle);
-               if (result != CS_OK) {
-                       printf("cpg_finalize failed: %d\n", result);
-                       exit(-1);
-               }
-       }
-
-       goto start;
-
-       exit(0);
-}
diff --git a/data/Makefile b/data/Makefile
deleted file mode 100644 (file)
index 7c7a7de..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-SUBDIRS := PVE src test
-
-export LD_LIBRARY_PATH+=$(CURDIR)/PVE
-export PERLLIB+=$(CURDIR)/PVE
-
-all:
-       set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
-
-.PHONY: install
-install:
-       set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
-
-
-.PHONY: check
-check:
-       set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
-
-.PHONY: clean
-clean:
-       for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
diff --git a/data/PVE/API2/ClusterConfig.pm b/data/PVE/API2/ClusterConfig.pm
deleted file mode 100644 (file)
index e7efe37..0000000
+++ /dev/null
@@ -1,711 +0,0 @@
-package PVE::API2::ClusterConfig;
-
-use strict;
-use warnings;
-
-use PVE::Exception;
-use PVE::Tools;
-use PVE::SafeSyslog;
-use PVE::RESTHandler;
-use PVE::RPCEnvironment;
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::Cluster;
-use PVE::APIClient::LWP;
-use PVE::Corosync;
-use PVE::Cluster::Setup;
-
-use IO::Socket::UNIX;
-
-use base qw(PVE::RESTHandler);
-
-my $clusterconf = "/etc/pve/corosync.conf";
-my $authfile = "/etc/corosync/authkey";
-my $local_cluster_lock = "/var/lock/pvecm.lock";
-
-my $nodeid_desc = {
-    type => 'integer',
-    description => "Node id for this node.",
-    minimum => 1,
-    optional => 1,
-};
-PVE::JSONSchema::register_standard_option("corosync-nodeid", $nodeid_desc);
-
-__PACKAGE__->register_method({
-    name => 'index',
-    path => '',
-    method => 'GET',
-    description => "Directory index.",
-    permissions => {
-       check => ['perm', '/', [ 'Sys.Audit' ]],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {},
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {},
-       },
-       links => [ { rel => 'child', href => "{name}" } ],
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $result = [
-           { name => 'nodes' },
-           { name => 'totem' },
-           { name => 'join' },
-           { name => 'qdevice' },
-           { name => 'apiversion' },
-       ];
-
-       return $result;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'join_api_version',
-    path => 'apiversion',
-    method => 'GET',
-    description => "Return the version of the cluster join API available on this node.",
-    permissions => {
-       check => ['perm', '/', [ 'Sys.Audit' ]],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {},
-    },
-    returns => {
-       type => 'integer',
-       minimum => 0,
-       description => "Cluster Join API version, currently " . PVE::Cluster::Setup::JOIN_API_VERSION,
-    },
-    code => sub {
-       return PVE::Cluster::Setup::JOIN_API_VERSION;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'create',
-    path => '',
-    method => 'POST',
-    protected => 1,
-    description => "Generate new cluster configuration. If no links given,"
-                . " default to local IP address as link0.",
-    parameters => {
-       additionalProperties => 0,
-       properties => PVE::Corosync::add_corosync_link_properties({
-           clustername => {
-               description => "The name of the cluster.",
-               type => 'string', format => 'pve-node',
-               maxLength => 15,
-           },
-           nodeid => get_standard_option('corosync-nodeid'),
-           votes => {
-               type => 'integer',
-               description => "Number of votes for this node.",
-               minimum => 1,
-               optional => 1,
-           },
-       }),
-    },
-    returns => { type => 'string' },
-    code => sub {
-       my ($param) = @_;
-
-       die "cluster config '$clusterconf' already exists\n" if -f $clusterconf;
-
-       my $rpcenv = PVE::RPCEnvironment::get();
-       my $authuser = $rpcenv->get_user();
-
-       my $code = sub {
-           STDOUT->autoflush();
-           PVE::Cluster::Setup::setup_sshd_config(1);
-           PVE::Cluster::Setup::setup_rootsshconfig();
-           PVE::Cluster::Setup::setup_ssh_keys();
-
-           PVE::Tools::run_command(['/usr/sbin/corosync-keygen', '-lk', $authfile])
-               if !-f $authfile;
-           die "no authentication key available\n" if -f !$authfile;
-
-           my $nodename = PVE::INotify::nodename();
-
-           # get the corosync basis config for the new cluster
-           my $config = PVE::Corosync::create_conf($nodename, $param);
-
-           print "Writing corosync config to /etc/pve/corosync.conf\n";
-           PVE::Corosync::atomic_write_conf($config);
-
-           my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
-           PVE::Cluster::Setup::ssh_merge_keys();
-           PVE::Cluster::Setup::gen_pve_node_files($nodename, $local_ip_address);
-           PVE::Cluster::Setup::ssh_merge_known_hosts($nodename, $local_ip_address, 1);
-
-           print "Restart corosync and cluster filesystem\n";
-           PVE::Tools::run_command('systemctl restart corosync pve-cluster');
-       };
-
-       my $worker = sub {
-           PVE::Tools::lock_file($local_cluster_lock, 10, $code);
-           die $@ if $@;
-       };
-
-       return $rpcenv->fork_worker('clustercreate', $param->{clustername},  $authuser, $worker);
-}});
-
-__PACKAGE__->register_method({
-    name => 'nodes',
-    path => 'nodes',
-    method => 'GET',
-    description => "Corosync node list.",
-    permissions => {
-       check => ['perm', '/', [ 'Sys.Audit' ]],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {},
-    },
-    returns => {
-       type => 'array',
-       items => {
-           type => "object",
-           properties => {
-               node => { type => 'string' },
-           },
-       },
-       links => [ { rel => 'child', href => "{node}" } ],
-    },
-    code => sub {
-       my ($param) = @_;
-
-
-       my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
-       my $nodelist = PVE::Corosync::nodelist($conf);
-
-       return PVE::RESTHandler::hash_to_array($nodelist, 'node');
-    }});
-
-# lock method to ensure local and cluster wide atomicity
-# if we're a single node cluster just lock locally, we have no other cluster
-# node which we could contend with, else also acquire a cluster wide lock
-my $config_change_lock = sub {
-    my ($code) = @_;
-
-    PVE::Tools::lock_file($local_cluster_lock, 10, sub {
-       PVE::Cluster::cfs_update(1);
-       my $members = PVE::Cluster::get_members();
-       if (scalar(keys %$members) > 1) {
-           my $res = PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code);
-
-           # cfs_lock_file only sets $@ but lock_file doesn't propagates $@ unless we die here
-           die $@ if defined($@);
-
-           return $res;
-       } else {
-           return $code->();
-       }
-    });
-};
-
-__PACKAGE__->register_method ({
-    name => 'addnode',
-    path => 'nodes/{node}',
-    method => 'POST',
-    protected => 1,
-    description => "Adds a node to the cluster configuration. This call is for internal use.",
-    parameters => {
-       additionalProperties => 0,
-       properties => PVE::Corosync::add_corosync_link_properties({
-           node => get_standard_option('pve-node'),
-           nodeid => get_standard_option('corosync-nodeid'),
-           votes => {
-               type => 'integer',
-               description => "Number of votes for this node",
-               minimum => 0,
-               optional => 1,
-           },
-           force => {
-               type => 'boolean',
-               description => "Do not throw error if node already exists.",
-               optional => 1,
-           },
-           new_node_ip => {
-               type => 'string',
-               description => "IP Address of node to add. Used as fallback if no links are given.",
-               format => 'ip',
-               optional => 1,
-           },
-           apiversion => {
-               type => 'integer',
-               description => 'The JOIN_API_VERSION of the new node.',
-               optional => 1,
-           },
-       }),
-    },
-    returns => {
-       type => "object",
-       properties => {
-           corosync_authkey => {
-               type => 'string',
-           },
-           corosync_conf => {
-               type => 'string',
-           },
-           warnings => {
-               type => 'array',
-               items => {
-                   type => 'string',
-               },
-           },
-       },
-    },
-    code => sub {
-       my ($param) = @_;
-
-       $param->{apiversion} //= 0;
-       PVE::Cluster::Setup::assert_node_can_join_our_version($param->{apiversion});
-
-       PVE::Cluster::check_cfs_quorum();
-
-       my $vc_errors;
-       my $vc_warnings;
-
-       my $code = sub {
-           my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
-           my $nodelist = PVE::Corosync::nodelist($conf);
-           my $totem_cfg = PVE::Corosync::totem_config($conf);
-
-           ($vc_errors, $vc_warnings) = PVE::Corosync::verify_conf($conf);
-           die if scalar(@$vc_errors);
-
-           my $name = $param->{node};
-
-           # ensure we do not reuse an address, that can crash the whole cluster!
-           my $check_duplicate_addr = sub {
-               my $link = shift;
-               return if !defined($link) || !defined($link->{address});
-               my $addr = $link->{address};
-
-               while (my ($k, $v) = each %$nodelist) {
-                   next if $k eq $name; # allows re-adding a node if force is set
-
-                   for my $linknumber (0..PVE::Corosync::MAX_LINK_INDEX) {
-                       my $id = "ring${linknumber}_addr";
-                       next if !defined($v->{$id});
-
-                       die "corosync: address '$addr' already used on link $id by node '$k'\n"
-                           if $v->{$id} eq $addr;
-                   }
-               }
-           };
-
-           my $links = PVE::Corosync::extract_corosync_link_args($param);
-           foreach my $link (values %$links) {
-               $check_duplicate_addr->($link);
-           }
-
-           # Make sure that the newly added node has links compatible with the
-           # rest of the cluster. To start, extract all links that currently
-           # exist. Check any node, all have the same links here (because of
-           # verify_conf above).
-           my $node_options = (values %$nodelist)[0];
-           my $cluster_links = {};
-           foreach my $opt (keys %$node_options) {
-               my ($linktype, $linkid) = PVE::Corosync::parse_link_entry($opt);
-               next if !defined($linktype);
-               $cluster_links->{$linkid} = $node_options->{$opt};
-           }
-
-           # in case no fallback IP was passed, but instead only a single link,
-           # treat it as fallback to allow link-IDs to be matched automatically
-           # FIXME: remove in 8.0 or when joining an old node not supporting
-           # new_node_ip becomes infeasible otherwise
-           my $legacy_fallback = 0;
-           if (!$param->{new_node_ip} && scalar(%$links) == 1 && $param->{apiversion} == 0) {
-               my $passed_link_id = (keys %$links)[0];
-               my $passed_link = delete $links->{$passed_link_id};
-               $param->{new_node_ip} = $passed_link->{address};
-               $legacy_fallback = 1;
-           }
-
-           if (scalar(%$links)) {
-               # verify specified links exist and none are missing
-               for my $linknum (0..PVE::Corosync::MAX_LINK_INDEX) {
-                   my $have_cluster_link = defined($cluster_links->{$linknum});
-                   my $have_new_link = defined($links->{$linknum});
-
-                   die "corosync: link $linknum exists in cluster config but wasn't specified for new node\n"
-                       if $have_cluster_link && !$have_new_link;
-                   die "corosync: link $linknum specified for new node doesn't exist in cluster config\n"
-                       if !$have_cluster_link && $have_new_link;
-               }
-           } else {
-               # when called without any link parameters, fall back to
-               # new_node_ip, if all existing nodes only have a single link too
-               die "no links and no fallback ip (new_node_ip) given, cannot join cluster\n"
-                   if !$param->{new_node_ip};
-
-               my $cluster_link_count = scalar(%$cluster_links);
-               if ($cluster_link_count == 1) {
-                   my $linknum = (keys %$cluster_links)[0];
-                   $links->{$linknum} = { address => $param->{new_node_ip} };
-               } else {
-                   die "cluster has $cluster_link_count links, but only 1 given"
-                       if $legacy_fallback;
-                   die "no links given but multiple links found on other nodes,"
-                     . " fallback only supported on single-link clusters\n";
-               }
-           }
-
-           if (defined(my $res = $nodelist->{$name})) {
-               $param->{nodeid} = $res->{nodeid} if !$param->{nodeid};
-               $param->{votes} = $res->{quorum_votes} if !defined($param->{votes});
-
-               if ($res->{quorum_votes} == $param->{votes} &&
-                   $res->{nodeid} == $param->{nodeid} && $param->{force}) {
-                   print "forcing overwrite of configured node '$name'\n";
-               } else {
-                   die "can't add existing node '$name'\n";
-               }
-           } elsif (!$param->{nodeid}) {
-               my $nodeid = 1;
-
-               while(1) {
-                   my $found = 0;
-                   foreach my $v (values %$nodelist) {
-                       if ($v->{nodeid} eq $nodeid) {
-                           $found = 1;
-                           $nodeid++;
-                           last;
-                       }
-                   }
-                   last if !$found;
-               };
-
-               $param->{nodeid} = $nodeid;
-           }
-
-           $param->{votes} = 1 if !defined($param->{votes});
-
-           PVE::Cluster::Setup::gen_local_dirs($name);
-
-           eval { PVE::Cluster::Setup::ssh_merge_keys(); };
-           warn $@ if $@;
-
-           $nodelist->{$name} = {
-               nodeid => $param->{nodeid},
-               name => $name,
-           };
-           $nodelist->{$name}->{quorum_votes} = $param->{votes} if $param->{votes};
-
-           foreach my $link (keys %$links) {
-               $nodelist->{$name}->{"ring${link}_addr"} = $links->{$link}->{address};
-           }
-
-           PVE::Cluster::log_msg('notice', 'root@pam', "adding node $name to cluster");
-
-           PVE::Corosync::update_nodelist($conf, $nodelist);
-       };
-
-       $config_change_lock->($code);
-
-       # If vc_errors is set, we died because of verify_conf.
-       # Raise this error, since it contains more information than just a single-line string.
-       if (defined($vc_errors) && scalar(@$vc_errors)) {
-           my $err_hash = {};
-           my $add_errs = sub {
-               my ($type, @arr) = @_;
-               return if !scalar(@arr);
-               $err_hash->{"${type}$_"} = $arr[$_] for 0..$#arr;
-           };
-
-           $add_errs->("warning", @$vc_warnings);
-           $add_errs->("error", @$vc_errors);
-
-           PVE::Exception::raise("invalid corosync.conf\n", errors => $err_hash);
-       }
-
-       die $@ if $@;
-
-       my $res = {
-           corosync_authkey => PVE::Tools::file_get_contents($authfile),
-           corosync_conf => PVE::Tools::file_get_contents($clusterconf),
-           warnings => $vc_warnings,
-       };
-
-       return $res;
-    }});
-
-
-__PACKAGE__->register_method ({
-    name => 'delnode',
-    path => 'nodes/{node}',
-    method => 'DELETE',
-    protected => 1,
-    description => "Removes a node from the cluster configuration.",
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node'),
-       },
-    },
-    returns => { type => 'null' },
-    code => sub {
-       my ($param) = @_;
-
-       my $local_node = PVE::INotify::nodename();
-       die "Cannot delete myself from cluster!\n" if $param->{node} eq $local_node;
-
-       PVE::Cluster::check_cfs_quorum();
-
-       my $code = sub {
-           my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
-           my $nodelist = PVE::Corosync::nodelist($conf);
-
-           my $node;
-           my $nodeid;
-
-           foreach my $tmp_node (keys %$nodelist) {
-               my $d = $nodelist->{$tmp_node};
-               my $ring0_addr = $d->{ring0_addr};
-               my $ring1_addr = $d->{ring1_addr};
-               if (($tmp_node eq $param->{node}) ||
-                   (defined($ring0_addr) && ($ring0_addr eq $param->{node})) ||
-                   (defined($ring1_addr) && ($ring1_addr eq $param->{node}))) {
-                   $node = $tmp_node;
-                   $nodeid = $d->{nodeid};
-                   last;
-               }
-           }
-
-           die "Node/IP: $param->{node} is not a known host of the cluster.\n"
-               if !defined($node);
-
-           PVE::Cluster::log_msg('notice', 'root@pam', "deleting node $node from cluster");
-
-           delete $nodelist->{$node};
-
-           # allowed to fail when node is already shut down!
-           eval {
-               PVE::Tools::run_command(['corosync-cfgtool','-k', $nodeid])
-                   if defined($nodeid);
-           };
-
-           PVE::Corosync::update_nodelist($conf, $nodelist);
-       };
-
-       $config_change_lock->($code);
-       die $@ if $@;
-
-       return undef;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'join_info',
-    path => 'join',
-    permissions => {
-       check => ['perm', '/', [ 'Sys.Audit' ]],
-    },
-    method => 'GET',
-    description => "Get information needed to join this cluster over the connected node.",
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           node => get_standard_option('pve-node', {
-               description => "The node for which the joinee gets the nodeinfo. ",
-               default => "current connected node",
-               optional => 1,
-           }),
-       },
-    },
-    returns => {
-       type => 'object',
-       additionalProperties => 0,
-       properties => {
-           nodelist => {
-               type => 'array',
-               items => {
-                   type => "object",
-                   additionalProperties => 1,
-                   properties => {
-                       name => get_standard_option('pve-node'),
-                       nodeid => get_standard_option('corosync-nodeid'),
-                       ring0_addr => get_standard_option('corosync-link'),
-                       quorum_votes => { type => 'integer', minimum => 0 },
-                       pve_addr => { type => 'string', format => 'ip' },
-                       pve_fp => get_standard_option('fingerprint-sha256'),
-                   },
-               },
-           },
-           preferred_node => get_standard_option('pve-node'),
-           totem => { type => 'object' },
-           config_digest => { type => 'string' },
-       },
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $nodename = $param->{node} // PVE::INotify::nodename();
-
-       PVE::Cluster::cfs_update(1);
-       my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
-
-       # FIXME: just return undef or empty object in PVE 8.0 (check if manager can handle it!)
-       PVE::Exception::raise(
-           'node is not in a cluster, no join info available!',
-           code => HTTP::Status::HTTP_FAILED_DEPENDENCY,
-       ) if !($conf && $conf->{main});
-
-       my $totem_cfg = $conf->{main}->{totem} // {};
-       my $nodelist = $conf->{main}->{nodelist}->{node} // {};
-       my $corosync_config_digest = $conf->{digest};
-
-       die "unknown node '$nodename'\n" if ! $nodelist->{$nodename};
-
-       foreach my $name (keys %$nodelist) {
-           my $node = $nodelist->{$name};
-           $node->{pve_fp} = PVE::Cluster::get_node_fingerprint($name);
-           $node->{pve_addr} = scalar(PVE::Cluster::remote_node_ip($name));
-       }
-
-       my $res = {
-           nodelist => [ values %$nodelist ],
-           preferred_node => $nodename,
-           totem => $totem_cfg,
-           config_digest => $corosync_config_digest,
-       };
-
-       return $res;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'join',
-    path => 'join',
-    method => 'POST',
-    protected => 1,
-    description => "Joins this node into an existing cluster. If no links are"
-                . " given, default to IP resolved by node's hostname on single"
-                . " link (fallback fails for clusters with multiple links).",
-    parameters => {
-       additionalProperties => 0,
-       properties => PVE::Corosync::add_corosync_link_properties({
-           hostname => {
-               type => 'string',
-               description => "Hostname (or IP) of an existing cluster member."
-           },
-           nodeid => get_standard_option('corosync-nodeid'),
-           votes => {
-               type => 'integer',
-               description => "Number of votes for this node",
-               minimum => 0,
-               optional => 1,
-           },
-           force => {
-               type => 'boolean',
-               description => "Do not throw error if node already exists.",
-               optional => 1,
-           },
-           fingerprint => get_standard_option('fingerprint-sha256'),
-           password => {
-               description => "Superuser (root) password of peer node.",
-               type => 'string',
-               maxLength => 128,
-           },
-       }),
-    },
-    returns => { type => 'string' },
-    code => sub {
-       my ($param) = @_;
-
-       my $rpcenv = PVE::RPCEnvironment::get();
-       my $authuser = $rpcenv->get_user();
-
-       my $worker = sub {
-           STDOUT->autoflush();
-           PVE::Tools::lock_file($local_cluster_lock, 10, \&PVE::Cluster::Setup::join, $param);
-           die $@ if $@;
-       };
-
-       return $rpcenv->fork_worker('clusterjoin', undef,  $authuser, $worker);
-    }});
-
-
-__PACKAGE__->register_method({
-    name => 'totem',
-    path => 'totem',
-    method => 'GET',
-    description => "Get corosync totem protocol settings.",
-    permissions => {
-       check => ['perm', '/', [ 'Sys.Audit' ]],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {},
-    },
-    returns => {
-       type => "object",
-       properties => {},
-    },
-    code => sub {
-       my ($param) = @_;
-
-
-       my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
-
-       my $totem_cfg = $conf->{main}->{totem};
-
-       return $totem_cfg;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'status',
-    path => 'qdevice',
-    method => 'GET',
-    protected => 1,
-    description => 'Get QDevice status',
-    permissions => {
-       check => ['perm', '/', [ 'Sys.Audit' ]],
-    },
-    parameters => {
-       additionalProperties => 0,
-       properties => {},
-    },
-    returns => {
-       type => "object",
-    },
-    code => sub {
-       my ($param) = @_;
-
-       my $result = {};
-       my $socket_path = "/var/run/corosync-qdevice/corosync-qdevice.sock";
-       return $result if !-S $socket_path;
-
-       my $qdevice_socket = IO::Socket::UNIX->new(
-           Type => SOCK_STREAM,
-           Peer => $socket_path,
-       );
-
-       print $qdevice_socket "status verbose\n";
-       my $qdevice_keys = {
-           "Algorithm" => 1,
-           "Echo reply" => 1,
-           "Last poll call" => 1,
-           "Model" => 1,
-           "QNetd host" => 1,
-           "State" => 1,
-           "Tie-breaker" => 1,
-       };
-       while (my $line = <$qdevice_socket>) {
-           chomp $line;
-           next if $line =~ /^\s/;
-           if ($line =~ /^(.*?)\s*:\s*(.*)$/) {
-               $result->{$1} = $2 if $qdevice_keys->{$1};
-           }
-       }
-
-       return $result;
-    }});
-#TODO: possibly add setup and remove methods
-
-
-1;
diff --git a/data/PVE/API2/Makefile b/data/PVE/API2/Makefile
deleted file mode 100644 (file)
index 0eeba51..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-PVEDIR=${DESTDIR}/usr/share/perl5/PVE
-
-SOURCES=ClusterConfig.pm
-
-.PHONY: install
-install: ${SOURCES}
-       install -d ${PVEDIR}/API2/
-       for f in ${SOURCES}; do install -m 0664 $$f ${PVEDIR}/API2/$$f; done
-
-.PHONY: clean
-clean:
diff --git a/data/PVE/CLI/Makefile b/data/PVE/CLI/Makefile
deleted file mode 100644 (file)
index 1e58e2f..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-PVEDIR=${DESTDIR}/usr/share/perl5/PVE
-
-SOURCES=pvecm.pm
-
-.PHONY: install
-install: ${SOURCES}
-       install -d -m 0755 ${PVEDIR}/CLI
-       for f in ${SOURCES}; do install -D -m 0644 $$f ${PVEDIR}/CLI/$$f; done
-
-.PHONY: clean
-clean:
diff --git a/data/PVE/CLI/pvecm.pm b/data/PVE/CLI/pvecm.pm
deleted file mode 100755 (executable)
index b0b5931..0000000
+++ /dev/null
@@ -1,717 +0,0 @@
-package PVE::CLI::pvecm;
-
-use strict;
-use warnings;
-
-use Cwd qw(getcwd);
-use File::Path;
-use File::Basename;
-use PVE::Tools qw(run_command);
-use PVE::Cluster;
-use PVE::INotify;
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::RPCEnvironment;
-use PVE::CLIHandler;
-use PVE::PTY;
-use PVE::API2::ClusterConfig;
-use PVE::Corosync;
-use PVE::Cluster::Setup;
-
-use base qw(PVE::CLIHandler);
-
-$ENV{HOME} = '/root'; # for ssh-copy-id
-
-my $basedir = "/etc/pve";
-my $clusterconf = "$basedir/corosync.conf";
-my $libdir = "/var/lib/pve-cluster";
-my $authfile = "/etc/corosync/authkey";
-
-
-sub setup_environment {
-    PVE::RPCEnvironment->setup_default_cli_env();
-}
-
-__PACKAGE__->register_method ({
-    name => 'keygen',
-    path => 'keygen',
-    method => 'PUT',
-    description => "Generate new cryptographic key for corosync.",
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           filename => {
-               type => 'string',
-               description => "Output file name"
-           }
-       },
-    },
-    returns => { type => 'null' },
-
-    code => sub {
-       my ($param) = @_;
-
-       my $filename = $param->{filename};
-
-       # test EUID
-       $> == 0 || die "Error: Authorization key must be generated as root user.\n";
-       my $dirname = dirname($filename);
-
-       die "key file '$filename' already exists\n" if -e $filename;
-
-       File::Path::make_path($dirname) if $dirname;
-
-       run_command(['corosync-keygen', '-l', '-k', $filename]);
-
-       return undef;
-    }});
-
-my $foreach_member = sub {
-    my ($code, $noerr) = @_;
-
-    my $members = PVE::Cluster::get_members();
-    foreach my $node (sort keys %$members) {
-       if (my $ip = $members->{$node}->{ip}) {
-           $code->($node, $ip);
-       } else {
-           die "cannot get the cluster IP for node '$node'.\n" if !$noerr;
-           warn "cannot get the cluster IP for node '$node'.\n";
-           return undef;
-       }
-    }
-};
-
-__PACKAGE__->register_method ({
-    name => 'setup_qdevice',
-    path => 'setup_qdevice',
-    method => 'PUT',
-    description => "Setup the use of a QDevice",
-    parameters => {
-        additionalProperties => 0,
-       properties => {
-           address => {
-               type => 'string', format => 'ip',
-               description => "Specifies the network address of an external corosync QDevice" ,
-           },
-           network => {
-               type => 'string',
-               format => 'CIDR',
-               description => 'The network which should be used to connect to the external qdevice',
-               optional => 1,
-           },
-           force => {
-               type => 'boolean',
-               description => "Do not throw error on possible dangerous operations.",
-               optional => 1,
-           },
-       },
-    },
-    returns => { type => 'null' },
-
-    code => sub {
-       my ($param) = @_;
-
-       PVE::Corosync::check_conf_exists();
-
-       my $members = PVE::Cluster::get_members();
-       foreach my $node (sort keys %$members) {
-           die "All nodes must be online! Node $node is offline, aborting.\n"
-               if !$members->{$node}->{online};
-       }
-
-       my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
-
-       die "QDevice already configured!\n"
-           if defined($conf->{main}->{quorum}->{device}) && !$param->{force};
-
-       my $network = $param->{network};
-
-       my $model = "net";
-       my $algorithm = 'ffsplit';
-       if (scalar(%{$members}) & 1) {
-           if ($param->{force}) {
-               $algorithm = 'lms';
-           } else {
-               die "Clusters with an odd node count are not officially supported!\n";
-           }
-       }
-
-       my $qnetd_addr = $param->{address};
-       my $base_dir = "/etc/corosync/qdevice/net";
-       my $db_dir_qnetd = "/etc/corosync/qnetd/nssdb";
-       my $db_dir_node = "$base_dir/nssdb";
-       my $ca_export_base = "qnetd-cacert.crt";
-       my $ca_export_file = "$db_dir_qnetd/$ca_export_base";
-       my $crq_file_base = "qdevice-net-node.crq";
-       my $p12_file_base = "qdevice-net-node.p12";
-       my $qdevice_certutil = "corosync-qdevice-net-certutil";
-       my $qnetd_certutil= "corosync-qnetd-certutil";
-       my $clustername = $conf->{main}->{totem}->{cluster_name};
-
-       run_command(['ssh-copy-id', '-i', '/root/.ssh/id_rsa', "root\@$qnetd_addr"]);
-
-       if (-d $db_dir_node) {
-           # FIXME: check on all nodes?!
-           if ($param->{force}) {
-               rmtree $db_dir_node;
-           } else {
-               die "QDevice certificate store already initialised, set force to delete!\n";
-           }
-       }
-
-       my $ssh_cmd = ['ssh', '-o', 'BatchMode=yes', '-lroot'];
-       my $scp_cmd = ['scp', '-o', 'BatchMode=yes'];
-
-       print "\nINFO: initializing qnetd server\n";
-       run_command(
-           [@$ssh_cmd, $qnetd_addr, $qnetd_certutil, "-i"],
-           noerr => 1
-       );
-
-       print "\nINFO: copying CA cert and initializing on all nodes\n";
-       run_command([@$scp_cmd, "root\@\[$qnetd_addr\]:$ca_export_file", "/etc/pve/$ca_export_base"]);
-       $foreach_member->(sub {
-           my ($node, $ip) = @_;
-           my $outsub = sub { print "\nnode '$node': " . shift };
-           run_command(
-               [@$ssh_cmd, $ip, $qdevice_certutil, "-i", "-c", "/etc/pve/$ca_export_base"],
-               noerr => 1, outfunc => \&$outsub
-           );
-       });
-       unlink "/etc/pve/$ca_export_base";
-
-       print "\nINFO: generating cert request\n";
-       run_command([$qdevice_certutil, "-r", "-n", $clustername]);
-
-       print "\nINFO: copying exported cert request to qnetd server\n";
-       run_command([@$scp_cmd, "$db_dir_node/$crq_file_base", "root\@\[$qnetd_addr\]:/tmp"]);
-
-       print "\nINFO: sign and export cluster cert\n";
-       run_command([
-               @$ssh_cmd, $qnetd_addr, $qnetd_certutil, "-s", "-c",
-               "/tmp/$crq_file_base", "-n", "$clustername"
-           ]);
-
-       print "\nINFO: copy exported CRT\n";
-       run_command([
-               @$scp_cmd, "root\@\[$qnetd_addr\]:$db_dir_qnetd/cluster-$clustername.crt",
-               "$db_dir_node"
-           ]);
-
-       print "\nINFO: import certificate\n";
-       run_command(["$qdevice_certutil", "-M", "-c", "$db_dir_node/cluster-$clustername.crt"]);
-
-       print "\nINFO: copy and import pk12 cert to all nodes\n";
-       run_command([@$scp_cmd, "$db_dir_node/$p12_file_base", "/etc/pve/"]);
-       $foreach_member->(sub {
-           my ($node, $ip) = @_;
-           my $outsub = sub { print "\nnode '$node': " . shift };
-           run_command([
-                   @$ssh_cmd, $ip, "$qdevice_certutil", "-m", "-c",
-                   "/etc/pve/$p12_file_base"], outfunc => \&$outsub
-               );
-       });
-       unlink "/etc/pve/$p12_file_base";
-
-
-       my $code = sub {
-           my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
-           my $quorum_section = $conf->{main}->{quorum};
-
-           die "Qdevice already configured, must be removed before setting up new one!\n"
-               if defined($quorum_section->{device}); # must not be forced!
-
-           my $qdev_section = {
-               model => $model,
-               "$model" => {
-                   tls => 'on',
-                   host => $qnetd_addr,
-                   algorithm => $algorithm,
-               }
-           };
-           $qdev_section->{votes} = 1 if $algorithm eq 'ffsplit';
-
-           $quorum_section->{device} = $qdev_section;
-
-           PVE::Corosync::atomic_write_conf($conf);
-       };
-
-       print "\nINFO: add QDevice to cluster configuration\n";
-       PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code);
-       die $@ if $@;
-
-       $foreach_member->(sub {
-           my ($node, $ip) = @_;
-           my $outsub = sub { print "\nnode '$node': " . shift };
-           print "\nINFO: start and enable corosync qdevice daemon on node '$node'...\n";
-           run_command([@$ssh_cmd, $ip, 'systemctl', 'start', 'corosync-qdevice'], outfunc => \&$outsub);
-           run_command([@$ssh_cmd, $ip, 'systemctl', 'enable', 'corosync-qdevice'], outfunc => \&$outsub);
-       });
-
-       run_command(['corosync-cfgtool', '-R']); # do cluster wide config reload
-
-       return undef;
-}});
-
-__PACKAGE__->register_method ({
-    name => 'remove_qdevice',
-    path => 'remove_qdevice',
-    method => 'DELETE',
-    description => "Remove a configured QDevice",
-    parameters => {
-        additionalProperties => 0,
-       properties => {},
-    },
-    returns => { type => 'null' },
-
-    code => sub {
-       my ($param) = @_;
-
-       PVE::Corosync::check_conf_exists();
-
-       my $members = PVE::Cluster::get_members();
-       foreach my $node (sort keys %$members) {
-           die "All nodes must be online! Node $node is offline, aborting.\n"
-               if !$members->{$node}->{online};
-       }
-
-       my $ssh_cmd = ['ssh', '-o', 'BatchMode=yes', '-lroot'];
-
-       my $code = sub {
-           my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
-           my $quorum_section = $conf->{main}->{quorum};
-
-           die "No QDevice configured!\n" if !defined($quorum_section->{device});
-
-           delete $quorum_section->{device};
-
-           PVE::Corosync::atomic_write_conf($conf);
-
-           # cleanup qdev state (cert storage)
-           my $qdev_state_dir =  "/etc/corosync/qdevice";
-           $foreach_member->(sub {
-               my (undef, $ip) = @_;
-               run_command([@$ssh_cmd, $ip, '--', 'rm', '-rf', $qdev_state_dir]);
-           });
-       };
-
-       PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code);
-       die $@ if $@;
-
-       $foreach_member->(sub {
-           my (undef, $ip) = @_;
-           run_command([@$ssh_cmd, $ip, 'systemctl', 'stop', 'corosync-qdevice']);
-           run_command([@$ssh_cmd, $ip, 'systemctl', 'disable', 'corosync-qdevice']);
-       });
-
-       run_command(['corosync-cfgtool', '-R']);
-
-       print "\nRemoved Qdevice.\n";
-
-       return undef;
-}});
-
-__PACKAGE__->register_method ({
-    name => 'add',
-    path => 'add',
-    method => 'PUT',
-    description => "Adds the current node to an existing cluster.",
-    parameters => {
-       additionalProperties => 0,
-       properties => PVE::Corosync::add_corosync_link_properties({
-           hostname => {
-               type => 'string',
-               description => "Hostname (or IP) of an existing cluster member."
-           },
-           nodeid => get_standard_option('corosync-nodeid'),
-           votes => {
-               type => 'integer',
-               description => "Number of votes for this node",
-               minimum => 0,
-               optional => 1,
-           },
-           force => {
-               type => 'boolean',
-               description => "Do not throw error if node already exists.",
-               optional => 1,
-           },
-           fingerprint => get_standard_option('fingerprint-sha256', {
-               optional => 1,
-           }),
-           'use_ssh' => {
-               type => 'boolean',
-               description => "Always use SSH to join, even if peer may do it over API.",
-               optional => 1,
-           },
-       }),
-    },
-    returns => { type => 'null' },
-
-    code => sub {
-       my ($param) = @_;
-
-       # avoid "transport endpoint not connected" errors that occur if
-       # restarting pmxcfs while in fuse-mounted /etc/pve
-       die "Navigate out of $basedir before running 'pvecm add', for example by running 'cd'.\n"
-           if getcwd() =~ m!^$basedir(/.*)?$!;
-
-       my $nodename = PVE::INotify::nodename();
-       my $host = $param->{hostname};
-
-       my $worker = sub {
-
-           if (!$param->{use_ssh}) {
-               my $password = PVE::PTY::read_password("Please enter superuser (root) password for '$host': ");
-
-               delete $param->{use_ssh};
-               $param->{password} = $password;
-
-               my $local_cluster_lock = "/var/lock/pvecm.lock";
-               PVE::Tools::lock_file($local_cluster_lock, 10, \&PVE::Cluster::Setup::join, $param);
-
-               if (my $err = $@) {
-                   if (ref($err) eq 'PVE::APIClient::Exception' && defined($err->{code}) && $err->{code} == 501) {
-                       $err = "Remote side is not able to use API for Cluster join!\n" .
-                              "Pass the 'use_ssh' switch or update the remote side.\n";
-                   }
-                   die $err;
-               }
-               return; # all OK, the API join endpoint successfully set us up
-           }
-
-           # allow fallback to old ssh only join if wished or needed
-
-           my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
-           my $links = PVE::Corosync::extract_corosync_link_args($param);
-
-           PVE::Cluster::Setup::assert_joinable($local_ip_address, $links, $param->{force});
-
-           PVE::Cluster::Setup::setup_sshd_config();
-           PVE::Cluster::Setup::setup_rootsshconfig();
-           PVE::Cluster::Setup::setup_ssh_keys();
-
-           # make sure known_hosts is on local filesystem
-           PVE::Cluster::Setup::ssh_unmerge_known_hosts();
-
-           my $cmd = ['ssh-copy-id', '-i', '/root/.ssh/id_rsa', "root\@$host"];
-           run_command($cmd, 'outfunc' => sub {}, 'errfunc' => sub {},
-                                   'errmsg' => "unable to copy ssh ID");
-
-           $cmd = ['ssh', $host, '-o', 'BatchMode=yes', 'pvecm', 'apiver'];
-           my $remote_apiver = 0;
-           run_command($cmd, 'outfunc' => sub {
-               $remote_apiver = shift;
-               chomp $remote_apiver;
-           }, 'noerr' => 1);
-
-           PVE::Cluster::Setup::assert_we_can_join_cluster_version($remote_apiver);
-
-           $cmd = ['ssh', $host, '-o', 'BatchMode=yes',
-                   'pvecm', 'addnode', $nodename, '--force', 1];
-
-           push @$cmd, '--nodeid', $param->{nodeid} if $param->{nodeid};
-           push @$cmd, '--votes', $param->{votes} if defined($param->{votes});
-
-           my $link_desc = get_standard_option('corosync-link');
-
-           foreach my $link (keys %$links) {
-               push @$cmd, "--link$link", PVE::JSONSchema::print_property_string(
-                   $links->{$link}, $link_desc->{format});
-           }
-
-           # this will be used as fallback if no links are specified
-           if (!%$links) {
-               push @$cmd, '--link0', $local_ip_address if $remote_apiver == 0;
-               push @$cmd, '--new_node_ip', $local_ip_address if $remote_apiver >= 1;
-
-               print "No cluster network links passed explicitly, fallback to local node"
-                   . " IP '$local_ip_address'\n";
-           }
-
-           if (system (@$cmd) != 0) {
-               my $cmdtxt = join (' ', @$cmd);
-               die "unable to add node: command failed ($cmdtxt)\n";
-           }
-
-           my $tmpdir = "$libdir/.pvecm_add.tmp.$$";
-           mkdir $tmpdir;
-
-           eval {
-               print "copy corosync auth key\n";
-               $cmd = ['rsync', '--rsh=ssh -l root -o BatchMode=yes', '-lpgoq',
-                       "[$host]:$authfile $clusterconf", $tmpdir];
-
-               system(@$cmd) == 0 || die "can't rsync data from host '$host'\n";
-
-               my $corosync_conf = PVE::Tools::file_get_contents("$tmpdir/corosync.conf");
-               my $corosync_authkey = PVE::Tools::file_get_contents("$tmpdir/authkey");
-
-               PVE::Cluster::Setup::finish_join($nodename, $corosync_conf, $corosync_authkey);
-           };
-           my $err = $@;
-
-           rmtree $tmpdir;
-
-           die $err if $err;
-       };
-
-       # use a synced worker so we get a nice task log when joining through CLI
-       my $rpcenv = PVE::RPCEnvironment::get();
-       my $authuser = $rpcenv->get_user();
-
-       $rpcenv->fork_worker('clusterjoin', '',  $authuser, $worker);
-
-       return undef;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'status',
-    path => 'status',
-    method => 'GET',
-    description => "Displays the local view of the cluster status.",
-    parameters => {
-       additionalProperties => 0,
-       properties => {},
-    },
-    returns => { type => 'null' },
-
-    code => sub {
-       my ($param) = @_;
-
-       PVE::Corosync::check_conf_exists();
-       my $conf = eval { PVE::Cluster::cfs_read_file("corosync.conf") } // {};
-       warn "$@" if $@;
-       my $totem = PVE::Corosync::totem_config($conf);
-
-       if (scalar(%$totem)) {
-           my $print_info = sub {
-               my ($label, $key, $default) = @_;
-               my $val = $totem->{$key} // $default;
-               printf "%-17s %s\n", "$label:", "$val";
-           };
-
-           printf "Cluster information\n";
-           printf "-------------------\n";
-           $print_info->('Name', 'cluster_name', 'UNKOWN?');
-           $print_info->('Config Version', 'config_version', -1);
-           $print_info->('Transport', 'transport', 'knet');
-           $print_info->('Secure auth', 'secauth', 'off');
-           printf "\n";
-       }
-
-       exec ('corosync-quorumtool', '-siH');
-       exit (-1); # should not be reached
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'nodes',
-    path => 'nodes',
-    method => 'GET',
-    description => "Displays the local view of the cluster nodes.",
-    parameters => {
-       additionalProperties => 0,
-       properties => {},
-    },
-    returns => { type => 'null' },
-
-    code => sub {
-       my ($param) = @_;
-
-       PVE::Corosync::check_conf_exists();
-
-       exec ('corosync-quorumtool', '-l');
-       exit (-1); # should not be reached
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'expected',
-    path => 'expected',
-    method => 'PUT',
-    description => "Tells corosync a new value of expected votes.",
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           expected => {
-               type => 'integer',
-               description => "Expected votes",
-               minimum => 1,
-           },
-       },
-    },
-    returns => { type => 'null' },
-
-    code => sub {
-       my ($param) = @_;
-
-       PVE::Corosync::check_conf_exists();
-
-       exec ('corosync-quorumtool', '-e', $param->{expected});
-       exit (-1); # should not be reached
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'updatecerts',
-    path => 'updatecerts',
-    method => 'PUT',
-    description => "Update node certificates (and generate all needed files/directories).",
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           force => {
-               description => "Force generation of new SSL certificate.",
-               type => 'boolean',
-               optional => 1,
-           },
-           silent => {
-               description => "Ignore errors (i.e. when cluster has no quorum).",
-               type => 'boolean',
-               optional => 1,
-           },
-       },
-    },
-    returns => { type => 'null' },
-    code => sub {
-       my ($param) = @_;
-
-       # we get called by the pve-cluster.service ExecStartPost and as we do
-       # IO (on /etc/pve) which can hang (uninterruptedly D state). That'd be
-       # no-good for ExecStartPost as it fails the whole service in this case
-       PVE::Tools::run_fork_with_timeout(30, sub {
-           PVE::Cluster::Setup::updatecerts_and_ssh($param->@{qw(force silent)});
-           PVE::Cluster::prepare_observed_file_basedirs();
-       });
-
-       return undef;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'mtunnel',
-    path => 'mtunnel',
-    method => 'POST',
-    description => "Used by VM/CT migration - do not use manually.",
-    parameters => {
-       additionalProperties => 0,
-       properties => {
-           get_migration_ip => {
-               type => 'boolean',
-               default => 0,
-               description => 'return the migration IP, if configured',
-               optional => 1,
-           },
-           migration_network => {
-               type => 'string',
-               format => 'CIDR',
-               description => 'the migration network used to detect the local migration IP',
-               optional => 1,
-           },
-           'run-command' => {
-               type => 'boolean',
-               description => 'Run a command with a tcp socket as standard input.'
-                             .' The IP address and port are printed via this'
-                             ." command's stdandard output first, each on a separate line.",
-               optional => 1,
-           },
-           'extra-args' => PVE::JSONSchema::get_standard_option('extra-args'),
-       },
-    },
-    returns => { type => 'null'},
-    code => sub {
-       my ($param) = @_;
-
-       if (!PVE::Cluster::check_cfs_quorum(1)) {
-           print "no quorum\n";
-           return undef;
-       }
-
-       my $get_local_migration_ip = sub {
-           my ($cidr) = @_;
-
-           if (!defined($cidr)) {
-               my $dc_conf = cfs_read_file('datacenter.cfg');
-               $cidr = $dc_conf->{migration}->{network}
-               if defined($dc_conf->{migration}->{network});
-           }
-
-           if (defined($cidr)) {
-               my $ips = PVE::Network::get_local_ip_from_cidr($cidr);
-
-               die "could not get migration ip: no IP address configured on local " .
-                   "node for network '$cidr'\n" if scalar(@$ips) == 0;
-
-               die "could not get migration ip: multiple, different, IP address configured for " .
-                   "network '$cidr'\n" if scalar(@$ips) > 1 && grep { @$ips[0] ne $_ } @$ips;
-
-               return @$ips[0];
-           }
-
-           return undef;
-       };
-
-       my $network = $param->{migration_network};
-       if ($param->{get_migration_ip}) {
-           die "cannot use --run-command with --get_migration_ip\n"
-               if $param->{'run-command'};
-
-           if (my $ip = $get_local_migration_ip->($network)) {
-               print "ip: '$ip'\n";
-           } else {
-               print "no ip\n";
-           }
-           # do not keep tunnel open when asked for migration ip
-           return undef;
-       }
-
-       if ($param->{'run-command'}) {
-           my $cmd = $param->{'extra-args'};
-           die "missing command\n"
-               if !$cmd || !scalar(@$cmd);
-
-           # Get an ip address to listen on, and find a free migration port
-           my ($ip, $family);
-           if (defined($network)) {
-               $ip = $get_local_migration_ip->($network)
-                   or die "failed to get migration IP address to listen on\n";
-               $family = PVE::Tools::get_host_address_family($ip);
-           } else {
-               my $nodename = PVE::INotify::nodename();
-               ($ip, $family) = PVE::Network::get_ip_from_hostname($nodename, 0);
-           }
-           my $port = PVE::Tools::next_migrate_port($family, $ip);
-
-           PVE::Tools::pipe_socket_to_command($cmd, $ip, $port);
-           return undef;
-       }
-
-       print "tunnel online\n";
-       *STDOUT->flush();
-
-       while (my $line = <STDIN>) {
-           chomp $line;
-           last if $line =~ m/^quit$/;
-       }
-
-       return undef;
-    }});
-
-
-our $cmddef = {
-    apiver => [ 'PVE::API2::ClusterConfig', 'join_api_version', [], {}, sub {
-       my $apiver = shift;
-       print "$apiver\n";
-    }],
-    keygen => [ __PACKAGE__, 'keygen', ['filename']],
-    create => [ 'PVE::API2::ClusterConfig', 'create', ['clustername']],
-    add => [ __PACKAGE__, 'add', ['hostname']],
-    addnode => [ 'PVE::API2::ClusterConfig', 'addnode', ['node']],
-    delnode => [ 'PVE::API2::ClusterConfig', 'delnode', ['node']],
-    status => [ __PACKAGE__, 'status' ],
-    nodes => [ __PACKAGE__, 'nodes' ],
-    expected => [ __PACKAGE__, 'expected', ['expected']],
-    updatecerts => [ __PACKAGE__, 'updatecerts', []],
-    mtunnel => [ __PACKAGE__, 'mtunnel', ['extra-args']],
-    qdevice => {
-       setup => [ __PACKAGE__, 'setup_qdevice', ['address']],
-       remove => [ __PACKAGE__, 'remove_qdevice', []],
-    }
-};
-
-1;
diff --git a/data/PVE/Cluster.pm b/data/PVE/Cluster.pm
deleted file mode 100644 (file)
index efca58f..0000000
+++ /dev/null
@@ -1,903 +0,0 @@
-package PVE::Cluster;
-
-use strict;
-use warnings;
-
-use Encode;
-use File::stat qw();
-use File::Path qw(make_path);
-use JSON;
-use Net::SSLeay;
-use POSIX qw(ENOENT);
-use Socket;
-use Storable qw(dclone);
-
-use PVE::Certificate;
-use PVE::INotify;
-use PVE::IPCC;
-use PVE::JSONSchema;
-use PVE::Network;
-use PVE::SafeSyslog;
-use PVE::Tools qw(run_command);
-
-use PVE::Cluster::IPCConst;
-
-use base 'Exporter';
-
-our @EXPORT_OK = qw(
-cfs_read_file
-cfs_write_file
-cfs_register_file
-cfs_lock_file);
-
-# x509 certificate utils
-
-my $basedir = "/etc/pve";
-my $authdir = "$basedir/priv";
-my $lockdir = "/etc/pve/priv/lock";
-
-# cfs and corosync files
-my $dbfile = "/var/lib/pve-cluster/config.db";
-my $dbbackupdir = "/var/lib/pve-cluster/backup";
-
-# this is just a readonly copy, the relevant one is in status.c from pmxcfs
-# observed files are the one we can get directly through IPCC, they are cached
-# using a computed version and only those can be used by the cfs_*_file methods
-my $observed = {
-    'vzdump.cron' => 1,
-    'vzdump.conf' => 1,
-    'jobs.cfg' => 1,
-    'storage.cfg' => 1,
-    'datacenter.cfg' => 1,
-    'replication.cfg' => 1,
-    'corosync.conf' => 1,
-    'corosync.conf.new' => 1,
-    'firewall/cluster.fw' => 1,
-    'user.cfg' => 1,
-    'domains.cfg' => 1,
-    'priv/shadow.cfg' => 1,
-    'priv/tfa.cfg' => 1,
-    'priv/token.cfg' => 1,
-    'priv/acme/plugins.cfg' => 1,
-    'priv/ipam.db' => 1,
-    '/qemu-server/' => 1,
-    '/openvz/' => 1,
-    '/lxc/' => 1,
-    'ha/crm_commands' => 1,
-    'ha/manager_status' => 1,
-    'ha/resources.cfg' => 1,
-    'ha/groups.cfg' => 1,
-    'ha/fence.cfg' => 1,
-    'status.cfg' => 1,
-    'ceph.conf' => 1,
-    'sdn/vnets.cfg' => 1,
-    'sdn/zones.cfg' => 1,
-    'sdn/controllers.cfg' => 1,
-    'sdn/subnets.cfg' => 1,
-    'sdn/ipams.cfg' => 1,
-    'sdn/dns.cfg' => 1,
-    'sdn/.running-config' => 1,
-    'virtual-guest/cpu-models.conf' => 1,
-};
-
-sub prepare_observed_file_basedirs {
-
-    if (!check_cfs_is_mounted(1)) {
-       warn "pmxcfs isn't mounted (/etc/pve), chickening out..\n";
-       return;
-    }
-
-    for my $f (sort keys %$observed) {
-       next if $f !~ m!^(.*)/[^/]+$!;
-       my $dir = "$basedir/$1";
-       next if -e $dir; # can also be a link, so just use -e xist check
-       print "creating directory '$dir' for observerd files\n";
-       make_path($dir);
-    }
-}
-
-sub base_dir {
-    return $basedir;
-}
-
-sub auth_dir {
-    return $authdir;
-}
-
-sub check_cfs_quorum {
-    my ($noerr) = @_;
-
-    # note: -w filename always return 1 for root, so wee need
-    # to use File::lstat here
-    my $st = File::stat::lstat("$basedir/local");
-    my $quorate = ($st && (($st->mode & 0200) != 0));
-
-    die "cluster not ready - no quorum?\n" if !$quorate && !$noerr;
-
-    return $quorate;
-}
-
-sub check_cfs_is_mounted {
-    my ($noerr) = @_;
-
-    my $res = -l "$basedir/local";
-
-    die "pve configuration filesystem (pmxcfs) not mounted\n" if !$res && !$noerr;
-
-    return $res;
-}
-
-my $versions = {};
-my $vmlist = {};
-my $clinfo = {};
-
-my $ipcc_send_rec = sub {
-    my ($msgid, $data) = @_;
-
-    my $res = PVE::IPCC::ipcc_send_rec($msgid, $data);
-
-    die "ipcc_send_rec[$msgid] failed: $!\n" if !defined($res) && ($! != 0);
-
-    return $res;
-};
-
-my $ipcc_send_rec_json = sub {
-    my ($msgid, $data) = @_;
-
-    my $res = PVE::IPCC::ipcc_send_rec($msgid, $data);
-
-    die "ipcc_send_rec[$msgid] failed: $!\n" if !defined($res) && ($! != 0);
-
-    return decode_json($res);
-};
-
-my $ipcc_get_config = sub {
-    my ($path) = @_;
-
-    my $bindata = pack "Z*", $path;
-    my $res = PVE::IPCC::ipcc_send_rec(CFS_IPC_GET_CONFIG, $bindata);
-    if (!defined($res)) {
-       if ($! != 0) {
-           return undef if $! == ENOENT;
-           die "$!\n";
-       }
-       return '';
-    }
-
-    return $res;
-};
-
-my $ipcc_get_status = sub {
-    my ($name, $nodename) = @_;
-
-    my $bindata = pack "Z[256]Z[256]", $name, ($nodename || "");
-    return PVE::IPCC::ipcc_send_rec(CFS_IPC_GET_STATUS, $bindata);
-};
-
-my $ipcc_remove_status = sub {
-    my ($name) = @_;
-    # we just omit the data payload, pmxcfs takes this as hint and removes this
-    # key from the status hashtable
-    my $bindata = pack "Z[256]", $name;
-    return &$ipcc_send_rec(CFS_IPC_SET_STATUS, $bindata);
-};
-
-my $ipcc_update_status = sub {
-    my ($name, $data) = @_;
-
-    my $raw = ref($data) ? encode_json($data) : $data;
-    # update status
-    my $bindata = pack "Z[256]Z*", $name, $raw;
-
-    return &$ipcc_send_rec(CFS_IPC_SET_STATUS, $bindata);
-};
-
-my $ipcc_log = sub {
-    my ($priority, $ident, $tag, $msg) = @_;
-
-    my $bindata = pack "CCCZ*Z*Z*", $priority, bytes::length($ident) + 1,
-    bytes::length($tag) + 1, $ident, $tag, $msg;
-
-    return &$ipcc_send_rec(CFS_IPC_LOG_CLUSTER_MSG, $bindata);
-};
-
-my $ipcc_get_cluster_log = sub {
-    my ($user, $max) = @_;
-
-    $max = 0 if !defined($max);
-
-    my $bindata = pack "VVVVZ*", $max, 0, 0, 0, ($user || "");
-    return &$ipcc_send_rec(CFS_IPC_GET_CLUSTER_LOG, $bindata);
-};
-
-my $ipcc_verify_token = sub {
-    my ($full_token) = @_;
-
-    my $bindata = pack "Z*", $full_token;
-    my $res = PVE::IPCC::ipcc_send_rec(CFS_IPC_VERIFY_TOKEN, $bindata);
-
-    return 1 if $! == 0;
-    return 0 if $! == ENOENT;
-
-    die "$!\n";
-};
-
-my $ccache = {};
-
-sub cfs_update {
-    my ($fail) = @_;
-    eval {
-       my $res = &$ipcc_send_rec_json(CFS_IPC_GET_FS_VERSION);
-       die "no starttime\n" if !$res->{starttime};
-
-       if (!$res->{starttime} || !$versions->{starttime} ||
-           $res->{starttime} != $versions->{starttime}) {
-           #print "detected changed starttime\n";
-           $vmlist = {};
-           $clinfo = {};
-           $ccache = {};
-       }
-
-       $versions = $res;
-    };
-    my $err = $@;
-    if ($err) {
-       $versions = {};
-       $vmlist = {};
-       $clinfo = {};
-       $ccache = {};
-       die $err if $fail;
-       warn $err;
-    }
-
-    eval {
-       if (!$clinfo->{version} || $clinfo->{version} != $versions->{clinfo}) {
-           #warn "detected new clinfo\n";
-           $clinfo = &$ipcc_send_rec_json(CFS_IPC_GET_CLUSTER_INFO);
-       }
-    };
-    $err = $@;
-    if ($err) {
-       $clinfo = {};
-       die $err if $fail;
-       warn $err;
-    }
-
-    eval {
-       if (!$vmlist->{version} || $vmlist->{version} != $versions->{vmlist}) {
-           #warn "detected new vmlist1\n";
-           $vmlist = &$ipcc_send_rec_json(CFS_IPC_GET_GUEST_LIST);
-       }
-    };
-    $err = $@;
-    if ($err) {
-       $vmlist = {};
-       die $err if $fail;
-       warn $err;
-    }
-}
-
-sub get_vmlist {
-    return $vmlist;
-}
-
-sub get_clinfo {
-    return $clinfo;
-}
-
-sub get_members {
-    return $clinfo->{nodelist};
-}
-
-sub get_nodelist {
-    my $nodelist = $clinfo->{nodelist};
-
-    my $nodename = PVE::INotify::nodename();
-
-    if (!$nodelist || !$nodelist->{$nodename}) {
-       return [ $nodename ];
-    }
-
-    return [ keys %$nodelist ];
-}
-
-# only stored in a in-memory hashtable inside pmxcfs, local data is gone after
-# a restart (of pmxcfs or the node), peer data is still available then
-# best used for status data, like running (ceph) services, package versions, ...
-sub broadcast_node_kv {
-    my ($key, $data) = @_;
-
-    if (!defined($data)) {
-       eval { $ipcc_remove_status->("kv/$key") };
-    } else {
-       die "cannot send a reference\n" if ref($data);
-       my $size = length($data);
-       die "data for '$key' too big\n" if $size >= (32 * 1024); # limit from pmxfs
-
-       eval { $ipcc_update_status->("kv/$key", $data) };
-    }
-    warn $@ if $@;
-}
-
-# nodename is optional
-sub get_node_kv {
-    my ($key, $nodename) = @_;
-
-    my $res = {};
-    my $get_node_data = sub {
-       my ($node) = @_;
-       my $raw = $ipcc_get_status->("kv/$key", $node);
-       $res->{$node} = unpack("Z*", $raw) if $raw;
-    };
-
-    if ($nodename) {
-       $get_node_data->($nodename);
-    } else {
-       for my $node (get_nodelist()->@*) {
-           $get_node_data->($node);
-       }
-    }
-
-    return $res;
-}
-
-# properties: an array-ref of config properties you want to get, e.g., this
-# is perfect to get multiple properties of a guest _fast_
-# (>100 faster than manual parsing here)
-# vmid: optional, if a valid is passed we only check that one, else return all
-# NOTE: does *not* searches snapshot and PENDING entries sections!
-# NOTE: returns the guest config lines (excluding trailing whitespace) as is,
-#       so for non-trivial properties, checking the validity must be done
-# NOTE: no permission check is done, that is the responsibilty of the caller
-sub get_guest_config_properties {
-    my ($properties, $vmid) = @_;
-
-    die "properties required" if !defined($properties);
-
-    my $num_props = scalar(@$properties);
-    die "only up to 255 properties supported" if $num_props > 255;
-    my $bindata = pack "VC", $vmid // 0, $num_props;
-    for my $property (@$properties) {
-       $bindata .= pack "Z*", $property;
-    }
-    my $res = $ipcc_send_rec_json->(CFS_IPC_GET_GUEST_CONFIG_PROPERTIES, $bindata);
-
-    return $res;
-}
-
-# property: a config property you want to get, e.g., this is perfect to get
-# the 'lock' entry of a guest _fast_ (>100 faster than manual parsing here)
-# vmid: optional, if a valid is passed we only check that one, else return all
-# NOTE: does *not* searches snapshot and PENDING entries sections!
-# NOTE: returns the guest config lines (excluding trailing whitespace) as is,
-#       so for non-trivial properties, checking the validity must be done
-# NOTE: no permission check is done, that is the responsibilty of the caller
-sub get_guest_config_property {
-    my ($property, $vmid) = @_;
-
-    die "property is required" if !defined($property);
-
-    my $bindata = pack "VZ*", $vmid // 0, $property;
-    my $res = $ipcc_send_rec_json->(CFS_IPC_GET_GUEST_CONFIG_PROPERTY, $bindata);
-
-    return $res;
-}
-
-# $data must be a chronological descending ordered array of tasks
-sub broadcast_tasklist {
-    my ($data) = @_;
-
-    # the serialized list may not get bigger than 128 KiB (CFS_MAX_STATUS_SIZE from pmxcfs)
-    # drop older items until we satisfy this constraint
-    my $size = length(encode_json($data));
-    while ($size >= (32 * 1024)) { # TODO: update to 128 KiB in PVE 8.x
-       pop @$data;
-       $size = length(encode_json($data));
-    }
-
-    eval { $ipcc_update_status->("tasklist", $data) };
-    warn $@ if $@;
-}
-
-my $tasklistcache = {};
-
-sub get_tasklist {
-    my ($nodename) = @_;
-
-    my $kvstore = $versions->{kvstore} || {};
-
-    my $nodelist = get_nodelist();
-
-    my $res = [];
-    foreach my $node (@$nodelist) {
-       next if $nodename && ($nodename ne $node);
-       eval {
-           my $ver = exists $kvstore->{$node} ? $kvstore->{$node}->{tasklist} : undef;
-           my $cache = $tasklistcache->{$node};
-           if (!$cache || !$ver || !$cache->{version} || ($cache->{version} != $ver)) {
-               my $tasks = [];
-               if (my $raw = $ipcc_get_status->("tasklist", $node)) {
-                   my $json_str = unpack("Z*", $raw);
-                   $tasks = decode_json($json_str);
-               }
-               push @$res, @$tasks;
-               $tasklistcache->{$node} = {
-                   data => $tasks,
-                   version => $ver,
-               };
-           } elsif ($cache && $cache->{data}) {
-               push @$res, $cache->{data}->@*;
-           }
-       };
-       my $err = $@;
-       syslog('err', $err) if $err;
-    }
-
-    return $res;
-}
-
-sub broadcast_rrd {
-    my ($rrdid, $data) = @_;
-
-    eval {
-       &$ipcc_update_status("rrd/$rrdid", $data);
-    };
-    my $err = $@;
-
-    warn $err if $err;
-}
-
-my $last_rrd_dump = 0;
-my $last_rrd_data = "";
-
-sub rrd_dump {
-
-    my $ctime = time();
-
-    my $diff = $ctime - $last_rrd_dump;
-    if ($diff < 2) {
-       return $last_rrd_data;
-    }
-
-    my $raw;
-    eval {
-       $raw = &$ipcc_send_rec(CFS_IPC_GET_RRD_DUMP);
-    };
-    my $err = $@;
-
-    if ($err) {
-       warn $err;
-       return {};
-    }
-
-    my $res = {};
-
-    if ($raw) {
-       while ($raw =~ s/^(.*)\n//) {
-           my ($key, @ela) = split(/:/, $1);
-           next if !$key;
-           next if !(scalar(@ela) > 1);
-           $res->{$key} = [ map { $_ eq 'U' ? undef : $_ } @ela ];
-       }
-    }
-
-    $last_rrd_dump = $ctime;
-    $last_rrd_data = $res;
-
-    return $res;
-}
-
-
-# a fast way to read files (avoid fuse overhead)
-sub get_config {
-    my ($path) = @_;
-
-    return &$ipcc_get_config($path);
-}
-
-sub get_cluster_log {
-    my ($user, $max) = @_;
-
-    return &$ipcc_get_cluster_log($user, $max);
-}
-
-sub verify_token {
-    my ($userid, $token) = @_;
-
-    return &$ipcc_verify_token("$userid $token");
-}
-
-my $file_info = {};
-
-sub cfs_register_file {
-    my ($filename, $parser, $writer) = @_;
-
-    $observed->{$filename} || die "unknown file '$filename'";
-
-    die "file '$filename' already registered" if $file_info->{$filename};
-
-    $file_info->{$filename} = {
-       parser => $parser,
-       writer => $writer,
-    };
-}
-
-my $ccache_read = sub {
-    my ($filename, $parser, $version) = @_;
-
-    $ccache->{$filename} = {} if !$ccache->{$filename};
-
-    my $ci = $ccache->{$filename};
-
-    if (!$ci->{version} || !$version || $ci->{version} != $version) {
-       # we always call the parser, even when the file does not exist
-       # (in that case $data is undef)
-       my $data = get_config($filename);
-       $ci->{data} = &$parser("/etc/pve/$filename", $data);
-       $ci->{version} = $version;
-    }
-
-    my $res = ref($ci->{data}) ? dclone($ci->{data}) : $ci->{data};
-
-    return $res;
-};
-
-sub cfs_file_version {
-    my ($filename) = @_;
-
-    my $version;
-    my $infotag;
-    if ($filename =~ m!^nodes/[^/]+/(openvz|lxc|qemu-server)/(\d+)\.conf$!) {
-       my ($type, $vmid) = ($1, $2);
-       if ($vmlist && $vmlist->{ids} && $vmlist->{ids}->{$vmid}) {
-           $version = $vmlist->{ids}->{$vmid}->{version};
-       }
-       $infotag = "/$type/";
-    } else {
-       $infotag = $filename;
-       $version = $versions->{$filename};
-    }
-
-    my $info = $file_info->{$infotag} ||
-       die "unknown file type '$filename'\n";
-
-    return wantarray ? ($version, $info) : $version;
-}
-
-sub cfs_read_file {
-    my ($filename) = @_;
-
-    my ($version, $info) = cfs_file_version($filename);
-    my $parser = $info->{parser};
-
-    return &$ccache_read($filename, $parser, $version);
-}
-
-sub cfs_write_file {
-    my ($filename, $data) = @_;
-
-    my ($version, $info) = cfs_file_version($filename);
-
-    my $writer = $info->{writer} || die "no writer defined";
-
-    my $fsname = "/etc/pve/$filename";
-
-    my $raw = &$writer($fsname, $data);
-
-    if (my $ci = $ccache->{$filename}) {
-       $ci->{version} = undef;
-    }
-
-    PVE::Tools::file_set_contents($fsname, $raw);
-}
-
-my $cfs_lock = sub {
-    my ($lockid, $timeout, $code, @param) = @_;
-
-    my $prev_alarm = alarm(0); # suspend outer alarm early
-
-    my $res;
-    my $got_lock = 0;
-
-    # this timeout is for acquire the lock
-    $timeout = 10 if !$timeout;
-
-    my $filename = "$lockdir/$lockid";
-
-    my $is_code_err = 0;
-    eval {
-
-       mkdir $lockdir;
-
-       if (! -d $lockdir) {
-           die "pve cluster filesystem not online.\n";
-       }
-
-       my $timeout_err = sub { die "got lock request timeout\n"; };
-       local $SIG{ALRM} = $timeout_err;
-
-       while (1) {
-           alarm ($timeout);
-           $got_lock = mkdir($filename);
-           $timeout = alarm(0) - 1; # we'll sleep for 1s, see down below
-
-           last if $got_lock;
-
-           $timeout_err->() if $timeout <= 0;
-
-           print STDERR "trying to acquire cfs lock '$lockid' ...\n";
-           utime (0, 0, $filename); # cfs unlock request
-           sleep(1);
-       }
-
-       # fixed command timeout: cfs locks have a timeout of 120
-       # using 60 gives us another 60 seconds to abort the task
-       local $SIG{ALRM} = sub { die "'$lockid'-locked command timed out - aborting\n"; };
-       alarm(60);
-
-       cfs_update(); # make sure we read latest versions inside code()
-
-       $is_code_err = 1; # allows to differ between locking and actual-work errors
-
-       $res = &$code(@param);
-
-       alarm(0);
-    };
-
-    my $err = $@;
-
-    $err = "no quorum!\n" if !$got_lock && !check_cfs_quorum(1);
-
-    rmdir $filename if $got_lock; # if we held the lock always unlock again
-
-    alarm($prev_alarm);
-
-    if ($err) {
-       if (ref($err) eq 'PVE::Exception' || $is_code_err) {
-           # re-raise defined exceptions
-           $@ = $err;
-       } else {
-           # add lock info for plain errors comming from the locking itself
-           $@ = "cfs-lock '$lockid' error: $err";
-       }
-        return undef;
-    }
-
-    $@ = undef;
-
-    return $res;
-};
-
-sub cfs_lock_file {
-    my ($filename, $timeout, $code, @param) = @_;
-
-    my $info = $observed->{$filename} || die "unknown file '$filename'";
-
-    my $lockid = "file-$filename";
-    $lockid =~ s/[.\/]/_/g;
-
-    &$cfs_lock($lockid, $timeout, $code, @param);
-}
-
-sub cfs_lock_storage {
-    my ($storeid, $timeout, $code, @param) = @_;
-
-    my $lockid = "storage-$storeid";
-
-    &$cfs_lock($lockid, $timeout, $code, @param);
-}
-
-sub cfs_lock_domain {
-    my ($domainname, $timeout, $code, @param) = @_;
-
-    my $lockid = "domain-$domainname";
-
-    &$cfs_lock($lockid, $timeout, $code, @param);
-}
-
-sub cfs_lock_acme {
-    my ($account, $timeout, $code, @param) = @_;
-
-    my $lockid = "acme-$account";
-
-    &$cfs_lock($lockid, $timeout, $code, @param);
-}
-
-sub cfs_lock_authkey {
-    my ($timeout, $code, @param) = @_;
-
-    $cfs_lock->('authkey', $timeout, $code, @param);
-}
-
-sub cfs_lock_firewall {
-    my ($scope, $timeout, $code, @param) = @_;
-
-    my $lockid = "firewall-$scope";
-
-    $cfs_lock->($lockid, $timeout, $code, @param);
-}
-
-my $log_levels = {
-    "emerg" => 0,
-    "alert" => 1,
-    "crit" => 2,
-    "critical" => 2,
-    "err" => 3,
-    "error" => 3,
-    "warn" => 4,
-    "warning" => 4,
-    "notice" => 5,
-    "info" => 6,
-    "debug" => 7,
-};
-
-sub log_msg {
-   my ($priority, $ident, $msg) = @_;
-
-   if (my $tmp = $log_levels->{$priority}) {
-       $priority = $tmp;
-   }
-
-   die "need numeric log priority" if $priority !~ /^\d+$/;
-
-   my $tag = PVE::SafeSyslog::tag();
-
-   $msg = "empty message" if !$msg;
-
-   $ident = "" if !$ident;
-   $ident = encode("ascii", $ident,
-                  sub { sprintf "\\u%04x", shift });
-
-   my $ascii = encode("ascii", $msg, sub { sprintf "\\u%04x", shift });
-
-   if ($ident) {
-       syslog($priority, "<%s> %s", $ident, $ascii);
-   } else {
-       syslog($priority, "%s", $ascii);
-   }
-
-   eval { &$ipcc_log($priority, $ident, $tag, $ascii); };
-
-   syslog("err", "writing cluster log failed: $@") if $@;
-}
-
-sub check_vmid_unused {
-    my ($vmid, $noerr) = @_;
-
-    my $vmlist = get_vmlist();
-
-    my $d = $vmlist->{ids}->{$vmid};
-    return 1 if !defined($d);
-
-    return undef if $noerr;
-
-    my $vmtypestr =  $d->{type} eq 'qemu' ? 'VM' : 'CT';
-    die "$vmtypestr $vmid already exists on node '$d->{node}'\n";
-}
-
-sub check_node_exists {
-    my ($nodename, $noerr) = @_;
-
-    my $nodelist = $clinfo->{nodelist};
-    return 1 if $nodelist && $nodelist->{$nodename};
-
-    return undef if $noerr;
-
-    die "no such cluster node '$nodename'\n";
-}
-
-# this is also used to get the IP of the local node
-sub remote_node_ip {
-    my ($nodename, $noerr) = @_;
-
-    my $nodelist = $clinfo->{nodelist};
-    if ($nodelist && $nodelist->{$nodename}) {
-       if (my $ip = $nodelist->{$nodename}->{ip}) {
-           return $ip if !wantarray;
-           my $family = $nodelist->{$nodename}->{address_family};
-           if (!$family) {
-               $nodelist->{$nodename}->{address_family} =
-                   $family =
-                   PVE::Tools::get_host_address_family($ip);
-           }
-           return wantarray ? ($ip, $family) : $ip;
-       }
-    }
-
-    # fallback: try to get IP by other means
-    return PVE::Network::get_ip_from_hostname($nodename, $noerr);
-}
-
-sub get_node_fingerprint {
-    my ($node) = @_;
-
-    my $cert_path = "/etc/pve/nodes/$node/pve-ssl.pem";
-    my $custom_cert_path = "/etc/pve/nodes/$node/pveproxy-ssl.pem";
-
-    $cert_path = $custom_cert_path if -f $custom_cert_path;
-
-    return PVE::Certificate::get_certificate_fingerprint($cert_path);
-}
-
-# bash completion helpers
-
-sub complete_next_vmid {
-
-    my $vmlist = get_vmlist() || {};
-    my $idlist = $vmlist->{ids} || {};
-
-    for (my $i = 100; $i < 10000; $i++) {
-       return [$i] if !defined($idlist->{$i});
-    }
-
-    return [];
-}
-
-sub complete_vmid {
-
-    my $vmlist = get_vmlist();
-    my $ids = $vmlist->{ids} || {};
-
-    return [ keys %$ids ];
-}
-
-sub complete_local_vmid {
-
-    my $vmlist = get_vmlist();
-    my $ids = $vmlist->{ids} || {};
-
-    my $nodename = PVE::INotify::nodename();
-
-    my $res = [];
-    foreach my $vmid (keys %$ids) {
-       my $d = $ids->{$vmid};
-       next if !$d->{node} || $d->{node} ne $nodename;
-       push @$res, $vmid;
-    }
-
-    return $res;
-}
-
-sub complete_migration_target {
-
-    my $res = [];
-
-    my $nodename = PVE::INotify::nodename();
-
-    my $nodelist = get_nodelist();
-    foreach my $node (@$nodelist) {
-       next if $node eq $nodename;
-       push @$res, $node;
-    }
-
-    return $res;
-}
-
-
-# NOTE: filesystem must be offline here, no DB changes allowed
-sub cfs_backup_database {
-    mkdir $dbbackupdir;
-
-    my $ctime = time();
-    my $backup_fn = "$dbbackupdir/config-$ctime.sql.gz";
-
-    print "backup old database to '$backup_fn'\n";
-
-    my $cmd = [ ['sqlite3', $dbfile, '.dump'], ['gzip', '-', \ ">${backup_fn}"] ];
-    run_command($cmd, 'errmsg' => "cannot backup old database\n");
-
-    my $maxfiles = 10; # purge older backup
-    my $backups = [ sort { $b cmp $a } <$dbbackupdir/config-*.sql.gz> ];
-
-    if ((my $count = scalar(@$backups)) > $maxfiles) {
-       foreach my $f (@$backups[$maxfiles..$count-1]) {
-           next if $f !~ m/^(\S+)$/; # untaint
-           print "delete old backup '$1'\n";
-           unlink $1;
-       }
-    }
-
-    return $dbfile;
-}
-
-1;
diff --git a/data/PVE/Cluster/IPCConst.pm.awk b/data/PVE/Cluster/IPCConst.pm.awk
deleted file mode 100644 (file)
index 3c622ec..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-BEGIN {
-    print "package PVE::Cluster::IPCConst;"
-    print "use strict; use warnings;"
-    print
-    print "use base 'Exporter';"
-    print
-    print "my %IPC_OPS;"
-    print "BEGIN {"
-    print "  %IPC_OPS = ("
-}
-
-/^#define CFS_IPC/ {
-    print "    " $2, "=>", $3 ","
-}
-
-END {
-    print "  );"
-    print "}"
-    print "use constant \\%IPC_OPS;"
-    print "our @EXPORT = keys(%IPC_OPS);"
-}
diff --git a/data/PVE/Cluster/Makefile b/data/PVE/Cluster/Makefile
deleted file mode 100644 (file)
index 0b25cfc..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-PVEDIR=${DESTDIR}/usr/share/perl5/PVE
-
-SOURCES=IPCConst.pm Setup.pm
-
-.PHONY: install
-install: ${SOURCES}
-       install -d ${PVEDIR}/Cluster
-       for f in ${SOURCES}; do install -m 0664 $$f ${PVEDIR}/Cluster/$$f; done
-
-IPCConst.pm: ../../src/cfs-ipc-ops.h
-       awk -f IPCConst.pm.awk $< > $@.tmp
-       mv $@.tmp $@
-IPCConst.pm: IPCConst.pm.awk
-
-.PHONY: clean
-clean:
-       rm -f IPCConst.pm IPCConst.pm.tmp
diff --git a/data/PVE/Cluster/Setup.pm b/data/PVE/Cluster/Setup.pm
deleted file mode 100644 (file)
index 108817e..0000000
+++ /dev/null
@@ -1,826 +0,0 @@
-package PVE::Cluster::Setup;
-
-use strict;
-use warnings;
-
-use Digest::HMAC_SHA1;
-use Digest::SHA;
-use IO::File;
-use MIME::Base64;
-use Net::IP;
-use UUID;
-use POSIX qw(EEXIST);
-
-use PVE::APIClient::LWP;
-use PVE::Cluster;
-use PVE::Corosync;
-use PVE::INotify;
-use PVE::JSONSchema;
-use PVE::Network;
-use PVE::Tools;
-use PVE::Certificate;
-
-# relevant for joining or getting joined checks, after that versions in cluster can differ
-use constant JOIN_API_VERSION => 1;
-# (APIVER-this) is oldest version a new node in addnode can have and still be accepted
-use constant JOIN_API_AGE_AS_CLUSTER => 1;
-# (APIVER-this) is oldest version a cluster node can have to still try joining
-use constant JOIN_API_AGE_AS_JOINEE => 1;
-
-sub assert_we_can_join_cluster_version {
-    my ($version) = @_;
-    my $min_version = JOIN_API_VERSION - JOIN_API_AGE_AS_JOINEE;
-    return if $version >= $min_version;
-    die "error: incompatible join API version on cluster ($version), local node"
-       ." has ". JOIN_API_VERSION ." and supports >= $min_version. Make sure"
-       ."all cluster nodes are up-to-date.\n";
-}
-
-sub assert_node_can_join_our_version {
-    my ($version) = @_;
-    my $min_version = JOIN_API_VERSION - JOIN_API_AGE_AS_CLUSTER;
-    return if $version >= $min_version;
-    die "error: unsupported old API version on joining node ($version), cluster"
-       ." node has ". JOIN_API_VERSION ." and supports >= $min_version. Please"
-       ." upgrade node before joining\n";
-}
-
-my $pmxcfs_base_dir = PVE::Cluster::base_dir();
-my $pmxcfs_auth_dir = PVE::Cluster::auth_dir();
-
-# only write output if something fails
-sub run_silent_cmd {
-    my ($cmd) = @_;
-
-    my $outbuf = '';
-    my $record = sub { $outbuf .= shift . "\n"; };
-
-    eval { PVE::Tools::run_command($cmd, outfunc => $record, errfunc => $record) };
-
-    if (my $err = $@) {
-       print STDERR $outbuf;
-       die $err;
-    }
-}
-
-# Corosync related files
-my $localclusterdir = "/etc/corosync";
-my $localclusterconf = "$localclusterdir/corosync.conf";
-my $authfile = "$localclusterdir/authkey";
-my $clusterconf = "$pmxcfs_base_dir/corosync.conf";
-
-# CA/certificate related files
-my $pveca_key_fn = "$pmxcfs_auth_dir/pve-root-ca.key";
-my $pveca_srl_fn = "$pmxcfs_auth_dir/pve-root-ca.srl";
-my $pveca_cert_fn = "$pmxcfs_base_dir/pve-root-ca.pem";
-# this is just a secret accessable by the web browser
-# and is used for CSRF prevention
-my $pvewww_key_fn = "$pmxcfs_base_dir/pve-www.key";
-
-# ssh related files
-my $ssh_rsa_id_priv = "/root/.ssh/id_rsa";
-my $ssh_rsa_id = "/root/.ssh/id_rsa.pub";
-my $ssh_host_rsa_id = "/etc/ssh/ssh_host_rsa_key.pub";
-my $sshglobalknownhosts = "/etc/ssh/ssh_known_hosts";
-my $sshknownhosts = "$pmxcfs_auth_dir/known_hosts";
-my $sshauthkeys = "$pmxcfs_auth_dir/authorized_keys";
-my $sshd_config_fn = "/etc/ssh/sshd_config";
-my $rootsshauthkeys = "/root/.ssh/authorized_keys";
-my $rootsshauthkeysbackup = "${rootsshauthkeys}.org";
-my $rootsshconfig = "/root/.ssh/config";
-
-# ssh related utility functions
-
-sub ssh_merge_keys {
-    # remove duplicate keys in $sshauthkeys
-    # ssh-copy-id simply add keys, so the file can grow to large
-
-    my $data = '';
-    if (-f $sshauthkeys) {
-       $data = PVE::Tools::file_get_contents($sshauthkeys, 128*1024);
-       chomp($data);
-    }
-
-    my $found_backup;
-    if (-f $rootsshauthkeysbackup) {
-       $data .= "\n";
-       $data .= PVE::Tools::file_get_contents($rootsshauthkeysbackup, 128*1024);
-       chomp($data);
-       $found_backup = 1;
-    }
-
-    # always add ourself
-    if (-f $ssh_rsa_id) {
-       my $pub = PVE::Tools::file_get_contents($ssh_rsa_id);
-       chomp($pub);
-       $data .= "\n$pub\n";
-    }
-
-    my $newdata = "";
-    my $vhash = {};
-    my @lines = split(/\n/, $data);
-    foreach my $line (@lines) {
-       if ($line !~ /^#/ && $line =~ m/(^|\s)ssh-(rsa|dsa)\s+(\S+)\s+\S+$/) {
-            next if $vhash->{$3}++;
-       }
-       $newdata .= "$line\n";
-    }
-
-    PVE::Tools::file_set_contents($sshauthkeys, $newdata, 0600);
-
-    if ($found_backup && -l $rootsshauthkeys) {
-       # everything went well, so we can remove the backup
-       unlink $rootsshauthkeysbackup;
-    }
-}
-
-sub setup_sshd_config {
-    my () = @_;
-
-    my $conf = PVE::Tools::file_get_contents($sshd_config_fn);
-
-    return if $conf =~ m/^PermitRootLogin\s+yes\s*$/m;
-
-    if ($conf !~ s/^#?PermitRootLogin.*$/PermitRootLogin yes/m) {
-       chomp $conf;
-       $conf .= "\nPermitRootLogin yes\n";
-    }
-
-    PVE::Tools::file_set_contents($sshd_config_fn, $conf);
-
-    PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'sshd']);
-}
-
-sub setup_rootsshconfig {
-
-    # create ssh key if it does not exist
-    if (! -f $ssh_rsa_id) {
-       mkdir '/root/.ssh/';
-       system ("echo|ssh-keygen -t rsa -N '' -b 2048 -f ${ssh_rsa_id_priv}");
-    }
-
-    # create ssh config if it does not exist
-    if (! -f $rootsshconfig) {
-        mkdir '/root/.ssh';
-        if (my $fh = IO::File->new($rootsshconfig, O_CREAT|O_WRONLY|O_EXCL, 0640)) {
-            # this is the default ciphers list from Debian's OpenSSH package (OpenSSH_7.4p1 Debian-10, OpenSSL 1.0.2k  26 Jan 2017)
-           # changed order to put AES before Chacha20 (most hardware has AESNI)
-            print $fh "Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm\@openssh.com,aes256-gcm\@openssh.com,chacha20-poly1305\@openssh.com\n";
-            close($fh);
-        }
-    }
-}
-
-sub setup_ssh_keys {
-
-    mkdir $pmxcfs_auth_dir;
-
-    my $import_ok;
-
-    if (! -f $sshauthkeys) {
-       my $old;
-       if (-f $rootsshauthkeys) {
-           $old = PVE::Tools::file_get_contents($rootsshauthkeys, 128*1024);
-       }
-       if (my $fh = IO::File->new ($sshauthkeys, O_CREAT|O_WRONLY|O_EXCL, 0400)) {
-           PVE::Tools::safe_print($sshauthkeys, $fh, $old) if $old;
-           close($fh);
-           $import_ok = 1;
-       }
-    }
-
-    warn "can't create shared ssh key database '$sshauthkeys'\n"
-       if ! -f $sshauthkeys;
-
-    if (-f $rootsshauthkeys && ! -l $rootsshauthkeys) {
-       if (!rename($rootsshauthkeys , $rootsshauthkeysbackup)) {
-           warn "rename $rootsshauthkeys failed - $!\n";
-       }
-    }
-
-    if (! -l $rootsshauthkeys) {
-       symlink $sshauthkeys, $rootsshauthkeys;
-    }
-
-    if (! -l $rootsshauthkeys) {
-       warn "can't create symlink for ssh keys '$rootsshauthkeys' -> '$sshauthkeys'\n";
-    } else {
-       unlink $rootsshauthkeysbackup if $import_ok;
-    }
-}
-
-sub ssh_unmerge_known_hosts {
-    return if ! -l $sshglobalknownhosts;
-
-    my $old = '';
-    $old = PVE::Tools::file_get_contents($sshknownhosts, 128*1024)
-       if -f $sshknownhosts;
-
-    PVE::Tools::file_set_contents($sshglobalknownhosts, $old);
-}
-
-sub ssh_merge_known_hosts {
-    my ($nodename, $ip_address, $createLink) = @_;
-
-    die "no node name specified" if !$nodename;
-    die "no ip address specified" if !$ip_address;
-
-    # ssh lowercases hostnames (aliases) before comparision, so we need too
-    $nodename = lc($nodename);
-    $ip_address = lc($ip_address);
-
-    mkdir $pmxcfs_auth_dir;
-
-    if (! -f $sshknownhosts) {
-       if (my $fh = IO::File->new($sshknownhosts, O_CREAT|O_WRONLY|O_EXCL, 0600)) {
-           close($fh);
-       }
-    }
-
-    my $old = PVE::Tools::file_get_contents($sshknownhosts, 128*1024);
-
-    my $new = '';
-
-    if ((! -l $sshglobalknownhosts) && (-f $sshglobalknownhosts)) {
-       $new = PVE::Tools::file_get_contents($sshglobalknownhosts, 128*1024);
-    }
-
-    my $hostkey = PVE::Tools::file_get_contents($ssh_host_rsa_id);
-    # Note: file sometimes containe emty lines at start, so we use multiline match
-    die "can't parse $ssh_host_rsa_id" if $hostkey !~ m/^(ssh-rsa\s\S+)(\s.*)?$/m;
-    $hostkey = $1;
-
-    my $data = '';
-    my $vhash = {};
-
-    my $found_nodename;
-    my $found_local_ip;
-
-    my $merge_line = sub {
-       my ($line, $all) = @_;
-
-       return if $line =~ m/^\s*$/; # skip empty lines
-       return if $line =~ m/^#/; # skip comments
-
-       if ($line =~ m/^(\S+)\s(ssh-rsa\s\S+)(\s.*)?$/) {
-           my $key = $1;
-           my $rsakey = $2;
-           if (!$vhash->{$key}) {
-               $vhash->{$key} = 1;
-               if ($key =~ m/\|1\|([^\|\s]+)\|([^\|\s]+)$/) {
-                   my $salt = decode_base64($1);
-                   my $digest = $2;
-                   my $hmac = Digest::HMAC_SHA1->new($salt);
-                   $hmac->add($nodename);
-                   my $hd = $hmac->b64digest . '=';
-                   if ($digest eq $hd) {
-                       if ($rsakey eq $hostkey) {
-                           $found_nodename = 1;
-                           $data .= $line;
-                       }
-                       return;
-                   }
-                   $hmac = Digest::HMAC_SHA1->new($salt);
-                   $hmac->add($ip_address);
-                   $hd = $hmac->b64digest . '=';
-                   if ($digest eq $hd) {
-                       if ($rsakey eq $hostkey) {
-                           $found_local_ip = 1;
-                           $data .= $line;
-                       }
-                       return;
-                   }
-               } else {
-                   $key = lc($key); # avoid duplicate entries, ssh compares lowercased
-                   if ($key eq $ip_address) {
-                       $found_local_ip = 1 if $rsakey eq $hostkey;
-                   } elsif ($key eq $nodename) {
-                       $found_nodename = 1 if $rsakey eq $hostkey;
-                   }
-               }
-               $data .= $line;
-           }
-       } elsif ($all) {
-           $data .= $line;
-       }
-    };
-
-    while ($old && $old =~ s/^((.*?)(\n|$))//) {
-       my $line = "$2\n";
-       &$merge_line($line, 1);
-    }
-
-    while ($new && $new =~ s/^((.*?)(\n|$))//) {
-       my $line = "$2\n";
-       &$merge_line($line);
-    }
-
-    # add our own key if not already there
-    $data .= "$nodename $hostkey\n" if !$found_nodename;
-    $data .= "$ip_address $hostkey\n" if !$found_local_ip;
-
-    PVE::Tools::file_set_contents($sshknownhosts, $data);
-
-    return if !$createLink;
-
-    unlink $sshglobalknownhosts;
-    symlink $sshknownhosts, $sshglobalknownhosts;
-
-    warn "can't create symlink for ssh known hosts '$sshglobalknownhosts' -> '$sshknownhosts'\n"
-       if ! -l $sshglobalknownhosts;
-
-}
-
-# directory and file creation
-
-sub gen_local_dirs {
-    my ($nodename) = @_;
-
-    PVE::Cluster::check_cfs_is_mounted();
-
-    my @required_dirs = (
-       "$pmxcfs_base_dir/priv",
-       "$pmxcfs_base_dir/nodes",
-       "$pmxcfs_base_dir/nodes/$nodename",
-       "$pmxcfs_base_dir/nodes/$nodename/lxc",
-       "$pmxcfs_base_dir/nodes/$nodename/qemu-server",
-       "$pmxcfs_base_dir/nodes/$nodename/openvz",
-       "$pmxcfs_base_dir/nodes/$nodename/priv");
-
-    foreach my $dir (@required_dirs) {
-       if (! -d $dir) {
-           mkdir($dir) || $! == EEXIST || die "unable to create directory '$dir' - $!\n";
-       }
-    }
-}
-
-sub gen_auth_key {
-    my $authprivkeyfn = "$pmxcfs_auth_dir/authkey.key";
-    my $authpubkeyfn = "$pmxcfs_base_dir/authkey.pub";
-
-    return if -f "$authprivkeyfn";
-
-    PVE::Cluster::check_cfs_is_mounted();
-
-    PVE::Cluster::cfs_lock_authkey(undef, sub {
-       mkdir $pmxcfs_auth_dir || $! == EEXIST || die "unable to create dir '$pmxcfs_auth_dir' - $!\n";
-
-       run_silent_cmd(['openssl', 'genrsa', '-out', $authprivkeyfn, '2048']);
-
-       run_silent_cmd(['openssl', 'rsa', '-in', $authprivkeyfn, '-pubout', '-out', $authpubkeyfn]);
-    });
-
-    die "$@\n" if $@;
-}
-
-sub gen_pveca_key {
-
-    return if -f $pveca_key_fn;
-
-    eval {
-       run_silent_cmd(['openssl', 'genrsa', '-out', $pveca_key_fn, '4096']);
-    };
-
-    die "unable to generate pve ca key:\n$@" if $@;
-}
-
-sub gen_pveca_cert {
-
-    if (-f $pveca_key_fn && -f $pveca_cert_fn) {
-       return 0;
-    }
-
-    gen_pveca_key();
-
-    # we try to generate an unique 'subject' to avoid browser problems
-    # (reused serial numbers, ..)
-    my $uuid;
-    UUID::generate($uuid);
-    my $uuid_str;
-    UUID::unparse($uuid, $uuid_str);
-
-    eval {
-       # wrap openssl with faketime to prevent bug #904
-       run_silent_cmd(['faketime', 'yesterday', 'openssl', 'req', '-batch',
-                       '-days', '3650', '-new', '-x509', '-nodes', '-key',
-                       $pveca_key_fn, '-out', $pveca_cert_fn, '-subj',
-                       "/CN=Proxmox Virtual Environment/OU=$uuid_str/O=PVE Cluster Manager CA/"]);
-    };
-
-    die "generating pve root certificate failed:\n$@" if $@;
-
-    return 1;
-}
-
-sub gen_pve_ssl_key {
-    my ($nodename) = @_;
-
-    die "no node name specified" if !$nodename;
-
-    my $pvessl_key_fn = "$pmxcfs_base_dir/nodes/$nodename/pve-ssl.key";
-
-    return if -f $pvessl_key_fn;
-
-    eval {
-       run_silent_cmd(['openssl', 'genrsa', '-out', $pvessl_key_fn, '2048']);
-    };
-
-    die "unable to generate pve ssl key for node '$nodename':\n$@" if $@;
-}
-
-sub gen_pve_www_key {
-
-    return if -f $pvewww_key_fn;
-
-    eval {
-       run_silent_cmd(['openssl', 'genrsa', '-out', $pvewww_key_fn, '2048']);
-    };
-
-    die "unable to generate pve www key:\n$@" if $@;
-}
-
-sub update_serial {
-    my ($serial) = @_;
-
-    PVE::Tools::file_set_contents($pveca_srl_fn, $serial);
-}
-
-sub gen_pve_ssl_cert {
-    my ($force, $nodename, $ip) = @_;
-
-    die "no node name specified" if !$nodename;
-    die "no IP specified" if !$ip;
-
-    my $pvessl_cert_fn = "$pmxcfs_base_dir/nodes/$nodename/pve-ssl.pem";
-
-    return if !$force && -f $pvessl_cert_fn;
-
-    my $names = "IP:127.0.0.1,IP:::1,DNS:localhost";
-
-    my $rc = PVE::INotify::read_file('resolvconf');
-
-    $names .= ",IP:$ip";
-
-    $names .= ",DNS:$nodename";
-
-    my $fqdn = $nodename;
-    if ($rc && $rc->{search}) {
-       $fqdn .= ".$rc->{search}";
-       $names .= ",DNS:$fqdn";
-    }
-
-    my $sslconf = <<__EOD;
-RANDFILE = /root/.rnd
-extensions = v3_req
-
-[ req ]
-default_bits = 2048
-distinguished_name = req_distinguished_name
-req_extensions = v3_req
-prompt = no
-string_mask = nombstr
-
-[ req_distinguished_name ]
-organizationalUnitName = PVE Cluster Node
-organizationName = Proxmox Virtual Environment
-commonName = $fqdn
-
-[ v3_req ]
-basicConstraints = CA:FALSE
-extendedKeyUsage = serverAuth
-subjectAltName = $names
-__EOD
-
-    my $cfgfn = "/tmp/pvesslconf-$$.tmp";
-    my $fh = IO::File->new ($cfgfn, "w");
-    print $fh $sslconf;
-    close ($fh);
-
-    my $reqfn = "/tmp/pvecertreq-$$.tmp";
-    unlink $reqfn;
-
-    my $pvessl_key_fn = "$pmxcfs_base_dir/nodes/$nodename/pve-ssl.key";
-    eval {
-       run_silent_cmd([
-           'openssl', 'req', '-batch', '-new', '-config', $cfgfn, '-key', $pvessl_key_fn, '-out', $reqfn
-       ]);
-    };
-
-    if (my $err = $@) {
-       unlink $reqfn;
-       unlink $cfgfn;
-       die "unable to generate pve certificate request:\n$err";
-    }
-
-    update_serial("0000000000000000") if ! -f $pveca_srl_fn;
-
-    # get ca expiry
-    my $cainfo = PVE::Certificate::get_certificate_info($pveca_cert_fn);
-    my $daysleft = int(($cainfo->{notafter} - time())/(24*60*60));
-
-    if ($daysleft < 14) {
-       die "CA expires in less than 2 weeks, unable to generate certificate.\n";
-    }
-
-    # let the certificate expire a little sooner that the ca, so subtract 2 days
-    $daysleft -= 2;
-
-    # we want the certificates to only last 2 years, since some browsers
-    # do not accept certificates with very long expiry time
-    if ($daysleft >= 2*365) {
-       $daysleft = 2*365;
-    }
-
-    eval {
-       run_silent_cmd([
-           'faketime', 'yesterday', # NOTE: wrap openssl with faketime to prevent bug #904
-           'openssl', 'x509', '-req', '-in', $reqfn, '-days', $daysleft, '-out', $pvessl_cert_fn,
-           '-CAkey', $pveca_key_fn, '-CA', $pveca_cert_fn, '-CAserial', $pveca_srl_fn, '-extfile', $cfgfn
-       ]);
-    };
-
-    if (my $err = $@) {
-       unlink $reqfn;
-       unlink $cfgfn;
-       die "unable to generate pve ssl certificate:\n$err";
-    }
-
-    unlink $cfgfn;
-    unlink $reqfn;
-}
-
-sub gen_pve_node_files {
-    my ($nodename, $ip, $opt_force) = @_;
-
-    gen_local_dirs($nodename);
-
-    gen_auth_key();
-
-    # make sure we have a (cluster wide) secret
-    # for CSRFR prevention
-    gen_pve_www_key();
-
-    # make sure we have a (per node) private key
-    gen_pve_ssl_key($nodename);
-
-    # make sure we have a CA
-    my $force = gen_pveca_cert();
-
-    $force = 1 if $opt_force;
-
-    gen_pve_ssl_cert($force, $nodename, $ip);
-}
-
-my $vzdump_cron_dummy = <<__EOD;
-# cluster wide vzdump cron schedule
-# Atomatically generated file - do not edit
-
-PATH="/usr/sbin:/usr/bin:/sbin:/bin"
-
-__EOD
-
-sub gen_pve_vzdump_symlink {
-
-    my $filename = "/etc/pve/vzdump.cron";
-
-    my $link_fn = "/etc/cron.d/vzdump";
-
-    if ((-f $filename) && (! -l $link_fn)) {
-       rename($link_fn, "/root/etc_cron_vzdump.org"); # make backup if file exists
-       symlink($filename, $link_fn);
-    }
-}
-
-sub gen_pve_vzdump_files {
-
-    my $filename = "/etc/pve/vzdump.cron";
-
-    PVE::Tools::file_set_contents($filename, $vzdump_cron_dummy)
-       if ! -f $filename;
-
-    gen_pve_vzdump_symlink();
-};
-
-# join helpers
-
-sub assert_joinable {
-    my ($local_addr, $links, $force) = @_;
-
-    my $errors = '';
-    my $error = sub { $errors .= "* $_[0]\n"; };
-
-    if (-f $authfile) {
-       $error->("authentication key '$authfile' already exists");
-    }
-
-    if (-f $clusterconf)  {
-       $error->("cluster config '$clusterconf' already exists");
-    }
-
-    my $vmlist = PVE::Cluster::get_vmlist();
-    if ($vmlist && $vmlist->{ids} && scalar(keys %{$vmlist->{ids}})) {
-       $error->("this host already contains virtual guests");
-    }
-
-    if (PVE::Tools::run_command(['corosync-quorumtool', '-l'], noerr => 1, quiet => 1) == 0) {
-       $error->("corosync is already running, is this node already in a cluster?!");
-    }
-
-    # check if corosync ring IPs are configured on the current nodes interfaces
-    my $check_ip = sub {
-       my $ip = shift // return;
-       my $logid = shift;
-       if (!PVE::JSONSchema::pve_verify_ip($ip, 1)) {
-           my $host = $ip;
-           eval { $ip = PVE::Network::get_ip_from_hostname($host); };
-           if ($@) {
-               $error->("$logid: cannot use '$host': $@\n") ;
-               return;
-           }
-       }
-
-       my $cidr = (Net::IP::ip_is_ipv6($ip)) ? "$ip/128" : "$ip/32";
-       my $configured_ips = PVE::Network::get_local_ip_from_cidr($cidr);
-
-       $error->("$logid: cannot use IP '$ip', not found on local node!\n")
-           if scalar(@$configured_ips) < 1;
-    };
-
-    $check_ip->($local_addr, 'local node address');
-
-    foreach my $link (keys %$links) {
-       $check_ip->($links->{$link}->{address}, "link$link");
-    }
-
-    if ($errors) {
-       warn "detected the following error(s):\n$errors";
-       die "Check if node may join a cluster failed!\n" if !$force;
-       warn "\nWARNING : detected error but forced to continue!\n\n";
-    }
-}
-
-sub join {
-    my ($param) = @_;
-
-    my $nodename = PVE::INotify::nodename();
-    my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
-
-    my $links = PVE::Corosync::extract_corosync_link_args($param);
-
-    # check if we can join with the given parameters and current node state
-    assert_joinable($local_ip_address, $links, $param->{force});
-
-    setup_sshd_config();
-    setup_rootsshconfig();
-    setup_ssh_keys();
-
-    # make sure known_hosts is on local filesystem
-    ssh_unmerge_known_hosts();
-
-    my $host = $param->{hostname};
-    my $conn_args = {
-       username => 'root@pam',
-       password => $param->{password},
-       cookie_name => 'PVEAuthCookie',
-       protocol => 'https',
-       host => $host,
-       port => 8006,
-    };
-
-    if (my $fp = $param->{fingerprint}) {
-       $conn_args->{cached_fingerprints} = { uc($fp) => 1 };
-    } else {
-       # API schema ensures that we can only get here from CLI handler
-       $conn_args->{manual_verification} = 1;
-    }
-
-    print "Establishing API connection with host '$host'\n";
-
-    my $conn = PVE::APIClient::LWP->new(%$conn_args);
-    $conn->login();
-
-    # login raises an exception on failure, so if we get here we're good
-    print "Login succeeded.\n";
-
-    print "check cluster join API version\n";
-    my $apiver = eval { $conn->get("/cluster/config/apiversion") } // 0;
-    assert_we_can_join_cluster_version($apiver);
-
-    my $args = {};
-    $args->{force} = $param->{force} if defined($param->{force});
-    $args->{nodeid} = $param->{nodeid} if $param->{nodeid};
-    $args->{votes} = $param->{votes} if defined($param->{votes});
-    foreach my $link (keys %$links) {
-       $args->{"link$link"} = PVE::Corosync::print_corosync_link($links->{$link});
-    }
-
-    # this will be used as fallback if no links are specified
-    if (!%$links) {
-       $args->{link0} = $local_ip_address if $apiver == 0;
-       $args->{new_node_ip} = $local_ip_address if $apiver >= 1;
-
-       print "No cluster network links passed explicitly, fallback to local node"
-           . " IP '$local_ip_address'\n";
-    }
-
-    if ($apiver >= 1) {
-       $args->{apiversion} = JOIN_API_VERSION;
-    }
-
-    print "Request addition of this node\n";
-    my $res = eval { $conn->post("/cluster/config/nodes/$nodename", $args); };
-    if (my $err = $@) {
-       if (ref($err) && $err->isa('PVE::APIClient::Exception')) {
-           # we received additional info about the error, show the user
-           chomp $err->{msg};
-           warn "An error occurred on the cluster node: $err->{msg}\n";
-           foreach my $key (sort keys %{$err->{errors}}) {
-               my $symbol = ($key =~ m/^warning/) ? '*' : '!';
-               warn "$symbol $err->{errors}->{$key}\n";
-           }
-
-           die "Cluster join aborted!\n";
-       }
-
-       die $@;
-    }
-
-    if (defined($res->{warnings})) {
-       foreach my $warn (@{$res->{warnings}}) {
-           warn "cluster: $warn\n";
-       }
-    }
-
-    print "Join request OK, finishing setup locally\n";
-
-    # added successfuly - now prepare local node
-    finish_join($nodename, $res->{corosync_conf}, $res->{corosync_authkey});
-}
-
-sub finish_join {
-    my ($nodename, $corosync_conf, $corosync_authkey) = @_;
-
-    mkdir "$localclusterdir";
-    PVE::Tools::file_set_contents($authfile, $corosync_authkey);
-    PVE::Tools::file_set_contents($localclusterconf, $corosync_conf);
-
-    print "stopping pve-cluster service\n";
-    my $cmd = ['systemctl', 'stop', 'pve-cluster'];
-    PVE::Tools::run_command($cmd, errmsg => "can't stop pve-cluster service");
-
-    my $dbfile = PVE::Cluster::cfs_backup_database();
-    unlink $dbfile;
-
-    $cmd = ['systemctl', 'start', 'corosync', 'pve-cluster'];
-    PVE::Tools::run_command($cmd, errmsg => "starting pve-cluster failed");
-
-    # wait for quorum
-    my $printqmsg = 1;
-    while (!PVE::Cluster::check_cfs_quorum(1)) {
-       if ($printqmsg) {
-           print "waiting for quorum...";
-           STDOUT->flush();
-           $printqmsg = 0;
-       }
-       sleep(1);
-    }
-    print "OK\n" if !$printqmsg;
-
-    updatecerts_and_ssh(1);
-
-    print "generated new node certificate, restart pveproxy and pvedaemon services\n";
-    PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pvedaemon', 'pveproxy']);
-
-    print "successfully added node '$nodename' to cluster.\n";
-}
-
-sub updatecerts_and_ssh {
-    my ($force_new_cert, $silent) = @_;
-
-    my $p = sub { print "$_[0]\n" if !$silent };
-
-    setup_rootsshconfig();
-
-    gen_pve_vzdump_symlink();
-
-    if (!PVE::Cluster::check_cfs_quorum(1)) {
-       return undef if $silent;
-       die "no quorum - unable to update files\n";
-    }
-
-    setup_ssh_keys();
-
-    my $nodename = PVE::INotify::nodename();
-    my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
-
-    $p->("(re)generate node files");
-    $p->("generate new node certificate") if $force_new_cert;
-    gen_pve_node_files($nodename, $local_ip_address, $force_new_cert);
-
-    $p->("merge authorized SSH keys and known hosts");
-    ssh_merge_keys();
-    ssh_merge_known_hosts($nodename, $local_ip_address, 1);
-    gen_pve_vzdump_files();
-}
-
-1;
diff --git a/data/PVE/Corosync.pm b/data/PVE/Corosync.pm
deleted file mode 100644 (file)
index 2eb38b8..0000000
+++ /dev/null
@@ -1,528 +0,0 @@
-package PVE::Corosync;
-
-use strict;
-use warnings;
-
-use Clone 'clone';
-use Digest::SHA;
-use Net::IP qw(ip_is_ipv6);
-use Scalar::Util qw(weaken);
-use Socket qw(AF_INET AF_INET6 inet_ntop);
-
-use PVE::Cluster;
-use PVE::JSONSchema;
-use PVE::Tools;
-use PVE::Tools qw($IPV4RE $IPV6RE);
-
-my $basedir = "/etc/pve";
-our $link_addr_re = qw/^(ring|link)(\d+)_addr$/;
-
-my $conf_array_sections = {
-    node => 1,
-    interface => 1,
-};
-
-my $corosync_link_format = {
-    address => {
-       default_key => 1,
-       type => 'string', format => 'address',
-       format_description => 'IP',
-       description => "Hostname (or IP) of this corosync link address.",
-    },
-    priority => {
-       optional => 1,
-       type => 'integer',
-       minimum => 0,
-       maximum => 255,
-       default => 0,
-       description => "The priority for the link when knet is used in 'passive'"
-                    . " mode (default). Lower value means higher priority. Only"
-                    . " valid for cluster create, ignored on node add.",
-    },
-};
-my $corosync_link_desc = {
-    type => 'string', format => $corosync_link_format,
-    description => "Address and priority information of a single corosync link."
-                . " (up to 8 links supported; link0..link7)",
-    optional => 1,
-};
-PVE::JSONSchema::register_standard_option("corosync-link", $corosync_link_desc);
-
-sub parse_corosync_link {
-    my ($value) = @_;
-
-    return undef if !defined($value);
-
-    return PVE::JSONSchema::parse_property_string($corosync_link_format, $value);
-}
-
-sub print_corosync_link {
-    my ($link) = @_;
-
-    return undef if !defined($link);
-
-    return PVE::JSONSchema::print_property_string($link, $corosync_link_format);
-}
-
-use constant MAX_LINK_INDEX => 7;
-
-sub add_corosync_link_properties {
-    my ($prop) = @_;
-
-    for my $lnum (0..MAX_LINK_INDEX) {
-       $prop->{"link$lnum"} = PVE::JSONSchema::get_standard_option("corosync-link");
-    }
-
-    return $prop;
-}
-
-sub extract_corosync_link_args {
-    my ($args) = @_;
-
-    my $links = {};
-    for my $lnum (0..MAX_LINK_INDEX) {
-       $links->{$lnum} = parse_corosync_link($args->{"link$lnum"})
-           if $args->{"link$lnum"};
-    }
-
-    return $links;
-}
-
-# a very simply parser ...
-sub parse_conf {
-    my ($filename, $raw) = @_;
-
-    return {} if !$raw;
-
-    my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : '');
-
-    $raw =~ s/#.*$//mg;
-    $raw =~ s/\r?\n/ /g;
-    $raw =~ s/\s+/ /g;
-    $raw =~ s/^\s+//;
-    $raw =~ s/\s*$//;
-
-    my @tokens = split(/\s/, $raw);
-
-    my $conf = { 'main' => {} };
-
-    my $stack = [];
-    my $section = $conf->{main};
-
-    while (defined(my $token = shift @tokens)) {
-       my $nexttok = $tokens[0];
-
-       if ($nexttok && ($nexttok eq '{')) {
-           shift @tokens; # skip '{'
-           my $new_section = {};
-           if ($conf_array_sections->{$token}) {
-               $section->{$token} = [] if !defined($section->{$token});
-               push @{$section->{$token}}, $new_section;
-           } elsif (!defined($section->{$token})) {
-               $section->{$token} = $new_section;
-           } else {
-               die "section '$token' already exists and not marked as array!\n";
-           }
-           push @$stack, $section;
-           $section = $new_section;
-           next;
-       }
-
-       if ($token eq '}') {
-           $section = pop @$stack;
-           die "parse error - uncexpected '}'\n" if !$section;
-           next;
-       }
-
-       my $key = $token;
-       die "missing ':' after key '$key'\n" if ! ($key =~ s/:$//);
-
-       die "parse error - no value for '$key'\n" if !defined($nexttok);
-       my $value = shift @tokens;
-
-       $section->{$key} = $value;
-    }
-
-    # make working with the config way easier
-    my ($totem, $nodelist) = $conf->{main}->@{"totem", "nodelist"};
-
-    $nodelist->{node} = {
-       map {
-           $_->{name} // $_->{ring0_addr} => $_
-       } @{$nodelist->{node}}
-    };
-    $totem->{interface} = {
-       map {
-           $_->{linknumber} // $_->{ringnumber} => $_
-       } @{$totem->{interface}}
-    };
-
-    $conf->{digest} = $digest;
-
-    return $conf;
-}
-
-sub write_conf {
-    my ($filename, $conf) = @_;
-
-    my $c = clone($conf->{main}) // die "no main section";
-
-    # retransform back for easier dumping
-    my $hash_to_array = sub {
-       my ($hash) = @_;
-       return [ $hash->@{sort keys %$hash} ];
-    };
-
-    $c->{nodelist}->{node} = &$hash_to_array($c->{nodelist}->{node});
-    $c->{totem}->{interface} = &$hash_to_array($c->{totem}->{interface});
-
-    my $dump_section_weak;
-    $dump_section_weak = sub {
-       my ($section, $prefix) = @_;
-
-       my $raw = '';
-
-       foreach my $k (sort keys %$section) {
-           my $v = $section->{$k};
-           if (ref($v) eq 'HASH') {
-               $raw .= $prefix . "$k {\n";
-               $raw .= $dump_section_weak->($v, "$prefix  ");
-               $raw .=  $prefix . "}\n";
-               $raw .= "\n" if !$prefix; # add extra newline at 1st level only
-           } elsif (ref($v) eq 'ARRAY') {
-               foreach my $child (@$v) {
-                   $raw .= $prefix . "$k {\n";
-                   $raw .= $dump_section_weak->($child, "$prefix  ");
-                   $raw .=  $prefix . "}\n";
-               }
-           } elsif (!ref($v)) {
-               die "got undefined value for key '$k'!\n" if !defined($v);
-               $raw .= $prefix . "$k: $v\n";
-           } else {
-               die "unexpected reference in config hash: $k => ". ref($v) ."\n";
-           }
-       }
-
-       return $raw;
-    };
-    my $dump_section = $dump_section_weak;
-    weaken($dump_section_weak);
-
-    my $raw = $dump_section->($c, '');
-
-    return $raw;
-}
-
-# read only - use atomic_write_conf method to write
-PVE::Cluster::cfs_register_file('corosync.conf', \&parse_conf);
-# this is read/write
-PVE::Cluster::cfs_register_file('corosync.conf.new', \&parse_conf,
-                               \&write_conf);
-
-sub check_conf_exists {
-    my ($noerr) = @_;
-
-    my $exists = -f "$basedir/corosync.conf";
-
-    die "Error: Corosync config '$basedir/corosync.conf' does not exist - is this node part of a cluster?\n"
-       if !$noerr && !$exists;
-
-    return $exists;
-}
-
-sub update_nodelist {
-    my ($conf, $nodelist) = @_;
-
-    $conf->{main}->{nodelist}->{node} = $nodelist;
-
-    atomic_write_conf($conf);
-}
-
-sub nodelist {
-    my ($conf) = @_;
-    return clone($conf->{main}->{nodelist}->{node});
-}
-
-sub totem_config {
-    my ($conf) = @_;
-    return clone($conf->{main}->{totem});
-}
-
-# caller must hold corosync.conf cfs lock if used in read-modify-write cycle
-sub atomic_write_conf {
-    my ($conf, $no_increase_version) = @_;
-
-    if (!$no_increase_version) {
-       die "invalid corosync config: unable to read config version\n"
-           if !defined($conf->{main}->{totem}->{config_version});
-       $conf->{main}->{totem}->{config_version}++;
-    }
-
-    PVE::Cluster::cfs_write_file("corosync.conf.new", $conf);
-
-    rename("/etc/pve/corosync.conf.new", "/etc/pve/corosync.conf")
-       || die "activating corosync.conf.new failed - $!\n";
-}
-
-# for creating a new cluster with the current node
-# params are those from the API/CLI cluster create call
-sub create_conf {
-    my ($nodename, $param) = @_;
-
-    my $clustername = $param->{clustername};
-    my $nodeid = $param->{nodeid} || 1;
-    my $votes = $param->{votes} || 1;
-
-    my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
-
-    my $links = extract_corosync_link_args($param);
-
-    # if no links given, fall back to local IP as link0
-    $links->{0} = { address => $local_ip_address }
-       if !%$links;
-
-    my $conf = {
-       totem => {
-           version => 2, # protocol version
-           secauth => 'on',
-           cluster_name => $clustername,
-           config_version => 0,
-           ip_version => 'ipv4-6',
-           link_mode => 'passive',
-           interface => {},
-       },
-       nodelist => {
-           node => {
-               $nodename => {
-                   name => $nodename,
-                   nodeid => $nodeid,
-                   quorum_votes => $votes,
-               },
-           },
-       },
-       quorum => {
-           provider => 'corosync_votequorum',
-       },
-       logging => {
-           to_syslog => 'yes',
-           debug => 'off',
-       },
-    };
-    my $totem = $conf->{totem};
-    my $node = $conf->{nodelist}->{node}->{$nodename};
-
-    foreach my $lnum (keys %$links) {
-       my $link = $links->{$lnum};
-
-       $totem->{interface}->{$lnum} = { linknumber => $lnum };
-
-       my $prio = $link->{priority};
-       $totem->{interface}->{$lnum}->{knet_link_priority} = $prio if $prio;
-
-       $node->{"ring${lnum}_addr"} = $link->{address};
-    }
-
-    return { main => $conf };
-}
-
-# returns (\@errors, \@warnings) to the caller, does *not* 'die' or 'warn'
-# verification was successful if \@errors is empty
-sub verify_conf {
-    my ($conf) = @_;
-
-    my @errors = ();
-    my @warnings = ();
-
-    my $nodelist = nodelist($conf);
-    if (!$nodelist) {
-       push @errors, "no nodes found";
-       return (\@errors, \@warnings);
-    }
-
-    my $totem = $conf->{main}->{totem};
-    if (!$totem) {
-       push @errors, "no totem found";
-       return (\@errors, \@warnings);
-    }
-
-    if ((!defined($totem->{secauth}) || $totem->{secauth} ne 'on') &&
-       (!defined($totem->{crypto_cipher}) || $totem->{crypto_cipher} eq 'none')) {
-       push @warnings, "warning: authentication/encryption is not explicitly enabled"
-           . " (secauth / crypto_cipher / crypto_hash)";
-    }
-
-    my $interfaces = $totem->{interface};
-
-    my $verify_link_ip = sub {
-       my ($key, $link, $node) = @_;
-       my ($resolved_ip, undef) = resolve_hostname_like_corosync($link, $conf);
-       if (!defined($resolved_ip)) {
-           push @warnings, "warning: unable to resolve $key '$link' for node '$node'"
-               . " to an IP address according to Corosync's resolve strategy -"
-               . " cluster could fail on restart!";
-       } elsif ($resolved_ip ne $link) {
-           push @warnings, "warning: $key '$link' for node '$node' resolves to"
-               . " '$resolved_ip' - consider replacing it with the currently"
-               . " resolved IP address for stability";
-       }
-    };
-
-    # sort for output order stability
-    my @node_names = sort keys %$nodelist;
-
-    my $node_links = {};
-    foreach my $node (@node_names) {
-       my $options = $nodelist->{$node};
-       foreach my $opt (keys %$options) {
-           my ($linktype, $linkid) = parse_link_entry($opt);
-           next if !defined($linktype);
-           $node_links->{$node}->{$linkid} = {
-               name => "${linktype}${linkid}_addr",
-               addr => $options->{$opt},
-           };
-       }
-    }
-
-    if (%$interfaces) {
-       # if interfaces are defined, *all* links must have a matching interface
-       # definition, and vice versa
-       for my $link (0..MAX_LINK_INDEX) {
-           my $have_interface = defined($interfaces->{$link});
-           foreach my $node (@node_names) {
-               my $linkdef = $node_links->{$node}->{$link};
-               if (defined($linkdef)) {
-                   $verify_link_ip->($linkdef->{name}, $linkdef->{addr}, $node);
-                   if (!$have_interface) {
-                       push @errors, "node '$node' has '$linkdef->{name}', but"
-                           . " there is no interface number $link configured";
-                   }
-               } else {
-                   if ($have_interface) {
-                       push @errors, "node '$node' is missing address for"
-                           . "interface number $link";
-                   }
-               }
-           }
-       }
-    } else {
-       # without interfaces, only check that links are consistent among nodes
-       for my $link (0..MAX_LINK_INDEX) {
-           my $nodes_with_link = {};
-           foreach my $node (@node_names) {
-               my $linkdef = $node_links->{$node}->{$link};
-               if (defined($linkdef)) {
-                   $verify_link_ip->($linkdef->{name}, $linkdef->{addr}, $node);
-                   $nodes_with_link->{$node} = 1;
-               }
-           }
-
-           if (%$nodes_with_link) {
-               foreach my $node (@node_names) {
-                   if (!defined($nodes_with_link->{$node})) {
-                       push @errors, "node '$node' is missing link $link,"
-                           . " which is configured on other nodes";
-                   }
-               }
-           }
-       }
-    }
-
-    return (\@errors, \@warnings);
-}
-
-# returns ($linktype, $linkid) with $linktype being 'ring' for now, and possibly
-# 'link' with upcoming corosync versions
-sub parse_link_entry {
-    my ($opt) = @_;
-    return (undef, undef) if $opt !~ $link_addr_re;
-    return ($1, $2);
-}
-
-sub for_all_corosync_addresses {
-    my ($corosync_conf, $ip_version, $func) = @_;
-
-    my $nodelist = nodelist($corosync_conf);
-    return if !defined($nodelist);
-
-    # iterate sorted to make rules deterministic (for change detection)
-    foreach my $node_name (sort keys %$nodelist) {
-       my $node_config = $nodelist->{$node_name};
-       foreach my $node_key (sort keys %$node_config) {
-           if ($node_key =~ $link_addr_re) {
-               my $node_address = $node_config->{$node_key};
-
-               my($ip, $version) = resolve_hostname_like_corosync($node_address, $corosync_conf);
-               next if !defined($ip);
-               next if defined($version) && defined($ip_version) && $version != $ip_version;
-
-               $func->($node_name, $ip, $version, $node_key);
-           }
-       }
-    }
-}
-
-# NOTE: Corosync actually only resolves on startup or config change, but we
-# currently do not have an easy way to synchronize our behaviour to that.
-sub resolve_hostname_like_corosync {
-    my ($hostname, $corosync_conf) = @_;
-
-    my $corosync_strategy = $corosync_conf->{main}->{totem}->{ip_version};
-    $corosync_strategy = lc ($corosync_strategy // "ipv6-4");
-
-    my $match_ip_and_version = sub {
-       my ($addr) = @_;
-
-       return undef if !defined($addr);
-
-       if ($addr =~ m/^$IPV4RE$/) {
-           return ($addr, 4);
-       } elsif ($addr =~ m/^$IPV6RE$/) {
-           return ($addr, 6);
-       }
-
-       return undef;
-    };
-
-    my ($resolved_ip, $ip_version) = $match_ip_and_version->($hostname);
-
-    return ($resolved_ip, $ip_version) if defined($resolved_ip);
-
-    my $resolved_ip4;
-    my $resolved_ip6;
-
-    my @resolved_raw;
-    eval { @resolved_raw = PVE::Tools::getaddrinfo_all($hostname); };
-
-    return undef if ($@ || !@resolved_raw);
-
-    foreach my $socket_info (@resolved_raw) {
-       next if !$socket_info->{addr};
-
-       my ($family, undef, $host) = PVE::Tools::unpack_sockaddr_in46($socket_info->{addr});
-
-       if ($family == AF_INET && !defined($resolved_ip4)) {
-           $resolved_ip4 = inet_ntop(AF_INET, $host);
-       } elsif ($family == AF_INET6 && !defined($resolved_ip6)) {
-           $resolved_ip6 = inet_ntop(AF_INET6, $host);
-       }
-
-       last if defined($resolved_ip4) && defined($resolved_ip6);
-    }
-
-    # corosync_strategy specifies the order in which IP addresses are resolved
-    # by corosync. We need to match that order, to ensure we create firewall
-    # rules for the correct address family.
-    if ($corosync_strategy eq "ipv4") {
-       $resolved_ip = $resolved_ip4;
-    } elsif ($corosync_strategy eq "ipv6") {
-       $resolved_ip = $resolved_ip6;
-    } elsif ($corosync_strategy eq "ipv6-4") {
-       $resolved_ip = $resolved_ip6 // $resolved_ip4;
-    } elsif ($corosync_strategy eq "ipv4-6") {
-       $resolved_ip = $resolved_ip4 // $resolved_ip6;
-    }
-
-    return $match_ip_and_version->($resolved_ip);
-}
-
-1;
diff --git a/data/PVE/DataCenterConfig.pm b/data/PVE/DataCenterConfig.pm
deleted file mode 100644 (file)
index 5f12450..0000000
+++ /dev/null
@@ -1,553 +0,0 @@
-package PVE::DataCenterConfig;
-
-use strict;
-use warnings;
-
-use PVE::JSONSchema qw(parse_property_string);
-use PVE::Tools;
-use PVE::Cluster;
-
-my $crs_format = {
-    ha => {
-       type => 'string',
-       enum => ['basic', 'static'],
-       optional => 1,
-       default => 'basic',
-       description => "Use this resource scheduler mode for HA.",
-       verbose_description => "Configures how the HA manager should select nodes to start or ".
-           "recover services. With 'basic', only the number of services is used, with 'static', ".
-           "static CPU and memory configuration of services is considered.",
-    },
-    'ha-rebalance-on-start' => {
-       type => 'boolean',
-       optional => 1,
-       default => 0,
-       description => "Set to use CRS for selecting a suited node when a HA services request-state"
-           ." changes from stop to start.",
-    }
-};
-
-my $migration_format = {
-    type => {
-       default_key => 1,
-       type => 'string',
-       enum => ['secure', 'insecure'],
-       description => "Migration traffic is encrypted using an SSH tunnel by " .
-         "default. On secure, completely private networks this can be " .
-         "disabled to increase performance.",
-       default => 'secure',
-    },
-    network => {
-       optional => 1,
-       type => 'string', format => 'CIDR',
-       format_description => 'CIDR',
-       description => "CIDR of the (sub) network that is used for migration."
-    },
-};
-
-my $notification_format = {
-    'package-updates' => {
-       type => 'string',
-       enum => ['auto', 'always', 'never'],
-       description => "Control when the daily update job should send out notification mails.",
-       verbose_description => "Control how often the daily update job should send out notification mails:\n"
-           ."* 'auto' daily for systems with a valid subscription, as those are assumed to be "
-           ." production-ready and thus should know about pending updates.\n"
-           ."* 'always' every update, if there are new pending updates.\n"
-           ."* 'never' never send a notification for new pending updates.\n",
-       default => 'auto',
-    },
-};
-
-my $ha_format = {
-    shutdown_policy => {
-       type => 'string',
-       enum => ['freeze', 'failover', 'conditional', 'migrate'],
-       description => "The policy for HA services on node shutdown. 'freeze' disables ".
-           "auto-recovery, 'failover' ensures recovery, 'conditional' recovers on ".
-           "poweroff and freezes on reboot. 'migrate' will migrate running services ".
-           "to other nodes, if possible. With 'freeze' or 'failover', HA Services will ".
-           "always get stopped first on shutdown.",
-       verbose_description => "Describes the policy for handling HA services on poweroff ".
-           "or reboot of a node. Freeze will always freeze services which are still located ".
-           "on the node on shutdown, those services won't be recovered by the HA manager. ".
-           "Failover will not mark the services as frozen and thus the services will get ".
-           "recovered to other nodes, if the shutdown node does not come up again quickly ".
-           "(< 1min). 'conditional' chooses automatically depending on the type of shutdown, ".
-           "i.e., on a reboot the service will be frozen but on a poweroff the service will ".
-           "stay as is, and thus get recovered after about 2 minutes. ".
-           "Migrate will try to move all running services to another node when a reboot or ".
-           "shutdown was triggered. The poweroff process will only continue once no running services ".
-           "are located on the node anymore. If the node comes up again, the service will ".
-           "be moved back to the previously powered-off node, at least if no other migration, ".
-           "reloaction or recovery took place.",
-       default => 'conditional',
-    }
-};
-
-my $next_id_format = {
-    lower => {
-       type => 'integer',
-       description => "Lower, inclusive boundary for free next-id API range.",
-       min => 100,
-       max => 1000 * 1000 * 1000 - 1,
-       default => 100,
-       optional => 1,
-    },
-    upper => {
-       type => 'integer',
-       description => "Upper, exclusive boundary for free next-id API range.",
-       min => 100,
-       max => 1000 * 1000 * 1000,
-       default => 1000 * 1000, # lower than the maximum on purpose
-       optional => 1,
-    },
-};
-
-my $u2f_format = {
-    appid => {
-       type => 'string',
-       description => "U2F AppId URL override. Defaults to the origin.",
-       format_description => 'APPID',
-       optional => 1,
-    },
-    origin => {
-       type => 'string',
-       description => "U2F Origin override. Mostly useful for single nodes with a single URL.",
-       format_description => 'URL',
-       optional => 1,
-    },
-};
-
-my $webauthn_format = {
-    rp => {
-       type => 'string',
-       description =>
-           'Relying party name. Any text identifier.'
-           .' Changing this *may* break existing credentials.',
-       format_description => 'RELYING_PARTY',
-       optional => 1,
-    },
-    origin => {
-       type => 'string',
-       description =>
-           'Site origin. Must be a `https://` URL (or `http://localhost`).'
-           .' Should contain the address users type in their browsers to access'
-           .' the web interface.'
-           .' Changing this *may* break existing credentials.',
-       format_description => 'URL',
-       optional => 1,
-    },
-    id => {
-       type => 'string',
-       description =>
-           'Relying party ID. Must be the domain name without protocol, port or location.'
-           .' Changing this *will* break existing credentials.',
-       format_description => 'DOMAINNAME',
-       optional => 1,
-    },
-    'allow-subdomains' => {
-       type => 'boolean',
-       description => 'Whether to allow the origin to be a subdomain, rather than the exact URL.',
-       optional => 1,
-       default => 1,
-    },
-};
-
-PVE::JSONSchema::register_format('mac-prefix', \&pve_verify_mac_prefix);
-sub pve_verify_mac_prefix {
-    my ($mac_prefix, $noerr) = @_;
-
-    if ($mac_prefix !~ m/^[a-f0-9][02468ace](?::[a-f0-9]{2}){0,2}:?$/i) {
-       return undef if $noerr;
-       die "value is not a valid unicast MAC address prefix\n";
-    }
-    return $mac_prefix;
-}
-
-my $COLOR_RE = '[0-9a-fA-F]{6}';
-my $TAG_COLOR_OVERRIDE_RE = "(?:${PVE::JSONSchema::PVE_TAG_RE}:${COLOR_RE}(?:\:${COLOR_RE})?)";
-
-my $tag_style_format = {
-    'shape' => {
-       optional => 1,
-       type => 'string',
-       enum => ['full', 'circle', 'dense', 'none'],
-       default => 'circle',
-       description => "Tag shape for the web ui tree. 'full' draws the full tag. "
-           ."'circle' draws only a circle with the background color. "
-           ."'dense' only draws a small rectancle (useful when many tags are assigned to each guest)."
-           ."'none' disables showing the tags.",
-    },
-    'color-map' => {
-       optional => 1,
-       type => 'string',
-       pattern => "${TAG_COLOR_OVERRIDE_RE}(?:\;$TAG_COLOR_OVERRIDE_RE)*",
-       typetext => '<tag>:<hex-color>[:<hex-color-for-text>][;<tag>=...]',
-       description => "Manual color mapping for tags (semicolon separated).",
-    },
-    ordering => {
-       optional => 1,
-       type => 'string',
-       enum => ['config', 'alphabetical'],
-       default => 'alphabetical',
-       description => 'Controls the sorting of the tags in the web-interface and the API update.',
-    },
-    'case-sensitive' => {
-       type => 'boolean',
-       description => 'Controls if filtering for unique tags on update should check case-sensitive.',
-       optional => 1,
-       default => 0,
-    },
-};
-
-my $user_tag_privs_format = {
-    'user-allow' => {
-       optional => 1,
-       type => 'string',
-       enum => ['none', 'list', 'existing', 'free'],
-       default => 'free',
-       description => "Controls tag usage for users without `Sys.Modify` on `/` by either "
-           ."allowing `none`, a `list`, already `existing` or anything (`free`).",
-       verbose_description => "Controls which tags can be set or deleted on resources a user "
-           ."controls (such as guests). Users with the `Sys.Modify` privilege on `/` are always "
-           ." unrestricted. "
-           ."* 'none' no tags are usable. "
-           ."* 'list' tags from 'user-allow-list' are usable. "
-           ."* 'existing' like list, but already existing tags of resources are also usable."
-           ."* 'free' no tag restrictions.",
-    },
-    'user-allow-list' => {
-       optional => 1,
-       type => 'string',
-       pattern => "${PVE::JSONSchema::PVE_TAG_RE}(?:\;${PVE::JSONSchema::PVE_TAG_RE})*",
-       typetext => "<tag>[;<tag>...]",
-       description => "List of tags users are allowed to set and delete (semicolon separated) "
-           ."for 'user-allow' values 'list' and 'existing'.",
-    },
-};
-
-my $datacenter_schema = {
-    type => "object",
-    additionalProperties => 0,
-    properties => {
-       crs => {
-           optional => 1,
-           type => 'string', format => $crs_format,
-           description => "Cluster resource scheduling settings.",
-       },
-       keyboard => {
-           optional => 1,
-           type => 'string',
-           description => "Default keybord layout for vnc server.",
-           enum => PVE::Tools::kvmkeymaplist(),
-       },
-       language => {
-           optional => 1,
-           type => 'string',
-           description => "Default GUI language.",
-           enum => [
-               'ca',
-               'da',
-               'de',
-               'en',
-               'es',
-               'eu',
-               'fa',
-               'fr',
-               'he',
-               'it',
-               'ja',
-               'nb',
-               'nn',
-               'pl',
-               'pt_BR',
-               'ru',
-               'sl',
-               'sv',
-               'tr',
-               'zh_CN',
-               'zh_TW',
-           ],
-       },
-       http_proxy => {
-           optional => 1,
-           type => 'string',
-           description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
-           pattern => "http://.*",
-       },
-       # FIXME: remove with 8.0 (add check to pve7to8!), merged into "migration" since 4.3
-       migration_unsecure => {
-           optional => 1,
-           type => 'boolean',
-           description => "Migration is secure using SSH tunnel by default. " .
-             "For secure private networks you can disable it to speed up " .
-             "migration. Deprecated, use the 'migration' property instead!",
-       },
-       'next-id' => {
-           optional => 1,
-           type => 'string',
-           format => $next_id_format,
-           description => "Control the range for the free VMID auto-selection pool.",
-       },
-       migration => {
-           optional => 1,
-           type => 'string', format => $migration_format,
-           description => "For cluster wide migration settings.",
-       },
-       console => {
-           optional => 1,
-           type => 'string',
-           description => "Select the default Console viewer. You can either use the builtin java"
-               ." applet (VNC; deprecated and maps to html5), an external virt-viewer comtatible application (SPICE), an HTML5 based vnc viewer (noVNC), or an HTML5 based console client (xtermjs). If the selected viewer is not available (e.g. SPICE not activated for the VM), the fallback is noVNC.",
-           # FIXME: remove 'applet' with 8.0 (add pve7to8 check!)
-           enum => ['applet', 'vv', 'html5', 'xtermjs'],
-       },
-       email_from => {
-           optional => 1,
-           type => 'string',
-           format => 'email-opt',
-           description => "Specify email address to send notification from (default is root@\$hostname)",
-       },
-       max_workers => {
-           optional => 1,
-           type => 'integer',
-           minimum => 1,
-           description => "Defines how many workers (per node) are maximal started ".
-             " on actions like 'stopall VMs' or task from the ha-manager.",
-       },
-       fencing => {
-           optional => 1,
-           type => 'string',
-           default => 'watchdog',
-           enum => [ 'watchdog', 'hardware', 'both' ],
-           description => "Set the fencing mode of the HA cluster. Hardware mode " .
-             "needs a valid configuration of fence devices in /etc/pve/ha/fence.cfg." .
-             " With both all two modes are used." .
-             "\n\nWARNING: 'hardware' and 'both' are EXPERIMENTAL & WIP",
-       },
-       ha => {
-           optional => 1,
-           type => 'string', format => $ha_format,
-           description => "Cluster wide HA settings.",
-       },
-       mac_prefix => {
-           optional => 1,
-           type => 'string',
-           format => 'mac-prefix',
-           description => 'Prefix for autogenerated MAC addresses.',
-       },
-       notify => {
-           optional => 1,
-           type => 'string', format => $notification_format,
-           description => "Cluster-wide notification settings.",
-       },
-       bwlimit => PVE::JSONSchema::get_standard_option('bwlimit'),
-       u2f => {
-           optional => 1,
-           type => 'string',
-           format => $u2f_format,
-           description => 'u2f',
-       },
-       webauthn => {
-           optional => 1,
-           type => 'string',
-           format => $webauthn_format,
-           description => 'webauthn configuration',
-       },
-       description => {
-           type => 'string',
-           description => "Datacenter description. Shown in the web-interface datacenter notes panel."
-               ." This is saved as comment inside the configuration file.",
-           maxLength => 64 * 1024,
-           optional => 1,
-       },
-       'tag-style' => {
-           optional => 1,
-           type => 'string',
-           description => "Tag style options.",
-           format => $tag_style_format,
-       },
-       'user-tag-access' => {
-           optional => 1,
-           type => 'string',
-           description => "Privilege options for user-settable tags",
-           format => $user_tag_privs_format,
-       },
-       'registered-tags' => {
-           optional => 1,
-           type => 'string',
-           description => "A list of tags that require a `Sys.Modify` on '/' to set and delete. "
-               ."Tags set here that are also in 'user-tag-access' also require `Sys.Modify`.",
-           pattern => "(?:${PVE::JSONSchema::PVE_TAG_RE};)*${PVE::JSONSchema::PVE_TAG_RE}",
-           typetext => "<tag>[;<tag>...]",
-       },
-    },
-};
-
-# make schema accessible from outside (for documentation)
-sub get_datacenter_schema { return $datacenter_schema };
-
-sub parse_datacenter_config {
-    my ($filename, $raw) = @_;
-
-    $raw = '' if !defined($raw);
-
-    # description may be comment or key-value pair (or both)
-    my $comment = '';
-    for my $line (split(/\n/, $raw)) {
-       if ($line =~ /^\#(.*)$/) {
-           $comment .= PVE::Tools::decode_text($1) . "\n";
-       }
-    }
-
-    # parse_config ignores lines with # => use $raw
-    my $res = PVE::JSONSchema::parse_config($datacenter_schema, $filename, $raw);
-
-    $res->{description} = $comment;
-
-    if (my $crs = $res->{crs}) {
-       $res->{crs} = parse_property_string($crs_format, $crs);
-    }
-
-    if (my $migration = $res->{migration}) {
-       $res->{migration} = parse_property_string($migration_format, $migration);
-    }
-
-    if (my $next_id = $res->{'next-id'}) {
-       $res->{'next-id'} = parse_property_string($next_id_format, $next_id);
-    }
-
-    if (my $ha = $res->{ha}) {
-       $res->{ha} = parse_property_string($ha_format, $ha);
-    }
-    if (my $notify = $res->{notify}) {
-       $res->{notify} = parse_property_string($notification_format, $notify);
-    }
-
-    if (my $u2f = $res->{u2f}) {
-       $res->{u2f} = parse_property_string($u2f_format, $u2f);
-    }
-
-    if (my $webauthn = $res->{webauthn}) {
-       $res->{webauthn} = parse_property_string($webauthn_format, $webauthn);
-    }
-
-    if (my $tag_style = $res->{'tag-style'}) {
-       $res->{'tag-style'} = parse_property_string($tag_style_format, $tag_style);
-    }
-
-    if (my $user_tag_privs = $res->{'user-tag-access'}) {
-       $res->{'user-tag-access'} =
-           parse_property_string($user_tag_privs_format, $user_tag_privs);
-
-       if (my $user_tags = $res->{'user-tag-access'}->{'user-allow-list'}) {
-           $res->{'user-tag-access'}->{'user-allow-list'} = [split(';', $user_tags)];
-       }
-    }
-
-    if (my $admin_tags = $res->{'registered-tags'}) {
-       $res->{'registered-tags'} = [split(';', $admin_tags)];
-    }
-
-    # for backwards compatibility only, new migration property has precedence
-    if (defined($res->{migration_unsecure})) {
-       if (defined($res->{migration}->{type})) {
-           warn "deprecated setting 'migration_unsecure' and new 'migration: type' " .
-             "set at same time! Ignore 'migration_unsecure'\n";
-       } else {
-           $res->{migration}->{type} = ($res->{migration_unsecure}) ? 'insecure' : 'secure';
-       }
-    }
-
-    # for backwards compatibility only, applet maps to html5
-    if (defined($res->{console}) && $res->{console} eq 'applet') {
-       $res->{console} = 'html5';
-    }
-
-    return $res;
-}
-
-sub write_datacenter_config {
-    my ($filename, $cfg) = @_;
-
-    # map deprecated setting to new one
-    if (defined($cfg->{migration_unsecure}) && !defined($cfg->{migration})) {
-       my $migration_unsecure = delete $cfg->{migration_unsecure};
-       $cfg->{migration}->{type} = ($migration_unsecure) ? 'insecure' : 'secure';
-    }
-
-    # map deprecated applet setting to html5
-    if (defined($cfg->{console}) && $cfg->{console} eq 'applet') {
-       $cfg->{console} = 'html5';
-    }
-
-    if (ref(my $crs = $cfg->{crs})) {
-       $cfg->{crs} = PVE::JSONSchema::print_property_string($crs, $crs_format);
-    }
-
-    if (ref(my $migration = $cfg->{migration})) {
-       $cfg->{migration} = PVE::JSONSchema::print_property_string($migration, $migration_format);
-    }
-
-    if (defined(my $next_id = $cfg->{'next-id'})) {
-        $next_id = parse_property_string($next_id_format, $next_id) if !ref($next_id);
-
-       my $lower = int($next_id->{lower} // $next_id_format->{lower}->{default});
-       my $upper = int($next_id->{upper} // $next_id_format->{upper}->{default});
-
-       die "lower ($lower) <= upper ($upper) boundary rule broken\n" if $lower > $upper;
-
-       $cfg->{'next-id'} = PVE::JSONSchema::print_property_string($next_id, $next_id_format);
-    }
-
-    if (ref(my $ha = $cfg->{ha})) {
-       $cfg->{ha} = PVE::JSONSchema::print_property_string($ha, $ha_format);
-    }
-    if (ref(my $notify = $cfg->{notify})) {
-       $cfg->{notify} = PVE::JSONSchema::print_property_string($notify, $notification_format);
-    }
-
-    if (ref(my $u2f = $cfg->{u2f})) {
-       $cfg->{u2f} = PVE::JSONSchema::print_property_string($u2f, $u2f_format);
-    }
-
-    if (ref(my $webauthn = $cfg->{webauthn})) {
-       $cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, $webauthn_format);
-    }
-
-    if (ref(my $tag_style = $cfg->{'tag-style'})) {
-       $cfg->{'tag-style'} = PVE::JSONSchema::print_property_string($tag_style, $tag_style_format);
-    }
-
-    if (ref(my $user_tag_privs = $cfg->{'user-tag-access'})) {
-       if (my $user_tags = $user_tag_privs->{'user-allow-list'}) {
-           $user_tag_privs->{'user-allow-list'} = join(';', sort $user_tags->@*);
-       }
-       $cfg->{'user-tag-access'} =
-           PVE::JSONSchema::print_property_string($user_tag_privs, $user_tag_privs_format);
-    }
-
-    if (ref(my $admin_tags = $cfg->{'registered-tags'})) {
-       $cfg->{'registered-tags'} = join(';', sort $admin_tags->@*);
-    }
-
-    my $comment = '';
-    # add description as comment to top of file
-    my $description = $cfg->{description} || '';
-    foreach my $line (split(/\n/, $description)) {
-       $comment .= '#' .  PVE::Tools::encode_text($line) . "\n";
-    }
-    delete $cfg->{description}; # add only as comment, no additional key-value pair
-    my $dump = PVE::JSONSchema::dump_config($datacenter_schema, $filename, $cfg);
-
-    return $comment . "\n" . $dump;
-}
-
-PVE::Cluster::cfs_register_file(
-    'datacenter.cfg',
-    \&parse_datacenter_config,
-    \&write_datacenter_config,
-);
-
-1;
diff --git a/data/PVE/IPCC.pm b/data/PVE/IPCC.pm
deleted file mode 100644 (file)
index 529ebf5..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package PVE::IPCC;
-
-use 5.010001;
-use strict;
-use warnings;
-
-require Exporter;
-
-our @ISA = qw(Exporter);
-
-# Items to export into callers namespace by default. Note: do not export
-# names by default without a very good reason. Use EXPORT_OK instead.
-# Do not simply export all your public functions/methods/constants.
-
-# This allows declaration      use PVE::IPCC ':all';
-# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
-# will save memory.
-our %EXPORT_TAGS = ( 'all' => [ qw(
-       
-) ] );
-
-our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
-
-our @EXPORT = qw(
-       
-);
-
-our $VERSION = '1.0';
-
-require XSLoader;
-XSLoader::load('PVE::IPCC', $VERSION);
-
-# Preloaded methods go here.
-
-1;
-__END__
-
-=head1 NAME
-
-PVE::IPCC - Perl extension to access the PVE IPC Server
-
-=head1 SYNOPSIS
-
-  use PVE::IPCC;
-  
-  my $res = PVE::IPCC::ipcc_send_rec(1, "hello");
-  my $res = PVE::IPCC::sendfd($socketfd, $fd, $opt_data);
-
-=head1 DESCRIPTION
-
-Send/receive RAW data packets from the PVE IPC Server.
-
-Pass file descriptor over unix domain sockets (used to pass
-file destriptors to qemu fdset). This is use in PVE::QMPClient.
-
-=head2 EXPORT
-
-None by default.
-
-=head1 AUTHOR
-
-Dietmar Maurer, E<lt>dietmar@proxmox.com<gt>
-
-=cut
diff --git a/data/PVE/IPCC.xs b/data/PVE/IPCC.xs
deleted file mode 100644 (file)
index 4661df0..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include "EXTERN.h"
-#include "perl.h"
-#include "XSUB.h"
-
-#include "ppport.h"
-
-/* sendfd: BSD style file descriptor passing over unix domain sockets
- *  Richard Stevens: Unix Network Programming, Prentice Hall, 1990;
- */
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/uio.h>
-#include <unistd.h>
-#include <time.h>
-#include <errno.h>
-
-#ifndef SCM_RIGHTS
-#error "SCM_RIGHTS undefined"
-#endif 
-
-/* interface to pmxcfs (libqb) */
-#include <sys/syslog.h>
-#include <qb/qbdefs.h>
-#include <qb/qbutil.h>
-#include <qb/qblog.h>
-#include <qb/qbipcc.h>
-
-#define RESTART_FLAG_FILE "/run/pve-cluster/cfs-restart-flag"
-#define RESTART_GRACE_PERIOD 10
-
-#define PCS_SOCKET_NAME "pve2"
-
-#define PCS_SERVICE1 1
-#define MAX_MSG_SIZE (8192*128)
-
-static qb_ipcc_connection_t *conn;
-static pid_t conn_pid;
-
-static char ipcbuffer[MAX_MSG_SIZE];
-
-static qb_ipcc_connection_t *init_connection() {
-
-       static qb_ipcc_connection_t *connection = NULL;
-       struct timespec retry_timeout, now;
-       int cfs_restart_flag_fd = -1;
-
-       // check if pmxcfs is currently restarting
-       if ((cfs_restart_flag_fd = open(RESTART_FLAG_FILE, 0)) > 0) {
-               clock_gettime(CLOCK_MONOTONIC, &retry_timeout);
-               retry_timeout.tv_sec += RESTART_GRACE_PERIOD;
-       }
-
-       qb_log_init("IPCC.xs", LOG_USER, LOG_EMERG);
-       qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE);
-
-retry_connection:
-       connection = qb_ipcc_connect(PCS_SOCKET_NAME, MAX_MSG_SIZE);
-
-       if (!connection) {
-               if (cfs_restart_flag_fd >= 0) {
-                       // cfs restarting and hopefully back soon, poll
-                       clock_gettime(CLOCK_MONOTONIC, &now);
-
-                       if (now.tv_sec < retry_timeout.tv_sec ||
-                          (now.tv_sec == retry_timeout.tv_sec &&
-                           now.tv_nsec < retry_timeout.tv_nsec)) {
-
-                               usleep(100 * 1000);
-                               goto retry_connection;
-
-                       } else {
-                               // timeout: cleanup flag file if still the same
-                               struct stat s;
-                               fstat(cfs_restart_flag_fd, &s);
-                               if (s.st_nlink > 0)
-                                       unlink(RESTART_FLAG_FILE);
-                       }
-               }
-       }
-
-       if (cfs_restart_flag_fd >= 0) close(cfs_restart_flag_fd);
-
-       return connection;
-}
-
-
-MODULE = PVE::IPCC             PACKAGE = PVE::IPCC             
-
-SV *
-ipcc_send_rec(msgid, data=NULL)
-I32 msgid;
-SV * data;
-PROTOTYPE: $;$
-CODE:
-{
-       uint8_t retried_cache_connection = 0;
-       pid_t cpid = getpid();
-
-       /* Each process needs its own ipcc connection,
-        * else the shared memory buffer gets corrupted.
-        */ 
-       if (conn && conn_pid != cpid) {
-               conn = NULL;
-       }
-
-       if (conn == NULL) {
-recache_connection:
-               conn = init_connection();
-
-               if (!conn)
-                       XSRETURN_UNDEF;
-
-               conn_pid = cpid;
-       }
-
-       size_t len = 0;
-       char *dataptr = NULL;
-       if (data && SvPOK(data))
-               dataptr = SvPV(data, len);
-
-       int iov_len = 2;
-       struct iovec iov[iov_len];
-
-       struct qb_ipc_request_header req_header;
-
-       req_header.id = msgid;
-       req_header.size = sizeof(req_header) + len;
-
-       iov[0].iov_base = (char *)&req_header;
-       iov[0].iov_len = sizeof(req_header);
-       iov[1].iov_base = dataptr;
-       iov[1].iov_len = len;
-
-       int32_t ms_timeout = -1; // fixme:     
-       int res = qb_ipcc_sendv_recv(conn, iov, iov_len, ipcbuffer, sizeof(ipcbuffer), ms_timeout);
-       if (res < 0) {
-               qb_ipcc_disconnect(conn);
-               conn = NULL;
-               // requests during cfs restart and the first thereafter will fail, retry
-               if (!retried_cache_connection) {
-                       retried_cache_connection = 1;
-                       goto recache_connection;
-               }
-               errno = -res;
-               XSRETURN_UNDEF;
-       }
-
-       struct qb_ipc_response_header *res_header;
-
-       res_header = (struct qb_ipc_response_header *)ipcbuffer;
-       int dsize = res_header->size - sizeof(struct qb_ipc_response_header);
-
-       if (res_header->error < 0) {
-               errno = -res_header->error;
-               XSRETURN_UNDEF;
-       } else {
-               errno = 0;
-               if (dsize > 0) {
-                       RETVAL = newSVpv(ipcbuffer + sizeof(struct qb_ipc_response_header), dsize);
-               } else {
-                       XSRETURN_UNDEF;
-               }
-       }
-}
-OUTPUT: RETVAL
-
-# helper to pass SCM ACCESS RIGHTS
-
-int
-sendfd(sock_fd, send_me_fd, data=NULL)
-int sock_fd
-int send_me_fd
-SV * data;
-CODE:
-{
-       int ret = 0;
-       struct iovec  iov[1];
-       struct msghdr msg;
-       memset(&msg, 0, sizeof(msg));
-
-       size_t len = 0;
-       char *dataptr = NULL;
-       if (data && SvPOK(data))
-               dataptr = SvPV(data, len);
-       
-       iov[0].iov_base = dataptr;
-       iov[0].iov_len = len;   
-       msg.msg_iov = iov;
-       msg.msg_iovlen = 1;
-       msg.msg_name = 0;
-       msg.msg_namelen = 0;
-
-       char control[CMSG_SPACE(sizeof(int))];
-       memset(control, 0, sizeof(control));
-
-       msg.msg_control = control;
-       msg.msg_controllen = sizeof(control);
-       msg.msg_flags = 0;
-               
-       struct cmsghdr* h = CMSG_FIRSTHDR(&msg);
-       h->cmsg_len = CMSG_LEN(sizeof(int));
-       h->cmsg_level= SOL_SOCKET;
-       h->cmsg_type = SCM_RIGHTS;
-       *((int*)CMSG_DATA(h)) = send_me_fd;
-
-       int repeat;
-       do {
-               repeat = 0;
-               ret = sendmsg(sock_fd, &msg, 0);
-               if (ret < 0) {
-                       if (errno == EINTR) {
-                               repeat = 1;
-                       } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
-                               repeat = 1;
-                               usleep(1000);
-                       }
-               }
-       } while (repeat);
-       
-       RETVAL = ret;
-}
-OUTPUT: RETVAL
diff --git a/data/PVE/Makefile b/data/PVE/Makefile
deleted file mode 100644 (file)
index 8ea5383..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-DESTDIR=
-PERL5DIR=${DESTDIR}/usr/share/perl5
-PVEDIR=${PERL5DIR}/PVE
-MAN=${DESTDIR}/usr/share/man
-BASHCOMPLETION=${DESTDIR}/usr/share/bash-completion/completions
-ZSHCOMPLETION=${DESTDIR}/usr/share/zsh/vendor-completions
-
-PERL_VENDORARCH=$(shell perl -MConfig -e 'print $$Config{vendorarch};')
-PVE_VENDORARCH=${DESTDIR}/${PERL_VENDORARCH}/auto/PVE/IPCC
-
-PERL_DOC_INC_DIRS:=..
-
-SUBDIRS=Cluster CLI API2
-SOURCES=IPCC.pm Cluster.pm Corosync.pm RRD.pm DataCenterConfig.pm SSHInfo.pm
-
-all:
-
-.PHONY: install
-install: pvecm ${SOURCES} IPCC.so pvecm.1 pvecm.bash-completion pvecm.zsh-completion datacenter.cfg.5
-       install -D -m 0755 pvecm ${DESTDIR}/usr/bin/pvecm
-       install -d ${PVEDIR}
-       for f in ${SOURCES}; do install -m 0664 $$f ${PVEDIR}/$$f; done
-       install -D IPCC.so ${PVE_VENDORARCH}/IPCC.so
-       install -D pvecm.1 ${MAN}/man1/pvecm.1
-       install -D datacenter.cfg.5 ${MAN}/man5/datacenter.cfg.5
-       install -m 0644 -D pvecm.bash-completion ${BASHCOMPLETION}/pvecm
-       install -m 0644 -D pvecm.zsh-completion ${ZSHCOMPLETION}/_pvecm
-       for d in ${SUBDIRS}; do $(MAKE) -C $$d install; done
-
-%.bash-completion:
-       perl ${PERL_DOC_INC} -T -e "use PVE::CLI::$*; PVE::CLI::$*->generate_bash_completions();" >$@.tmp
-       mv $@.tmp $@
-
-%.zsh-completion:
-       perl ${PERL_DOC_INC} -T -e "use PVE::CLI::$*; PVE::CLI::$*->generate_zsh_completions();" >$@.tmp
-       mv $@.tmp $@
-
-Cluster/IPCConst.pm:
-       $(MAKE) -C Cluster IPCConst.pm
-
-check: IPCC.so Cluster/IPCConst.pm
-       perl ${PERL_DOC_INC} -T -e "use PVE::CLI::pvecm; PVE::CLI::pvecm->verify_api();"
-
-CC=gcc
-CFLAGS += -fPIC -Wl,-z,relro -Wall -Werror -Wno-strict-aliasing -g -O2 -shared
-CFLAGS += $(shell pkg-config --cflags libqb)
-CFLAGS += $(shell perl -MExtUtils::Embed -e perl_inc)
-LDFLAGS = $(shell pkg-config --libs libqb)
-
-.c.o:
-       $(CC) $(CFLAGS) -c -o $@ $<
-
-IPCC.c: IPCC.xs ppport.h
-       xsubpp -noversioncheck IPCC.xs > IPCC.xsc
-       mv IPCC.xsc IPCC.c
-
-IPCC.so: IPCC.o
-       $(CC) ${CFLAGS} -shared -o $@ $<  ${LDFLAGS}
-
--include /usr/share/pve-doc-generator/pve-doc-generator.mk
-
-.PHONY: clean
-clean:
-       $(MAKE) cleanup-docgen
-       rm -f IPCC.so IPCC.o IPCC.c pvecm.bash-completion pvecm.zsh-completion
-       for d in ${SUBDIRS}; do $(MAKE) -C $$d clean; done
diff --git a/data/PVE/RRD.pm b/data/PVE/RRD.pm
deleted file mode 100644 (file)
index 5d4abc9..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-package PVE::RRD;
-
-use strict; use warnings;
-
-use RRDs;
-
-use PVE::Tools;
-
-sub create_rrd_data {
-    my ($rrdname, $timeframe, $cf) = @_;
-
-    my $rrddir = "/var/lib/rrdcached/db";
-
-    my $rrd = "$rrddir/$rrdname";
-
-    my $setup = {
-       hour =>  [ 60, 70 ],
-       day  =>  [ 60*30, 70 ],
-       week =>  [ 60*180, 70 ],
-       month => [ 60*720, 70 ],
-       year =>  [ 60*10080, 70 ],
-    };
-
-    my ($reso, $count) = @{$setup->{$timeframe}};
-    my $ctime  = $reso*int(time()/$reso);
-    my $req_start = $ctime - $reso*$count;
-
-    $cf = "AVERAGE" if !$cf;
-
-    my @args = (
-       "-s" => $req_start,
-       "-e" => $ctime - 1,
-       "-r" => $reso,
-       );
-
-    my $socket = "/var/run/rrdcached.sock";
-    push @args, "--daemon" => "unix:$socket" if -S $socket;
-
-    my ($start, $step, $names, $data) = RRDs::fetch($rrd, $cf, @args);
-
-    my $err = RRDs::error;
-    die "RRD error: $err\n" if $err;
-
-    die "got wrong time resolution ($step != $reso)\n"
-       if $step != $reso;
-
-    my $res = [];
-    my $fields = scalar(@$names);
-    for my $line (@$data) {
-       my $entry = { 'time' => $start };
-       $start += $step;
-       for (my $i = 0; $i < $fields; $i++) {
-           my $name = $names->[$i];
-           if (defined(my $val = $line->[$i])) {
-               $entry->{$name} = $val;
-           } else {
-               # leave empty fields undefined
-               # maybe make this configurable?
-           }
-       }
-       push @$res, $entry;
-    }
-
-    return $res;
-}
-
-sub create_rrd_graph {
-    my ($rrdname, $timeframe, $ds, $cf) = @_;
-
-    # Using RRD graph is clumsy - maybe it
-    # is better to simply fetch the data, and do all display
-    # related things with javascript (new extjs html5 graph library).
-
-    my $rrddir = "/var/lib/rrdcached/db";
-
-    my $rrd = "$rrddir/$rrdname";
-
-    my @ids = PVE::Tools::split_list($ds);
-
-    my $ds_txt = join('_', @ids);
-
-    my $filename = "${rrd}_${ds_txt}.png";
-
-    my $setup = {
-       hour =>  [ 60, 60 ],
-       day  =>  [ 60*30, 70 ],
-       week =>  [ 60*180, 70 ],
-       month => [ 60*720, 70 ],
-       year =>  [ 60*10080, 70 ],
-    };
-
-    my ($reso, $count) = @{$setup->{$timeframe}};
-
-    my @args = (
-       "--imgformat" => "PNG",
-       "--border" => 0,
-       "--height" => 200,
-       "--width" => 800,
-       "--start" => - $reso*$count,
-       "--end" => 'now' ,
-       "--lower-limit" => 0,
-       );
-
-    my $socket = "/var/run/rrdcached.sock";
-    push @args, "--daemon" => "unix:$socket" if -S $socket;
-
-    my @coldef = ('#00ddff', '#ff0000');
-
-    $cf = "AVERAGE" if !$cf;
-
-    my $i = 0;
-    foreach my $id (@ids) {
-       my $col = $coldef[$i++] || die "fixme: no color definition";
-       push @args, "DEF:${id}=$rrd:${id}:$cf";
-       my $dataid = $id;
-       if ($id eq 'cpu' || $id eq 'iowait') {
-           push @args, "CDEF:${id}_per=${id},100,*";
-           $dataid = "${id}_per";
-       }
-       push @args, "LINE2:${dataid}${col}:${id}";
-    }
-
-    push @args, '--full-size-mode';
-
-    # we do not really store data into the file
-    my $res = RRDs::graphv('-', @args);
-
-    my $err = RRDs::error;
-    die "RRD error: $err\n" if $err;
-
-    return { filename => $filename, image => $res->{image} };
-}
-
-1;
diff --git a/data/PVE/SSHInfo.pm b/data/PVE/SSHInfo.pm
deleted file mode 100644 (file)
index c351148..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-package PVE::SSHInfo;
-
-use strict;
-use warnings;
-
-use PVE::Cluster;
-use PVE::Tools;
-
-sub get_ssh_info {
-    my ($node, $network_cidr) = @_;
-
-    my $ip;
-    if (defined($network_cidr)) {
-       # Use mtunnel via to get the remote node's ip inside $network_cidr.
-       # This goes over the regular network (iow. uses get_ssh_info() with
-       # $network_cidr undefined.
-       # FIXME: Use the REST API client for this after creating an API entry
-       # for get_migration_ip.
-       my $default_remote = get_ssh_info($node, undef);
-       my $default_ssh = ssh_info_to_command($default_remote);
-       my $cmd =[@$default_ssh, 'pvecm', 'mtunnel',
-           '-migration_network', $network_cidr,
-           '-get_migration_ip'
-       ];
-       PVE::Tools::run_command($cmd, outfunc => sub {
-           my ($line) = @_;
-           chomp $line;
-           die "internal error: unexpected output from mtunnel\n"
-               if defined($ip);
-           if ($line =~ /^ip: '(.*)'$/) {
-               $ip = $1;
-           } else {
-               die "internal error: bad output from mtunnel\n"
-                   if defined($ip);
-           }
-       });
-       die "failed to get ip for node '$node' in network '$network_cidr'\n"
-           if !defined($ip);
-    } else {
-       $ip = PVE::Cluster::remote_node_ip($node);
-    }
-
-    return {
-       ip => $ip,
-       name => $node,
-       network => $network_cidr,
-    };
-}
-
-sub ssh_info_to_command_base {
-    my ($info, @extra_options) = @_;
-    return [
-       '/usr/bin/ssh',
-       '-e', 'none',
-       '-o', 'BatchMode=yes',
-       '-o', 'HostKeyAlias='.$info->{name},
-       @extra_options
-    ];
-}
-
-sub ssh_info_to_command {
-    my ($info, @extra_options) = @_;
-    my $cmd = ssh_info_to_command_base($info, @extra_options);
-    push @$cmd, "root\@$info->{ip}";
-    return $cmd;
-}
-
-1;
diff --git a/data/PVE/ppport.h b/data/PVE/ppport.h
deleted file mode 100644 (file)
index 8ec0d5f..0000000
+++ /dev/null
@@ -1,7063 +0,0 @@
-#if 0
-<<'SKIP';
-#endif
-/*
-----------------------------------------------------------------------
-
-    ppport.h -- Perl/Pollution/Portability Version 3.19
-
-    Automatically created by Devel::PPPort running under perl 5.010001.
-
-    Do NOT edit this file directly! -- Edit PPPort_pm.PL and the
-    includes in parts/inc/ instead.
-
-    Use 'perldoc ppport.h' to view the documentation below.
-
-----------------------------------------------------------------------
-
-SKIP
-
-=pod
-
-=head1 NAME
-
-ppport.h - Perl/Pollution/Portability version 3.19
-
-=head1 SYNOPSIS
-
-  perl ppport.h [options] [source files]
-
-  Searches current directory for files if no [source files] are given
-
-  --help                      show short help
-
-  --version                   show version
-
-  --patch=file                write one patch file with changes
-  --copy=suffix               write changed copies with suffix
-  --diff=program              use diff program and options
-
-  --compat-version=version    provide compatibility with Perl version
-  --cplusplus                 accept C++ comments
-
-  --quiet                     don't output anything except fatal errors
-  --nodiag                    don't show diagnostics
-  --nohints                   don't show hints
-  --nochanges                 don't suggest changes
-  --nofilter                  don't filter input files
-
-  --strip                     strip all script and doc functionality from
-                              ppport.h
-
-  --list-provided             list provided API
-  --list-unsupported          list unsupported API
-  --api-info=name             show Perl API portability information
-
-=head1 COMPATIBILITY
-
-This version of F<ppport.h> is designed to support operation with Perl
-installations back to 5.003, and has been tested up to 5.10.0.
-
-=head1 OPTIONS
-
-=head2 --help
-
-Display a brief usage summary.
-
-=head2 --version
-
-Display the version of F<ppport.h>.
-
-=head2 --patch=I<file>
-
-If this option is given, a single patch file will be created if
-any changes are suggested. This requires a working diff program
-to be installed on your system.
-
-=head2 --copy=I<suffix>
-
-If this option is given, a copy of each file will be saved with
-the given suffix that contains the suggested changes. This does
-not require any external programs. Note that this does not
-automagially add a dot between the original filename and the
-suffix. If you want the dot, you have to include it in the option
-argument.
-
-If neither C<--patch> or C<--copy> are given, the default is to
-simply print the diffs for each file. This requires either
-C<Text::Diff> or a C<diff> program to be installed.
-
-=head2 --diff=I<program>
-
-Manually set the diff program and options to use. The default
-is to use C<Text::Diff>, when installed, and output unified
-context diffs.
-
-=head2 --compat-version=I<version>
-
-Tell F<ppport.h> to check for compatibility with the given
-Perl version. The default is to check for compatibility with Perl
-version 5.003. You can use this option to reduce the output
-of F<ppport.h> if you intend to be backward compatible only
-down to a certain Perl version.
-
-=head2 --cplusplus
-
-Usually, F<ppport.h> will detect C++ style comments and
-replace them with C style comments for portability reasons.
-Using this option instructs F<ppport.h> to leave C++
-comments untouched.
-
-=head2 --quiet
-
-Be quiet. Don't print anything except fatal errors.
-
-=head2 --nodiag
-
-Don't output any diagnostic messages. Only portability
-alerts will be printed.
-
-=head2 --nohints
-
-Don't output any hints. Hints often contain useful portability
-notes. Warnings will still be displayed.
-
-=head2 --nochanges
-
-Don't suggest any changes. Only give diagnostic output and hints
-unless these are also deactivated.
-
-=head2 --nofilter
-
-Don't filter the list of input files. By default, files not looking
-like source code (i.e. not *.xs, *.c, *.cc, *.cpp or *.h) are skipped.
-
-=head2 --strip
-
-Strip all script and documentation functionality from F<ppport.h>.
-This reduces the size of F<ppport.h> dramatically and may be useful
-if you want to include F<ppport.h> in smaller modules without
-increasing their distribution size too much.
-
-The stripped F<ppport.h> will have a C<--unstrip> option that allows
-you to undo the stripping, but only if an appropriate C<Devel::PPPort>
-module is installed.
-
-=head2 --list-provided
-
-Lists the API elements for which compatibility is provided by
-F<ppport.h>. Also lists if it must be explicitly requested,
-if it has dependencies, and if there are hints or warnings for it.
-
-=head2 --list-unsupported
-
-Lists the API elements that are known not to be supported by
-F<ppport.h> and below which version of Perl they probably
-won't be available or work.
-
-=head2 --api-info=I<name>
-
-Show portability information for API elements matching I<name>.
-If I<name> is surrounded by slashes, it is interpreted as a regular
-expression.
-
-=head1 DESCRIPTION
-
-In order for a Perl extension (XS) module to be as portable as possible
-across differing versions of Perl itself, certain steps need to be taken.
-
-=over 4
-
-=item *
-
-Including this header is the first major one. This alone will give you
-access to a large part of the Perl API that hasn't been available in
-earlier Perl releases. Use
-
-    perl ppport.h --list-provided
-
-to see which API elements are provided by ppport.h.
-
-=item *
-
-You should avoid using deprecated parts of the API. For example, using
-global Perl variables without the C<PL_> prefix is deprecated. Also,
-some API functions used to have a C<perl_> prefix. Using this form is
-also deprecated. You can safely use the supported API, as F<ppport.h>
-will provide wrappers for older Perl versions.
-
-=item *
-
-If you use one of a few functions or variables that were not present in
-earlier versions of Perl, and that can't be provided using a macro, you
-have to explicitly request support for these functions by adding one or
-more C<#define>s in your source code before the inclusion of F<ppport.h>.
-
-These functions or variables will be marked C<explicit> in the list shown
-by C<--list-provided>.
-
-Depending on whether you module has a single or multiple files that
-use such functions or variables, you want either C<static> or global
-variants.
-
-For a C<static> function or variable (used only in a single source
-file), use:
-
-    #define NEED_function
-    #define NEED_variable
-
-For a global function or variable (used in multiple source files),
-use:
-
-    #define NEED_function_GLOBAL
-    #define NEED_variable_GLOBAL
-
-Note that you mustn't have more than one global request for the
-same function or variable in your project.
-
-    Function / Variable       Static Request               Global Request
-    -----------------------------------------------------------------------------------------
-    PL_parser                 NEED_PL_parser               NEED_PL_parser_GLOBAL
-    PL_signals                NEED_PL_signals              NEED_PL_signals_GLOBAL
-    eval_pv()                 NEED_eval_pv                 NEED_eval_pv_GLOBAL
-    grok_bin()                NEED_grok_bin                NEED_grok_bin_GLOBAL
-    grok_hex()                NEED_grok_hex                NEED_grok_hex_GLOBAL
-    grok_number()             NEED_grok_number             NEED_grok_number_GLOBAL
-    grok_numeric_radix()      NEED_grok_numeric_radix      NEED_grok_numeric_radix_GLOBAL
-    grok_oct()                NEED_grok_oct                NEED_grok_oct_GLOBAL
-    load_module()             NEED_load_module             NEED_load_module_GLOBAL
-    my_snprintf()             NEED_my_snprintf             NEED_my_snprintf_GLOBAL
-    my_sprintf()              NEED_my_sprintf              NEED_my_sprintf_GLOBAL
-    my_strlcat()              NEED_my_strlcat              NEED_my_strlcat_GLOBAL
-    my_strlcpy()              NEED_my_strlcpy              NEED_my_strlcpy_GLOBAL
-    newCONSTSUB()             NEED_newCONSTSUB             NEED_newCONSTSUB_GLOBAL
-    newRV_noinc()             NEED_newRV_noinc             NEED_newRV_noinc_GLOBAL
-    newSV_type()              NEED_newSV_type              NEED_newSV_type_GLOBAL
-    newSVpvn_flags()          NEED_newSVpvn_flags          NEED_newSVpvn_flags_GLOBAL
-    newSVpvn_share()          NEED_newSVpvn_share          NEED_newSVpvn_share_GLOBAL
-    pv_display()              NEED_pv_display              NEED_pv_display_GLOBAL
-    pv_escape()               NEED_pv_escape               NEED_pv_escape_GLOBAL
-    pv_pretty()               NEED_pv_pretty               NEED_pv_pretty_GLOBAL
-    sv_2pv_flags()            NEED_sv_2pv_flags            NEED_sv_2pv_flags_GLOBAL
-    sv_2pvbyte()              NEED_sv_2pvbyte              NEED_sv_2pvbyte_GLOBAL
-    sv_catpvf_mg()            NEED_sv_catpvf_mg            NEED_sv_catpvf_mg_GLOBAL
-    sv_catpvf_mg_nocontext()  NEED_sv_catpvf_mg_nocontext  NEED_sv_catpvf_mg_nocontext_GLOBAL
-    sv_pvn_force_flags()      NEED_sv_pvn_force_flags      NEED_sv_pvn_force_flags_GLOBAL
-    sv_setpvf_mg()            NEED_sv_setpvf_mg            NEED_sv_setpvf_mg_GLOBAL
-    sv_setpvf_mg_nocontext()  NEED_sv_setpvf_mg_nocontext  NEED_sv_setpvf_mg_nocontext_GLOBAL
-    vload_module()            NEED_vload_module            NEED_vload_module_GLOBAL
-    vnewSVpvf()               NEED_vnewSVpvf               NEED_vnewSVpvf_GLOBAL
-    warner()                  NEED_warner                  NEED_warner_GLOBAL
-
-To avoid namespace conflicts, you can change the namespace of the
-explicitly exported functions / variables using the C<DPPP_NAMESPACE>
-macro. Just C<#define> the macro before including C<ppport.h>:
-
-    #define DPPP_NAMESPACE MyOwnNamespace_
-    #include "ppport.h"
-
-The default namespace is C<DPPP_>.
-
-=back
-
-The good thing is that most of the above can be checked by running
-F<ppport.h> on your source code. See the next section for
-details.
-
-=head1 EXAMPLES
-
-To verify whether F<ppport.h> is needed for your module, whether you
-should make any changes to your code, and whether any special defines
-should be used, F<ppport.h> can be run as a Perl script to check your
-source code. Simply say:
-
-    perl ppport.h
-
-The result will usually be a list of patches suggesting changes
-that should at least be acceptable, if not necessarily the most
-efficient solution, or a fix for all possible problems.
-
-If you know that your XS module uses features only available in
-newer Perl releases, if you're aware that it uses C++ comments,
-and if you want all suggestions as a single patch file, you could
-use something like this:
-
-    perl ppport.h --compat-version=5.6.0 --cplusplus --patch=test.diff
-
-If you only want your code to be scanned without any suggestions
-for changes, use:
-
-    perl ppport.h --nochanges
-
-You can specify a different C<diff> program or options, using
-the C<--diff> option:
-
-    perl ppport.h --diff='diff -C 10'
-
-This would output context diffs with 10 lines of context.
-
-If you want to create patched copies of your files instead, use:
-
-    perl ppport.h --copy=.new
-
-To display portability information for the C<newSVpvn> function,
-use:
-
-    perl ppport.h --api-info=newSVpvn
-
-Since the argument to C<--api-info> can be a regular expression,
-you can use
-
-    perl ppport.h --api-info=/_nomg$/
-
-to display portability information for all C<_nomg> functions or
-
-    perl ppport.h --api-info=/./
-
-to display information for all known API elements.
-
-=head1 BUGS
-
-If this version of F<ppport.h> is causing failure during
-the compilation of this module, please check if newer versions
-of either this module or C<Devel::PPPort> are available on CPAN
-before sending a bug report.
-
-If F<ppport.h> was generated using the latest version of
-C<Devel::PPPort> and is causing failure of this module, please
-file a bug report using the CPAN Request Tracker at L<http://rt.cpan.org/>.
-
-Please include the following information:
-
-=over 4
-
-=item 1.
-
-The complete output from running "perl -V"
-
-=item 2.
-
-This file.
-
-=item 3.
-
-The name and version of the module you were trying to build.
-
-=item 4.
-
-A full log of the build that failed.
-
-=item 5.
-
-Any other information that you think could be relevant.
-
-=back
-
-For the latest version of this code, please get the C<Devel::PPPort>
-module from CPAN.
-
-=head1 COPYRIGHT
-
-Version 3.x, Copyright (c) 2004-2009, Marcus Holland-Moritz.
-
-Version 2.x, Copyright (C) 2001, Paul Marquess.
-
-Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
-
-This program is free software; you can redistribute it and/or
-modify it under the same terms as Perl itself.
-
-=head1 SEE ALSO
-
-See L<Devel::PPPort>.
-
-=cut
-
-use strict;
-
-# Disable broken TRIE-optimization
-BEGIN { eval '${^RE_TRIE_MAXBUF} = -1' if $] >= 5.009004 && $] <= 5.009005 }
-
-my $VERSION = 3.19;
-
-my %opt = (
-  quiet     => 0,
-  diag      => 1,
-  hints     => 1,
-  changes   => 1,
-  cplusplus => 0,
-  filter    => 1,
-  strip     => 0,
-  version   => 0,
-);
-
-my($ppport) = $0 =~ /([\w.]+)$/;
-my $LF = '(?:\r\n|[\r\n])';   # line feed
-my $HS = "[ \t]";             # horizontal whitespace
-
-# Never use C comments in this file!
-my $ccs  = '/'.'*';
-my $cce  = '*'.'/';
-my $rccs = quotemeta $ccs;
-my $rcce = quotemeta $cce;
-
-eval {
-  require Getopt::Long;
-  Getopt::Long::GetOptions(\%opt, qw(
-    help quiet diag! filter! hints! changes! cplusplus strip version
-    patch=s copy=s diff=s compat-version=s
-    list-provided list-unsupported api-info=s
-  )) or usage();
-};
-
-if ($@ and grep /^-/, @ARGV) {
-  usage() if "@ARGV" =~ /^--?h(?:elp)?$/;
-  die "Getopt::Long not found. Please don't use any options.\n";
-}
-
-if ($opt{version}) {
-  print "This is $0 $VERSION.\n";
-  exit 0;
-}
-
-usage() if $opt{help};
-strip() if $opt{strip};
-
-if (exists $opt{'compat-version'}) {
-  my($r,$v,$s) = eval { parse_version($opt{'compat-version'}) };
-  if ($@) {
-    die "Invalid version number format: '$opt{'compat-version'}'\n";
-  }
-  die "Only Perl 5 is supported\n" if $r != 5;
-  die "Invalid version number: $opt{'compat-version'}\n" if $v >= 1000 || $s >= 1000;
-  $opt{'compat-version'} = sprintf "%d.%03d%03d", $r, $v, $s;
-}
-else {
-  $opt{'compat-version'} = 5;
-}
-
-my %API = map { /^(\w+)\|([^|]*)\|([^|]*)\|(\w*)$/
-                ? ( $1 => {
-                      ($2                  ? ( base     => $2 ) : ()),
-                      ($3                  ? ( todo     => $3 ) : ()),
-                      (index($4, 'v') >= 0 ? ( varargs  => 1  ) : ()),
-                      (index($4, 'p') >= 0 ? ( provided => 1  ) : ()),
-                      (index($4, 'n') >= 0 ? ( nothxarg => 1  ) : ()),
-                    } )
-                : die "invalid spec: $_" } qw(
-AvFILLp|5.004050||p
-AvFILL|||
-CLASS|||n
-CPERLscope|5.005000||p
-CX_CURPAD_SAVE|||
-CX_CURPAD_SV|||
-CopFILEAV|5.006000||p
-CopFILEGV_set|5.006000||p
-CopFILEGV|5.006000||p
-CopFILESV|5.006000||p
-CopFILE_set|5.006000||p
-CopFILE|5.006000||p
-CopSTASHPV_set|5.006000||p
-CopSTASHPV|5.006000||p
-CopSTASH_eq|5.006000||p
-CopSTASH_set|5.006000||p
-CopSTASH|5.006000||p
-CopyD|5.009002||p
-Copy|||
-CvPADLIST|||
-CvSTASH|||
-CvWEAKOUTSIDE|||
-DEFSV_set|5.011000||p
-DEFSV|5.004050||p
-END_EXTERN_C|5.005000||p
-ENTER|||
-ERRSV|5.004050||p
-EXTEND|||
-EXTERN_C|5.005000||p
-F0convert|||n
-FREETMPS|||
-GIMME_V||5.004000|n
-GIMME|||n
-GROK_NUMERIC_RADIX|5.007002||p
-G_ARRAY|||
-G_DISCARD|||
-G_EVAL|||
-G_METHOD|5.006001||p
-G_NOARGS|||
-G_SCALAR|||
-G_VOID||5.004000|
-GetVars|||
-GvSVn|5.009003||p
-GvSV|||
-Gv_AMupdate|||
-HEf_SVKEY||5.004000|
-HeHASH||5.004000|
-HeKEY||5.004000|
-HeKLEN||5.004000|
-HePV||5.004000|
-HeSVKEY_force||5.004000|
-HeSVKEY_set||5.004000|
-HeSVKEY||5.004000|
-HeUTF8||5.011000|
-HeVAL||5.004000|
-HvNAMELEN_get|5.009003||p
-HvNAME_get|5.009003||p
-HvNAME|||
-INT2PTR|5.006000||p
-IN_LOCALE_COMPILETIME|5.007002||p
-IN_LOCALE_RUNTIME|5.007002||p
-IN_LOCALE|5.007002||p
-IN_PERL_COMPILETIME|5.008001||p
-IS_NUMBER_GREATER_THAN_UV_MAX|5.007002||p
-IS_NUMBER_INFINITY|5.007002||p
-IS_NUMBER_IN_UV|5.007002||p
-IS_NUMBER_NAN|5.007003||p
-IS_NUMBER_NEG|5.007002||p
-IS_NUMBER_NOT_INT|5.007002||p
-IVSIZE|5.006000||p
-IVTYPE|5.006000||p
-IVdf|5.006000||p
-LEAVE|||
-LVRET|||
-MARK|||
-MULTICALL||5.011000|
-MY_CXT_CLONE|5.009002||p
-MY_CXT_INIT|5.007003||p
-MY_CXT|5.007003||p
-MoveD|5.009002||p
-Move|||
-NOOP|5.005000||p
-NUM2PTR|5.006000||p
-NVTYPE|5.006000||p
-NVef|5.006001||p
-NVff|5.006001||p
-NVgf|5.006001||p
-Newxc|5.009003||p
-Newxz|5.009003||p
-Newx|5.009003||p
-Nullav|||
-Nullch|||
-Nullcv|||
-Nullhv|||
-Nullsv|||
-ORIGMARK|||
-PAD_BASE_SV|||
-PAD_CLONE_VARS|||
-PAD_COMPNAME_FLAGS|||
-PAD_COMPNAME_GEN_set|||
-PAD_COMPNAME_GEN|||
-PAD_COMPNAME_OURSTASH|||
-PAD_COMPNAME_PV|||
-PAD_COMPNAME_TYPE|||
-PAD_DUP|||
-PAD_RESTORE_LOCAL|||
-PAD_SAVE_LOCAL|||
-PAD_SAVE_SETNULLPAD|||
-PAD_SETSV|||
-PAD_SET_CUR_NOSAVE|||
-PAD_SET_CUR|||
-PAD_SVl|||
-PAD_SV|||
-PERLIO_FUNCS_CAST|5.009003||p
-PERLIO_FUNCS_DECL|5.009003||p
-PERL_ABS|5.008001||p
-PERL_BCDVERSION|5.011000||p
-PERL_GCC_BRACE_GROUPS_FORBIDDEN|5.008001||p
-PERL_HASH|5.004000||p
-PERL_INT_MAX|5.004000||p
-PERL_INT_MIN|5.004000||p
-PERL_LONG_MAX|5.004000||p
-PERL_LONG_MIN|5.004000||p
-PERL_MAGIC_arylen|5.007002||p
-PERL_MAGIC_backref|5.007002||p
-PERL_MAGIC_bm|5.007002||p
-PERL_MAGIC_collxfrm|5.007002||p
-PERL_MAGIC_dbfile|5.007002||p
-PERL_MAGIC_dbline|5.007002||p
-PERL_MAGIC_defelem|5.007002||p
-PERL_MAGIC_envelem|5.007002||p
-PERL_MAGIC_env|5.007002||p
-PERL_MAGIC_ext|5.007002||p
-PERL_MAGIC_fm|5.007002||p
-PERL_MAGIC_glob|5.011000||p
-PERL_MAGIC_isaelem|5.007002||p
-PERL_MAGIC_isa|5.007002||p
-PERL_MAGIC_mutex|5.011000||p
-PERL_MAGIC_nkeys|5.007002||p
-PERL_MAGIC_overload_elem|5.007002||p
-PERL_MAGIC_overload_table|5.007002||p
-PERL_MAGIC_overload|5.007002||p
-PERL_MAGIC_pos|5.007002||p
-PERL_MAGIC_qr|5.007002||p
-PERL_MAGIC_regdata|5.007002||p
-PERL_MAGIC_regdatum|5.007002||p
-PERL_MAGIC_regex_global|5.007002||p
-PERL_MAGIC_shared_scalar|5.007003||p
-PERL_MAGIC_shared|5.007003||p
-PERL_MAGIC_sigelem|5.007002||p
-PERL_MAGIC_sig|5.007002||p
-PERL_MAGIC_substr|5.007002||p
-PERL_MAGIC_sv|5.007002||p
-PERL_MAGIC_taint|5.007002||p
-PERL_MAGIC_tiedelem|5.007002||p
-PERL_MAGIC_tiedscalar|5.007002||p
-PERL_MAGIC_tied|5.007002||p
-PERL_MAGIC_utf8|5.008001||p
-PERL_MAGIC_uvar_elem|5.007003||p
-PERL_MAGIC_uvar|5.007002||p
-PERL_MAGIC_vec|5.007002||p
-PERL_MAGIC_vstring|5.008001||p
-PERL_PV_ESCAPE_ALL|5.009004||p
-PERL_PV_ESCAPE_FIRSTCHAR|5.009004||p
-PERL_PV_ESCAPE_NOBACKSLASH|5.009004||p
-PERL_PV_ESCAPE_NOCLEAR|5.009004||p
-PERL_PV_ESCAPE_QUOTE|5.009004||p
-PERL_PV_ESCAPE_RE|5.009005||p
-PERL_PV_ESCAPE_UNI_DETECT|5.009004||p
-PERL_PV_ESCAPE_UNI|5.009004||p
-PERL_PV_PRETTY_DUMP|5.009004||p
-PERL_PV_PRETTY_ELLIPSES|5.010000||p
-PERL_PV_PRETTY_LTGT|5.009004||p
-PERL_PV_PRETTY_NOCLEAR|5.010000||p
-PERL_PV_PRETTY_QUOTE|5.009004||p
-PERL_PV_PRETTY_REGPROP|5.009004||p
-PERL_QUAD_MAX|5.004000||p
-PERL_QUAD_MIN|5.004000||p
-PERL_REVISION|5.006000||p
-PERL_SCAN_ALLOW_UNDERSCORES|5.007003||p
-PERL_SCAN_DISALLOW_PREFIX|5.007003||p
-PERL_SCAN_GREATER_THAN_UV_MAX|5.007003||p
-PERL_SCAN_SILENT_ILLDIGIT|5.008001||p
-PERL_SHORT_MAX|5.004000||p
-PERL_SHORT_MIN|5.004000||p
-PERL_SIGNALS_UNSAFE_FLAG|5.008001||p
-PERL_SUBVERSION|5.006000||p
-PERL_SYS_INIT3||5.006000|
-PERL_SYS_INIT|||
-PERL_SYS_TERM||5.011000|
-PERL_UCHAR_MAX|5.004000||p
-PERL_UCHAR_MIN|5.004000||p
-PERL_UINT_MAX|5.004000||p
-PERL_UINT_MIN|5.004000||p
-PERL_ULONG_MAX|5.004000||p
-PERL_ULONG_MIN|5.004000||p
-PERL_UNUSED_ARG|5.009003||p
-PERL_UNUSED_CONTEXT|5.009004||p
-PERL_UNUSED_DECL|5.007002||p
-PERL_UNUSED_VAR|5.007002||p
-PERL_UQUAD_MAX|5.004000||p
-PERL_UQUAD_MIN|5.004000||p
-PERL_USE_GCC_BRACE_GROUPS|5.009004||p
-PERL_USHORT_MAX|5.004000||p
-PERL_USHORT_MIN|5.004000||p
-PERL_VERSION|5.006000||p
-PL_DBsignal|5.005000||p
-PL_DBsingle|||pn
-PL_DBsub|||pn
-PL_DBtrace|||pn
-PL_Sv|5.005000||p
-PL_bufend|5.011000||p
-PL_bufptr|5.011000||p
-PL_compiling|5.004050||p
-PL_copline|5.011000||p
-PL_curcop|5.004050||p
-PL_curstash|5.004050||p
-PL_debstash|5.004050||p
-PL_defgv|5.004050||p
-PL_diehook|5.004050||p
-PL_dirty|5.004050||p
-PL_dowarn|||pn
-PL_errgv|5.004050||p
-PL_error_count|5.011000||p
-PL_expect|5.011000||p
-PL_hexdigit|5.005000||p
-PL_hints|5.005000||p
-PL_in_my_stash|5.011000||p
-PL_in_my|5.011000||p
-PL_last_in_gv|||n
-PL_laststatval|5.005000||p
-PL_lex_state|5.011000||p
-PL_lex_stuff|5.011000||p
-PL_linestr|5.011000||p
-PL_modglobal||5.005000|n
-PL_na|5.004050||pn
-PL_no_modify|5.006000||p
-PL_ofsgv|||n
-PL_parser|5.009005||p
-PL_perl_destruct_level|5.004050||p
-PL_perldb|5.004050||p
-PL_ppaddr|5.006000||p
-PL_rsfp_filters|5.004050||p
-PL_rsfp|5.004050||p
-PL_rs|||n
-PL_signals|5.008001||p
-PL_stack_base|5.004050||p
-PL_stack_sp|5.004050||p
-PL_statcache|5.005000||p
-PL_stdingv|5.004050||p
-PL_sv_arenaroot|5.004050||p
-PL_sv_no|5.004050||pn
-PL_sv_undef|5.004050||pn
-PL_sv_yes|5.004050||pn
-PL_tainted|5.004050||p
-PL_tainting|5.004050||p
-PL_tokenbuf|5.011000||p
-POP_MULTICALL||5.011000|
-POPi|||n
-POPl|||n
-POPn|||n
-POPpbytex||5.007001|n
-POPpx||5.005030|n
-POPp|||n
-POPs|||n
-PTR2IV|5.006000||p
-PTR2NV|5.006000||p
-PTR2UV|5.006000||p
-PTR2nat|5.009003||p
-PTR2ul|5.007001||p
-PTRV|5.006000||p
-PUSHMARK|||
-PUSH_MULTICALL||5.011000|
-PUSHi|||
-PUSHmortal|5.009002||p
-PUSHn|||
-PUSHp|||
-PUSHs|||
-PUSHu|5.004000||p
-PUTBACK|||
-PerlIO_clearerr||5.007003|
-PerlIO_close||5.007003|
-PerlIO_context_layers||5.009004|
-PerlIO_eof||5.007003|
-PerlIO_error||5.007003|
-PerlIO_fileno||5.007003|
-PerlIO_fill||5.007003|
-PerlIO_flush||5.007003|
-PerlIO_get_base||5.007003|
-PerlIO_get_bufsiz||5.007003|
-PerlIO_get_cnt||5.007003|
-PerlIO_get_ptr||5.007003|
-PerlIO_read||5.007003|
-PerlIO_seek||5.007003|
-PerlIO_set_cnt||5.007003|
-PerlIO_set_ptrcnt||5.007003|
-PerlIO_setlinebuf||5.007003|
-PerlIO_stderr||5.007003|
-PerlIO_stdin||5.007003|
-PerlIO_stdout||5.007003|
-PerlIO_tell||5.007003|
-PerlIO_unread||5.007003|
-PerlIO_write||5.007003|
-Perl_signbit||5.009005|n
-PoisonFree|5.009004||p
-PoisonNew|5.009004||p
-PoisonWith|5.009004||p
-Poison|5.008000||p
-RETVAL|||n
-Renewc|||
-Renew|||
-SAVECLEARSV|||
-SAVECOMPPAD|||
-SAVEPADSV|||
-SAVETMPS|||
-SAVE_DEFSV|5.004050||p
-SPAGAIN|||
-SP|||
-START_EXTERN_C|5.005000||p
-START_MY_CXT|5.007003||p
-STMT_END|||p
-STMT_START|||p
-STR_WITH_LEN|5.009003||p
-ST|||
-SV_CONST_RETURN|5.009003||p
-SV_COW_DROP_PV|5.008001||p
-SV_COW_SHARED_HASH_KEYS|5.009005||p
-SV_GMAGIC|5.007002||p
-SV_HAS_TRAILING_NUL|5.009004||p
-SV_IMMEDIATE_UNREF|5.007001||p
-SV_MUTABLE_RETURN|5.009003||p
-SV_NOSTEAL|5.009002||p
-SV_SMAGIC|5.009003||p
-SV_UTF8_NO_ENCODING|5.008001||p
-SVfARG|5.009005||p
-SVf_UTF8|5.006000||p
-SVf|5.006000||p
-SVt_IV|||
-SVt_NV|||
-SVt_PVAV|||
-SVt_PVCV|||
-SVt_PVHV|||
-SVt_PVMG|||
-SVt_PV|||
-Safefree|||
-Slab_Alloc|||
-Slab_Free|||
-Slab_to_rw|||
-StructCopy|||
-SvCUR_set|||
-SvCUR|||
-SvEND|||
-SvGAMAGIC||5.006001|
-SvGETMAGIC|5.004050||p
-SvGROW|||
-SvIOK_UV||5.006000|
-SvIOK_notUV||5.006000|
-SvIOK_off|||
-SvIOK_only_UV||5.006000|
-SvIOK_only|||
-SvIOK_on|||
-SvIOKp|||
-SvIOK|||
-SvIVX|||
-SvIV_nomg|5.009001||p
-SvIV_set|||
-SvIVx|||
-SvIV|||
-SvIsCOW_shared_hash||5.008003|
-SvIsCOW||5.008003|
-SvLEN_set|||
-SvLEN|||
-SvLOCK||5.007003|
-SvMAGIC_set|5.009003||p
-SvNIOK_off|||
-SvNIOKp|||
-SvNIOK|||
-SvNOK_off|||
-SvNOK_only|||
-SvNOK_on|||
-SvNOKp|||
-SvNOK|||
-SvNVX|||
-SvNV_set|||
-SvNVx|||
-SvNV|||
-SvOK|||
-SvOOK_offset||5.011000|
-SvOOK|||
-SvPOK_off|||
-SvPOK_only_UTF8||5.006000|
-SvPOK_only|||
-SvPOK_on|||
-SvPOKp|||
-SvPOK|||
-SvPVX_const|5.009003||p
-SvPVX_mutable|5.009003||p
-SvPVX|||
-SvPV_const|5.009003||p
-SvPV_flags_const_nolen|5.009003||p
-SvPV_flags_const|5.009003||p
-SvPV_flags_mutable|5.009003||p
-SvPV_flags|5.007002||p
-SvPV_force_flags_mutable|5.009003||p
-SvPV_force_flags_nolen|5.009003||p
-SvPV_force_flags|5.007002||p
-SvPV_force_mutable|5.009003||p
-SvPV_force_nolen|5.009003||p
-SvPV_force_nomg_nolen|5.009003||p
-SvPV_force_nomg|5.007002||p
-SvPV_force|||p
-SvPV_mutable|5.009003||p
-SvPV_nolen_const|5.009003||p
-SvPV_nolen|5.006000||p
-SvPV_nomg_const_nolen|5.009003||p
-SvPV_nomg_const|5.009003||p
-SvPV_nomg|5.007002||p
-SvPV_renew|5.009003||p
-SvPV_set|||
-SvPVbyte_force||5.009002|
-SvPVbyte_nolen||5.006000|
-SvPVbytex_force||5.006000|
-SvPVbytex||5.006000|
-SvPVbyte|5.006000||p
-SvPVutf8_force||5.006000|
-SvPVutf8_nolen||5.006000|
-SvPVutf8x_force||5.006000|
-SvPVutf8x||5.006000|
-SvPVutf8||5.006000|
-SvPVx|||
-SvPV|||
-SvREFCNT_dec|||
-SvREFCNT_inc_NN|5.009004||p
-SvREFCNT_inc_simple_NN|5.009004||p
-SvREFCNT_inc_simple_void_NN|5.009004||p
-SvREFCNT_inc_simple_void|5.009004||p
-SvREFCNT_inc_simple|5.009004||p
-SvREFCNT_inc_void_NN|5.009004||p
-SvREFCNT_inc_void|5.009004||p
-SvREFCNT_inc|||p
-SvREFCNT|||
-SvROK_off|||
-SvROK_on|||
-SvROK|||
-SvRV_set|5.009003||p
-SvRV|||
-SvRXOK||5.009005|
-SvRX||5.009005|
-SvSETMAGIC|||
-SvSHARED_HASH|5.009003||p
-SvSHARE||5.007003|
-SvSTASH_set|5.009003||p
-SvSTASH|||
-SvSetMagicSV_nosteal||5.004000|
-SvSetMagicSV||5.004000|
-SvSetSV_nosteal||5.004000|
-SvSetSV|||
-SvTAINTED_off||5.004000|
-SvTAINTED_on||5.004000|
-SvTAINTED||5.004000|
-SvTAINT|||
-SvTRUE|||
-SvTYPE|||
-SvUNLOCK||5.007003|
-SvUOK|5.007001|5.006000|p
-SvUPGRADE|||
-SvUTF8_off||5.006000|
-SvUTF8_on||5.006000|
-SvUTF8||5.006000|
-SvUVXx|5.004000||p
-SvUVX|5.004000||p
-SvUV_nomg|5.009001||p
-SvUV_set|5.009003||p
-SvUVx|5.004000||p
-SvUV|5.004000||p
-SvVOK||5.008001|
-SvVSTRING_mg|5.009004||p
-THIS|||n
-UNDERBAR|5.009002||p
-UTF8_MAXBYTES|5.009002||p
-UVSIZE|5.006000||p
-UVTYPE|5.006000||p
-UVXf|5.007001||p
-UVof|5.006000||p
-UVuf|5.006000||p
-UVxf|5.006000||p
-WARN_ALL|5.006000||p
-WARN_AMBIGUOUS|5.006000||p
-WARN_ASSERTIONS|5.011000||p
-WARN_BAREWORD|5.006000||p
-WARN_CLOSED|5.006000||p
-WARN_CLOSURE|5.006000||p
-WARN_DEBUGGING|5.006000||p
-WARN_DEPRECATED|5.006000||p
-WARN_DIGIT|5.006000||p
-WARN_EXEC|5.006000||p
-WARN_EXITING|5.006000||p
-WARN_GLOB|5.006000||p
-WARN_INPLACE|5.006000||p
-WARN_INTERNAL|5.006000||p
-WARN_IO|5.006000||p
-WARN_LAYER|5.008000||p
-WARN_MALLOC|5.006000||p
-WARN_MISC|5.006000||p
-WARN_NEWLINE|5.006000||p
-WARN_NUMERIC|5.006000||p
-WARN_ONCE|5.006000||p
-WARN_OVERFLOW|5.006000||p
-WARN_PACK|5.006000||p
-WARN_PARENTHESIS|5.006000||p
-WARN_PIPE|5.006000||p
-WARN_PORTABLE|5.006000||p
-WARN_PRECEDENCE|5.006000||p
-WARN_PRINTF|5.006000||p
-WARN_PROTOTYPE|5.006000||p
-WARN_QW|5.006000||p
-WARN_RECURSION|5.006000||p
-WARN_REDEFINE|5.006000||p
-WARN_REGEXP|5.006000||p
-WARN_RESERVED|5.006000||p
-WARN_SEMICOLON|5.006000||p
-WARN_SEVERE|5.006000||p
-WARN_SIGNAL|5.006000||p
-WARN_SUBSTR|5.006000||p
-WARN_SYNTAX|5.006000||p
-WARN_TAINT|5.006000||p
-WARN_THREADS|5.008000||p
-WARN_UNINITIALIZED|5.006000||p
-WARN_UNOPENED|5.006000||p
-WARN_UNPACK|5.006000||p
-WARN_UNTIE|5.006000||p
-WARN_UTF8|5.006000||p
-WARN_VOID|5.006000||p
-XCPT_CATCH|5.009002||p
-XCPT_RETHROW|5.009002||p
-XCPT_TRY_END|5.009002||p
-XCPT_TRY_START|5.009002||p
-XPUSHi|||
-XPUSHmortal|5.009002||p
-XPUSHn|||
-XPUSHp|||
-XPUSHs|||
-XPUSHu|5.004000||p
-XSPROTO|5.010000||p
-XSRETURN_EMPTY|||
-XSRETURN_IV|||
-XSRETURN_NO|||
-XSRETURN_NV|||
-XSRETURN_PV|||
-XSRETURN_UNDEF|||
-XSRETURN_UV|5.008001||p
-XSRETURN_YES|||
-XSRETURN|||p
-XST_mIV|||
-XST_mNO|||
-XST_mNV|||
-XST_mPV|||
-XST_mUNDEF|||
-XST_mUV|5.008001||p
-XST_mYES|||
-XS_VERSION_BOOTCHECK|||
-XS_VERSION|||
-XSprePUSH|5.006000||p
-XS|||
-ZeroD|5.009002||p
-Zero|||
-_aMY_CXT|5.007003||p
-_pMY_CXT|5.007003||p
-aMY_CXT_|5.007003||p
-aMY_CXT|5.007003||p
-aTHXR_|5.011000||p
-aTHXR|5.011000||p
-aTHX_|5.006000||p
-aTHX|5.006000||p
-add_data|||n
-addmad|||
-allocmy|||
-amagic_call|||
-amagic_cmp_locale|||
-amagic_cmp|||
-amagic_i_ncmp|||
-amagic_ncmp|||
-any_dup|||
-ao|||
-append_elem|||
-append_list|||
-append_madprops|||
-apply_attrs_my|||
-apply_attrs_string||5.006001|
-apply_attrs|||
-apply|||
-atfork_lock||5.007003|n
-atfork_unlock||5.007003|n
-av_arylen_p||5.009003|
-av_clear|||
-av_create_and_push||5.009005|
-av_create_and_unshift_one||5.009005|
-av_delete||5.006000|
-av_exists||5.006000|
-av_extend|||
-av_fetch|||
-av_fill|||
-av_iter_p||5.011000|
-av_len|||
-av_make|||
-av_pop|||
-av_push|||
-av_reify|||
-av_shift|||
-av_store|||
-av_undef|||
-av_unshift|||
-ax|||n
-bad_type|||
-bind_match|||
-block_end|||
-block_gimme||5.004000|
-block_start|||
-boolSV|5.004000||p
-boot_core_PerlIO|||
-boot_core_UNIVERSAL|||
-boot_core_mro|||
-bytes_from_utf8||5.007001|
-bytes_to_uni|||n
-bytes_to_utf8||5.006001|
-call_argv|5.006000||p
-call_atexit||5.006000|
-call_list||5.004000|
-call_method|5.006000||p
-call_pv|5.006000||p
-call_sv|5.006000||p
-calloc||5.007002|n
-cando|||
-cast_i32||5.006000|
-cast_iv||5.006000|
-cast_ulong||5.006000|
-cast_uv||5.006000|
-check_type_and_open|||
-check_uni|||
-checkcomma|||
-checkposixcc|||
-ckWARN|5.006000||p
-ck_anoncode|||
-ck_bitop|||
-ck_concat|||
-ck_defined|||
-ck_delete|||
-ck_die|||
-ck_each|||
-ck_eof|||
-ck_eval|||
-ck_exec|||
-ck_exists|||
-ck_exit|||
-ck_ftst|||
-ck_fun|||
-ck_glob|||
-ck_grep|||
-ck_index|||
-ck_join|||
-ck_lfun|||
-ck_listiob|||
-ck_match|||
-ck_method|||
-ck_null|||
-ck_open|||
-ck_readline|||
-ck_repeat|||
-ck_require|||
-ck_return|||
-ck_rfun|||
-ck_rvconst|||
-ck_sassign|||
-ck_select|||
-ck_shift|||
-ck_sort|||
-ck_spair|||
-ck_split|||
-ck_subr|||
-ck_substr|||
-ck_svconst|||
-ck_trunc|||
-ck_unpack|||
-ckwarn_d||5.009003|
-ckwarn||5.009003|
-cl_and|||n
-cl_anything|||n
-cl_init_zero|||n
-cl_init|||n
-cl_is_anything|||n
-cl_or|||n
-clear_placeholders|||
-closest_cop|||
-convert|||
-cop_free|||
-cr_textfilter|||
-create_eval_scope|||
-croak_nocontext|||vn
-croak_xs_usage||5.011000|
-croak|||v
-csighandler||5.009003|n
-curmad|||
-custom_op_desc||5.007003|
-custom_op_name||5.007003|
-cv_ckproto_len|||
-cv_clone|||
-cv_const_sv||5.004000|
-cv_dump|||
-cv_undef|||
-cx_dump||5.005000|
-cx_dup|||
-cxinc|||
-dAXMARK|5.009003||p
-dAX|5.007002||p
-dITEMS|5.007002||p
-dMARK|||
-dMULTICALL||5.009003|
-dMY_CXT_SV|5.007003||p
-dMY_CXT|5.007003||p
-dNOOP|5.006000||p
-dORIGMARK|||
-dSP|||
-dTHR|5.004050||p
-dTHXR|5.011000||p
-dTHXa|5.006000||p
-dTHXoa|5.006000||p
-dTHX|5.006000||p
-dUNDERBAR|5.009002||p
-dVAR|5.009003||p
-dXCPT|5.009002||p
-dXSARGS|||
-dXSI32|||
-dXSTARG|5.006000||p
-deb_curcv|||
-deb_nocontext|||vn
-deb_stack_all|||
-deb_stack_n|||
-debop||5.005000|
-debprofdump||5.005000|
-debprof|||
-debstackptrs||5.007003|
-debstack||5.007003|
-debug_start_match|||
-deb||5.007003|v
-del_sv|||
-delete_eval_scope|||
-delimcpy||5.004000|
-deprecate_old|||
-deprecate|||
-despatch_signals||5.007001|
-destroy_matcher|||
-die_nocontext|||vn
-die_where|||
-die|||v
-dirp_dup|||
-div128|||
-djSP|||
-do_aexec5|||
-do_aexec|||
-do_aspawn|||
-do_binmode||5.004050|
-do_chomp|||
-do_chop|||
-do_close|||
-do_dump_pad|||
-do_eof|||
-do_exec3|||
-do_execfree|||
-do_exec|||
-do_gv_dump||5.006000|
-do_gvgv_dump||5.006000|
-do_hv_dump||5.006000|
-do_ipcctl|||
-do_ipcget|||
-do_join|||
-do_kv|||
-do_magic_dump||5.006000|
-do_msgrcv|||
-do_msgsnd|||
-do_oddball|||
-do_op_dump||5.006000|
-do_op_xmldump|||
-do_open9||5.006000|
-do_openn||5.007001|
-do_open||5.004000|
-do_pmop_dump||5.006000|
-do_pmop_xmldump|||
-do_print|||
-do_readline|||
-do_seek|||
-do_semop|||
-do_shmio|||
-do_smartmatch|||
-do_spawn_nowait|||
-do_spawn|||
-do_sprintf|||
-do_sv_dump||5.006000|
-do_sysseek|||
-do_tell|||
-do_trans_complex_utf8|||
-do_trans_complex|||
-do_trans_count_utf8|||
-do_trans_count|||
-do_trans_simple_utf8|||
-do_trans_simple|||
-do_trans|||
-do_vecget|||
-do_vecset|||
-do_vop|||
-docatch|||
-doeval|||
-dofile|||
-dofindlabel|||
-doform|||
-doing_taint||5.008001|n
-dooneliner|||
-doopen_pm|||
-doparseform|||
-dopoptoeval|||
-dopoptogiven|||
-dopoptolabel|||
-dopoptoloop|||
-dopoptosub_at|||
-dopoptowhen|||
-doref||5.009003|
-dounwind|||
-dowantarray|||
-dump_all||5.006000|
-dump_eval||5.006000|
-dump_exec_pos|||
-dump_fds|||
-dump_form||5.006000|
-dump_indent||5.006000|v
-dump_mstats|||
-dump_packsubs||5.006000|
-dump_sub||5.006000|
-dump_sv_child|||
-dump_trie_interim_list|||
-dump_trie_interim_table|||
-dump_trie|||
-dump_vindent||5.006000|
-dumpuntil|||
-dup_attrlist|||
-emulate_cop_io|||
-eval_pv|5.006000||p
-eval_sv|5.006000||p
-exec_failed|||
-expect_number|||
-fbm_compile||5.005000|
-fbm_instr||5.005000|
-feature_is_enabled|||
-fetch_cop_label||5.011000|
-filter_add|||
-filter_del|||
-filter_gets|||
-filter_read|||
-find_and_forget_pmops|||
-find_array_subscript|||
-find_beginning|||
-find_byclass|||
-find_hash_subscript|||
-find_in_my_stash|||
-find_runcv||5.008001|
-find_rundefsvoffset||5.009002|
-find_script|||
-find_uninit_var|||
-first_symbol|||n
-fold_constants|||
-forbid_setid|||
-force_ident|||
-force_list|||
-force_next|||
-force_version|||
-force_word|||
-forget_pmop|||
-form_nocontext|||vn
-form||5.004000|v
-fp_dup|||
-fprintf_nocontext|||vn
-free_global_struct|||
-free_tied_hv_pool|||
-free_tmps|||
-gen_constant_list|||
-get_arena|||
-get_aux_mg|||
-get_av|5.006000||p
-get_context||5.006000|n
-get_cvn_flags||5.009005|
-get_cv|5.006000||p
-get_db_sub|||
-get_debug_opts|||
-get_hash_seed|||
-get_hv|5.006000||p
-get_isa_hash|||
-get_mstats|||
-get_no_modify|||
-get_num|||
-get_op_descs||5.005000|
-get_op_names||5.005000|
-get_opargs|||
-get_ppaddr||5.006000|
-get_re_arg|||
-get_sv|5.006000||p
-get_vtbl||5.005030|
-getcwd_sv||5.007002|
-getenv_len|||
-glob_2number|||
-glob_assign_glob|||
-glob_assign_ref|||
-gp_dup|||
-gp_free|||
-gp_ref|||
-grok_bin|5.007003||p
-grok_hex|5.007003||p
-grok_number|5.007002||p
-grok_numeric_radix|5.007002||p
-grok_oct|5.007003||p
-group_end|||
-gv_AVadd|||
-gv_HVadd|||
-gv_IOadd|||
-gv_SVadd|||
-gv_autoload4||5.004000|
-gv_check|||
-gv_const_sv||5.009003|
-gv_dump||5.006000|
-gv_efullname3||5.004000|
-gv_efullname4||5.006001|
-gv_efullname|||
-gv_ename|||
-gv_fetchfile_flags||5.009005|
-gv_fetchfile|||
-gv_fetchmeth_autoload||5.007003|
-gv_fetchmethod_autoload||5.004000|
-gv_fetchmethod_flags||5.011000|
-gv_fetchmethod|||
-gv_fetchmeth|||
-gv_fetchpvn_flags|5.009002||p
-gv_fetchpvs|5.009004||p
-gv_fetchpv|||
-gv_fetchsv||5.009002|
-gv_fullname3||5.004000|
-gv_fullname4||5.006001|
-gv_fullname|||
-gv_get_super_pkg|||
-gv_handler||5.007001|
-gv_init_sv|||
-gv_init|||
-gv_name_set||5.009004|
-gv_stashpvn|5.004000||p
-gv_stashpvs|5.009003||p
-gv_stashpv|||
-gv_stashsv|||
-he_dup|||
-hek_dup|||
-hfreeentries|||
-hsplit|||
-hv_assert||5.011000|
-hv_auxinit|||n
-hv_backreferences_p|||
-hv_clear_placeholders||5.009001|
-hv_clear|||
-hv_common_key_len||5.010000|
-hv_common||5.010000|
-hv_copy_hints_hv|||
-hv_delayfree_ent||5.004000|
-hv_delete_common|||
-hv_delete_ent||5.004000|
-hv_delete|||
-hv_eiter_p||5.009003|
-hv_eiter_set||5.009003|
-hv_exists_ent||5.004000|
-hv_exists|||
-hv_fetch_ent||5.004000|
-hv_fetchs|5.009003||p
-hv_fetch|||
-hv_free_ent||5.004000|
-hv_iterinit|||
-hv_iterkeysv||5.004000|
-hv_iterkey|||
-hv_iternext_flags||5.008000|
-hv_iternextsv|||
-hv_iternext|||
-hv_iterval|||
-hv_kill_backrefs|||
-hv_ksplit||5.004000|
-hv_magic_check|||n
-hv_magic|||
-hv_name_set||5.009003|
-hv_notallowed|||
-hv_placeholders_get||5.009003|
-hv_placeholders_p||5.009003|
-hv_placeholders_set||5.009003|
-hv_riter_p||5.009003|
-hv_riter_set||5.009003|
-hv_scalar||5.009001|
-hv_store_ent||5.004000|
-hv_store_flags||5.008000|
-hv_stores|5.009004||p
-hv_store|||
-hv_undef|||
-ibcmp_locale||5.004000|
-ibcmp_utf8||5.007003|
-ibcmp|||
-incline|||
-incpush_if_exists|||
-incpush_use_sep|||
-incpush|||
-ingroup|||
-init_argv_symbols|||
-init_debugger|||
-init_global_struct|||
-init_i18nl10n||5.006000|
-init_i18nl14n||5.006000|
-init_ids|||
-init_interp|||
-init_main_stash|||
-init_perllib|||
-init_postdump_symbols|||
-init_predump_symbols|||
-init_stacks||5.005000|
-init_tm||5.007002|
-instr|||
-intro_my|||
-intuit_method|||
-intuit_more|||
-invert|||
-io_close|||
-isALNUMC|5.006000||p
-isALNUM|||
-isALPHA|||
-isASCII|5.006000||p
-isBLANK|5.006001||p
-isCNTRL|5.006000||p
-isDIGIT|||
-isGRAPH|5.006000||p
-isGV_with_GP|5.009004||p
-isLOWER|||
-isPRINT|5.004000||p
-isPSXSPC|5.006001||p
-isPUNCT|5.006000||p
-isSPACE|||
-isUPPER|||
-isXDIGIT|5.006000||p
-is_an_int|||
-is_gv_magical_sv|||
-is_handle_constructor|||n
-is_list_assignment|||
-is_lvalue_sub||5.007001|
-is_uni_alnum_lc||5.006000|
-is_uni_alnumc_lc||5.006000|
-is_uni_alnumc||5.006000|
-is_uni_alnum||5.006000|
-is_uni_alpha_lc||5.006000|
-is_uni_alpha||5.006000|
-is_uni_ascii_lc||5.006000|
-is_uni_ascii||5.006000|
-is_uni_cntrl_lc||5.006000|
-is_uni_cntrl||5.006000|
-is_uni_digit_lc||5.006000|
-is_uni_digit||5.006000|
-is_uni_graph_lc||5.006000|
-is_uni_graph||5.006000|
-is_uni_idfirst_lc||5.006000|
-is_uni_idfirst||5.006000|
-is_uni_lower_lc||5.006000|
-is_uni_lower||5.006000|
-is_uni_print_lc||5.006000|
-is_uni_print||5.006000|
-is_uni_punct_lc||5.006000|
-is_uni_punct||5.006000|
-is_uni_space_lc||5.006000|
-is_uni_space||5.006000|
-is_uni_upper_lc||5.006000|
-is_uni_upper||5.006000|
-is_uni_xdigit_lc||5.006000|
-is_uni_xdigit||5.006000|
-is_utf8_alnumc||5.006000|
-is_utf8_alnum||5.006000|
-is_utf8_alpha||5.006000|
-is_utf8_ascii||5.006000|
-is_utf8_char_slow|||n
-is_utf8_char||5.006000|
-is_utf8_cntrl||5.006000|
-is_utf8_common|||
-is_utf8_digit||5.006000|
-is_utf8_graph||5.006000|
-is_utf8_idcont||5.008000|
-is_utf8_idfirst||5.006000|
-is_utf8_lower||5.006000|
-is_utf8_mark||5.006000|
-is_utf8_print||5.006000|
-is_utf8_punct||5.006000|
-is_utf8_space||5.006000|
-is_utf8_string_loclen||5.009003|
-is_utf8_string_loc||5.008001|
-is_utf8_string||5.006001|
-is_utf8_upper||5.006000|
-is_utf8_xdigit||5.006000|
-isa_lookup|||
-items|||n
-ix|||n
-jmaybe|||
-join_exact|||
-keyword|||
-leave_scope|||
-lex_end|||
-lex_start|||
-linklist|||
-listkids|||
-list|||
-load_module_nocontext|||vn
-load_module|5.006000||pv
-localize|||
-looks_like_bool|||
-looks_like_number|||
-lop|||
-mPUSHi|5.009002||p
-mPUSHn|5.009002||p
-mPUSHp|5.009002||p
-mPUSHs|5.011000||p
-mPUSHu|5.009002||p
-mXPUSHi|5.009002||p
-mXPUSHn|5.009002||p
-mXPUSHp|5.009002||p
-mXPUSHs|5.011000||p
-mXPUSHu|5.009002||p
-mad_free|||
-madlex|||
-madparse|||
-magic_clear_all_env|||
-magic_clearenv|||
-magic_clearhint|||
-magic_clearisa|||
-magic_clearpack|||
-magic_clearsig|||
-magic_dump||5.006000|
-magic_existspack|||
-magic_freearylen_p|||
-magic_freeovrld|||
-magic_getarylen|||
-magic_getdefelem|||
-magic_getnkeys|||
-magic_getpack|||
-magic_getpos|||
-magic_getsig|||
-magic_getsubstr|||
-magic_gettaint|||
-magic_getuvar|||
-magic_getvec|||
-magic_get|||
-magic_killbackrefs|||
-magic_len|||
-magic_methcall|||
-magic_methpack|||
-magic_nextpack|||
-magic_regdata_cnt|||
-magic_regdatum_get|||
-magic_regdatum_set|||
-magic_scalarpack|||
-magic_set_all_env|||
-magic_setamagic|||
-magic_setarylen|||
-magic_setcollxfrm|||
-magic_setdbline|||
-magic_setdefelem|||
-magic_setenv|||
-magic_sethint|||
-magic_setisa|||
-magic_setmglob|||
-magic_setnkeys|||
-magic_setpack|||
-magic_setpos|||
-magic_setregexp|||
-magic_setsig|||
-magic_setsubstr|||
-magic_settaint|||
-magic_setutf8|||
-magic_setuvar|||
-magic_setvec|||
-magic_set|||
-magic_sizepack|||
-magic_wipepack|||
-make_matcher|||
-make_trie_failtable|||
-make_trie|||
-malloc_good_size|||n
-malloced_size|||n
-malloc||5.007002|n
-markstack_grow|||
-matcher_matches_sv|||
-measure_struct|||
-memEQ|5.004000||p
-memNE|5.004000||p
-mem_collxfrm|||
-mem_log_common|||n
-mess_alloc|||
-mess_nocontext|||vn
-mess||5.006000|v
-method_common|||
-mfree||5.007002|n
-mg_clear|||
-mg_copy|||
-mg_dup|||
-mg_find|||
-mg_free|||
-mg_get|||
-mg_length||5.005000|
-mg_localize|||
-mg_magical|||
-mg_set|||
-mg_size||5.005000|
-mini_mktime||5.007002|
-missingterm|||
-mode_from_discipline|||
-modkids|||
-mod|||
-more_bodies|||
-more_sv|||
-moreswitches|||
-mro_get_from_name||5.011000|
-mro_get_linear_isa_dfs|||
-mro_get_linear_isa||5.009005|
-mro_get_private_data||5.011000|
-mro_isa_changed_in|||
-mro_meta_dup|||
-mro_meta_init|||
-mro_method_changed_in||5.009005|
-mro_register||5.011000|
-mro_set_mro||5.011000|
-mro_set_private_data||5.011000|
-mul128|||
-mulexp10|||n
-my_atof2||5.007002|
-my_atof||5.006000|
-my_attrs|||
-my_bcopy|||n
-my_betoh16|||n
-my_betoh32|||n
-my_betoh64|||n
-my_betohi|||n
-my_betohl|||n
-my_betohs|||n
-my_bzero|||n
-my_chsize|||
-my_clearenv|||
-my_cxt_index|||
-my_cxt_init|||
-my_dirfd||5.009005|
-my_exit_jump|||
-my_exit|||
-my_failure_exit||5.004000|
-my_fflush_all||5.006000|
-my_fork||5.007003|n
-my_htobe16|||n
-my_htobe32|||n
-my_htobe64|||n
-my_htobei|||n
-my_htobel|||n
-my_htobes|||n
-my_htole16|||n
-my_htole32|||n
-my_htole64|||n
-my_htolei|||n
-my_htolel|||n
-my_htoles|||n
-my_htonl|||
-my_kid|||
-my_letoh16|||n
-my_letoh32|||n
-my_letoh64|||n
-my_letohi|||n
-my_letohl|||n
-my_letohs|||n
-my_lstat|||
-my_memcmp||5.004000|n
-my_memset|||n
-my_ntohl|||
-my_pclose||5.004000|
-my_popen_list||5.007001|
-my_popen||5.004000|
-my_setenv|||
-my_snprintf|5.009004||pvn
-my_socketpair||5.007003|n
-my_sprintf|5.009003||pvn
-my_stat|||
-my_strftime||5.007002|
-my_strlcat|5.009004||pn
-my_strlcpy|5.009004||pn
-my_swabn|||n
-my_swap|||
-my_unexec|||
-my_vsnprintf||5.009004|n
-need_utf8|||n
-newANONATTRSUB||5.006000|
-newANONHASH|||
-newANONLIST|||
-newANONSUB|||
-newASSIGNOP|||
-newATTRSUB||5.006000|
-newAVREF|||
-newAV|||
-newBINOP|||
-newCONDOP|||
-newCONSTSUB|5.004050||p
-newCVREF|||
-newDEFSVOP|||
-newFORM|||
-newFOROP|||
-newGIVENOP||5.009003|
-newGIVWHENOP|||
-newGP|||
-newGVOP|||
-newGVREF|||
-newGVgen|||
-newHVREF|||
-newHVhv||5.005000|
-newHV|||
-newIO|||
-newLISTOP|||
-newLOGOP|||
-newLOOPEX|||
-newLOOPOP|||
-newMADPROP|||
-newMADsv|||
-newMYSUB|||
-newNULLLIST|||
-newOP|||
-newPADOP|||
-newPMOP|||
-newPROG|||
-newPVOP|||
-newRANGE|||
-newRV_inc|5.004000||p
-newRV_noinc|5.004000||p
-newRV|||
-newSLICEOP|||
-newSTATEOP|||
-newSUB|||
-newSVOP|||
-newSVREF|||
-newSV_type|5.009005||p
-newSVhek||5.009003|
-newSViv|||
-newSVnv|||
-newSVpvf_nocontext|||vn
-newSVpvf||5.004000|v
-newSVpvn_flags|5.011000||p
-newSVpvn_share|5.007001||p
-newSVpvn_utf8|5.011000||p
-newSVpvn|5.004050||p
-newSVpvs_flags|5.011000||p
-newSVpvs_share||5.009003|
-newSVpvs|5.009003||p
-newSVpv|||
-newSVrv|||
-newSVsv|||
-newSVuv|5.006000||p
-newSV|||
-newTOKEN|||
-newUNOP|||
-newWHENOP||5.009003|
-newWHILEOP||5.009003|
-newXS_flags||5.009004|
-newXSproto||5.006000|
-newXS||5.006000|
-new_collate||5.006000|
-new_constant|||
-new_ctype||5.006000|
-new_he|||
-new_logop|||
-new_numeric||5.006000|
-new_stackinfo||5.005000|
-new_version||5.009000|
-new_warnings_bitfield|||
-next_symbol|||
-nextargv|||
-nextchar|||
-ninstr|||
-no_bareword_allowed|||
-no_fh_allowed|||
-no_op|||
-not_a_number|||
-nothreadhook||5.008000|
-nuke_stacks|||
-num_overflow|||n
-offer_nice_chunk|||
-oopsAV|||
-oopsHV|||
-op_clear|||
-op_const_sv|||
-op_dump||5.006000|
-op_free|||
-op_getmad_weak|||
-op_getmad|||
-op_null||5.007002|
-op_refcnt_dec|||
-op_refcnt_inc|||
-op_refcnt_lock||5.009002|
-op_refcnt_unlock||5.009002|
-op_xmldump|||
-open_script|||
-pMY_CXT_|5.007003||p
-pMY_CXT|5.007003||p
-pTHX_|5.006000||p
-pTHX|5.006000||p
-packWARN|5.007003||p
-pack_cat||5.007003|
-pack_rec|||
-package|||
-packlist||5.008001|
-pad_add_anon|||
-pad_add_name|||
-pad_alloc|||
-pad_block_start|||
-pad_check_dup|||
-pad_compname_type|||
-pad_findlex|||
-pad_findmy|||
-pad_fixup_inner_anons|||
-pad_free|||
-pad_leavemy|||
-pad_new|||
-pad_peg|||n
-pad_push|||
-pad_reset|||
-pad_setsv|||
-pad_sv||5.011000|
-pad_swipe|||
-pad_tidy|||
-pad_undef|||
-parse_body|||
-parse_unicode_opts|||
-parser_dup|||
-parser_free|||
-path_is_absolute|||n
-peep|||
-pending_Slabs_to_ro|||
-perl_alloc_using|||n
-perl_alloc|||n
-perl_clone_using|||n
-perl_clone|||n
-perl_construct|||n
-perl_destruct||5.007003|n
-perl_free|||n
-perl_parse||5.006000|n
-perl_run|||n
-pidgone|||
-pm_description|||
-pmflag|||
-pmop_dump||5.006000|
-pmop_xmldump|||
-pmruntime|||
-pmtrans|||
-pop_scope|||
-pregcomp||5.009005|
-pregexec|||
-pregfree2||5.011000|
-pregfree|||
-prepend_elem|||
-prepend_madprops|||
-printbuf|||
-printf_nocontext|||vn
-process_special_blocks|||
-ptr_table_clear||5.009005|
-ptr_table_fetch||5.009005|
-ptr_table_find|||n
-ptr_table_free||5.009005|
-ptr_table_new||5.009005|
-ptr_table_split||5.009005|
-ptr_table_store||5.009005|
-push_scope|||
-put_byte|||
-pv_display|5.006000||p
-pv_escape|5.009004||p
-pv_pretty|5.009004||p
-pv_uni_display||5.007003|
-qerror|||
-qsortsvu|||
-re_compile||5.009005|
-re_croak2|||
-re_dup_guts|||
-re_intuit_start||5.009005|
-re_intuit_string||5.006000|
-readpipe_override|||
-realloc||5.007002|n
-reentrant_free|||
-reentrant_init|||
-reentrant_retry|||vn
-reentrant_size|||
-ref_array_or_hash|||
-refcounted_he_chain_2hv|||
-refcounted_he_fetch|||
-refcounted_he_free|||
-refcounted_he_new_common|||
-refcounted_he_new|||
-refcounted_he_value|||
-refkids|||
-refto|||
-ref||5.011000|
-reg_check_named_buff_matched|||
-reg_named_buff_all||5.009005|
-reg_named_buff_exists||5.009005|
-reg_named_buff_fetch||5.009005|
-reg_named_buff_firstkey||5.009005|
-reg_named_buff_iter|||
-reg_named_buff_nextkey||5.009005|
-reg_named_buff_scalar||5.009005|
-reg_named_buff|||
-reg_namedseq|||
-reg_node|||
-reg_numbered_buff_fetch|||
-reg_numbered_buff_length|||
-reg_numbered_buff_store|||
-reg_qr_package|||
-reg_recode|||
-reg_scan_name|||
-reg_skipcomment|||
-reg_temp_copy|||
-reganode|||
-regatom|||
-regbranch|||
-regclass_swash||5.009004|
-regclass|||
-regcppop|||
-regcppush|||
-regcurly|||n
-regdump_extflags|||
-regdump||5.005000|
-regdupe_internal|||
-regexec_flags||5.005000|
-regfree_internal||5.009005|
-reghop3|||n
-reghop4|||n
-reghopmaybe3|||n
-reginclass|||
-reginitcolors||5.006000|
-reginsert|||
-regmatch|||
-regnext||5.005000|
-regpiece|||
-regpposixcc|||
-regprop|||
-regrepeat|||
-regtail_study|||
-regtail|||
-regtry|||
-reguni|||
-regwhite|||n
-reg|||
-repeatcpy|||
-report_evil_fh|||
-report_uninit|||
-require_pv||5.006000|
-require_tie_mod|||
-restore_magic|||
-rninstr|||
-rsignal_restore|||
-rsignal_save|||
-rsignal_state||5.004000|
-rsignal||5.004000|
-run_body|||
-run_user_filter|||
-runops_debug||5.005000|
-runops_standard||5.005000|
-rvpv_dup|||
-rxres_free|||
-rxres_restore|||
-rxres_save|||
-safesyscalloc||5.006000|n
-safesysfree||5.006000|n
-safesysmalloc||5.006000|n
-safesysrealloc||5.006000|n
-same_dirent|||
-save_I16||5.004000|
-save_I32|||
-save_I8||5.006000|
-save_adelete||5.011000|
-save_aelem||5.004050|
-save_alloc||5.006000|
-save_aptr|||
-save_ary|||
-save_bool||5.008001|
-save_clearsv|||
-save_delete|||
-save_destructor_x||5.006000|
-save_destructor||5.006000|
-save_freeop|||
-save_freepv|||
-save_freesv|||
-save_generic_pvref||5.006001|
-save_generic_svref||5.005030|
-save_gp||5.004000|
-save_hash|||
-save_hek_flags|||n
-save_helem_flags||5.011000|
-save_helem||5.004050|
-save_hints|||
-save_hptr|||
-save_int|||
-save_item|||
-save_iv||5.005000|
-save_lines|||
-save_list|||
-save_long|||
-save_magic|||
-save_mortalizesv||5.007001|
-save_nogv|||
-save_op|||
-save_padsv_and_mortalize||5.011000|
-save_pptr|||
-save_pushi32ptr|||
-save_pushptri32ptr|||
-save_pushptrptr|||
-save_pushptr||5.011000|
-save_re_context||5.006000|
-save_scalar_at|||
-save_scalar|||
-save_set_svflags||5.009000|
-save_shared_pvref||5.007003|
-save_sptr|||
-save_svref|||
-save_vptr||5.006000|
-savepvn|||
-savepvs||5.009003|
-savepv|||
-savesharedpvn||5.009005|
-savesharedpv||5.007003|
-savestack_grow_cnt||5.008001|
-savestack_grow|||
-savesvpv||5.009002|
-sawparens|||
-scalar_mod_type|||n
-scalarboolean|||
-scalarkids|||
-scalarseq|||
-scalarvoid|||
-scalar|||
-scan_bin||5.006000|
-scan_commit|||
-scan_const|||
-scan_formline|||
-scan_heredoc|||
-scan_hex|||
-scan_ident|||
-scan_inputsymbol|||
-scan_num||5.007001|
-scan_oct|||
-scan_pat|||
-scan_str|||
-scan_subst|||
-scan_trans|||
-scan_version||5.009001|
-scan_vstring||5.009005|
-scan_word|||
-scope|||
-screaminstr||5.005000|
-search_const|||
-seed||5.008001|
-sequence_num|||
-sequence_tail|||
-sequence|||
-set_context||5.006000|n
-set_numeric_local||5.006000|
-set_numeric_radix||5.006000|
-set_numeric_standard||5.006000|
-setdefout|||
-share_hek_flags|||
-share_hek||5.004000|
-si_dup|||
-sighandler|||n
-simplify_sort|||
-skipspace0|||
-skipspace1|||
-skipspace2|||
-skipspace|||
-softref2xv|||
-sortcv_stacked|||
-sortcv_xsub|||
-sortcv|||
-sortsv_flags||5.009003|
-sortsv||5.007003|
-space_join_names_mortal|||
-ss_dup|||
-stack_grow|||
-start_force|||
-start_glob|||
-start_subparse||5.004000|
-stashpv_hvname_match||5.011000|
-stdize_locale|||
-store_cop_label|||
-strEQ|||
-strGE|||
-strGT|||
-strLE|||
-strLT|||
-strNE|||
-str_to_version||5.006000|
-strip_return|||
-strnEQ|||
-strnNE|||
-study_chunk|||
-sub_crush_depth|||
-sublex_done|||
-sublex_push|||
-sublex_start|||
-sv_2bool|||
-sv_2cv|||
-sv_2io|||
-sv_2iuv_common|||
-sv_2iuv_non_preserve|||
-sv_2iv_flags||5.009001|
-sv_2iv|||
-sv_2mortal|||
-sv_2num|||
-sv_2nv|||
-sv_2pv_flags|5.007002||p
-sv_2pv_nolen|5.006000||p
-sv_2pvbyte_nolen|5.006000||p
-sv_2pvbyte|5.006000||p
-sv_2pvutf8_nolen||5.006000|
-sv_2pvutf8||5.006000|
-sv_2pv|||
-sv_2uv_flags||5.009001|
-sv_2uv|5.004000||p
-sv_add_arena|||
-sv_add_backref|||
-sv_backoff|||
-sv_bless|||
-sv_cat_decode||5.008001|
-sv_catpv_mg|5.004050||p
-sv_catpvf_mg_nocontext|||pvn
-sv_catpvf_mg|5.006000|5.004000|pv
-sv_catpvf_nocontext|||vn
-sv_catpvf||5.004000|v
-sv_catpvn_flags||5.007002|
-sv_catpvn_mg|5.004050||p
-sv_catpvn_nomg|5.007002||p
-sv_catpvn|||
-sv_catpvs|5.009003||p
-sv_catpv|||
-sv_catsv_flags||5.007002|
-sv_catsv_mg|5.004050||p
-sv_catsv_nomg|5.007002||p
-sv_catsv|||
-sv_catxmlpvn|||
-sv_catxmlsv|||
-sv_chop|||
-sv_clean_all|||
-sv_clean_objs|||
-sv_clear|||
-sv_cmp_locale||5.004000|
-sv_cmp|||
-sv_collxfrm|||
-sv_compile_2op||5.008001|
-sv_copypv||5.007003|
-sv_dec|||
-sv_del_backref|||
-sv_derived_from||5.004000|
-sv_destroyable||5.010000|
-sv_does||5.009004|
-sv_dump|||
-sv_dup_inc_multiple|||
-sv_dup|||
-sv_eq|||
-sv_exp_grow|||
-sv_force_normal_flags||5.007001|
-sv_force_normal||5.006000|
-sv_free2|||
-sv_free_arenas|||
-sv_free|||
-sv_gets||5.004000|
-sv_grow|||
-sv_i_ncmp|||
-sv_inc|||
-sv_insert_flags||5.011000|
-sv_insert|||
-sv_isa|||
-sv_isobject|||
-sv_iv||5.005000|
-sv_kill_backrefs|||
-sv_len_utf8||5.006000|
-sv_len|||
-sv_magic_portable|5.011000|5.004000|p
-sv_magicext||5.007003|
-sv_magic|||
-sv_mortalcopy|||
-sv_ncmp|||
-sv_newmortal|||
-sv_newref|||
-sv_nolocking||5.007003|
-sv_nosharing||5.007003|
-sv_nounlocking|||
-sv_nv||5.005000|
-sv_peek||5.005000|
-sv_pos_b2u_midway|||
-sv_pos_b2u||5.006000|
-sv_pos_u2b_cached|||
-sv_pos_u2b_forwards|||n
-sv_pos_u2b_midway|||n
-sv_pos_u2b||5.006000|
-sv_pvbyten_force||5.006000|
-sv_pvbyten||5.006000|
-sv_pvbyte||5.006000|
-sv_pvn_force_flags|5.007002||p
-sv_pvn_force|||
-sv_pvn_nomg|5.007003|5.005000|p
-sv_pvn||5.005000|
-sv_pvutf8n_force||5.006000|
-sv_pvutf8n||5.006000|
-sv_pvutf8||5.006000|
-sv_pv||5.006000|
-sv_recode_to_utf8||5.007003|
-sv_reftype|||
-sv_release_COW|||
-sv_replace|||
-sv_report_used|||
-sv_reset|||
-sv_rvweaken||5.006000|
-sv_setiv_mg|5.004050||p
-sv_setiv|||
-sv_setnv_mg|5.006000||p
-sv_setnv|||
-sv_setpv_mg|5.004050||p
-sv_setpvf_mg_nocontext|||pvn
-sv_setpvf_mg|5.006000|5.004000|pv
-sv_setpvf_nocontext|||vn
-sv_setpvf||5.004000|v
-sv_setpviv_mg||5.008001|
-sv_setpviv||5.008001|
-sv_setpvn_mg|5.004050||p
-sv_setpvn|||
-sv_setpvs|5.009004||p
-sv_setpv|||
-sv_setref_iv|||
-sv_setref_nv|||
-sv_setref_pvn|||
-sv_setref_pv|||
-sv_setref_uv||5.007001|
-sv_setsv_cow|||
-sv_setsv_flags||5.007002|
-sv_setsv_mg|5.004050||p
-sv_setsv_nomg|5.007002||p
-sv_setsv|||
-sv_setuv_mg|5.004050||p
-sv_setuv|5.004000||p
-sv_tainted||5.004000|
-sv_taint||5.004000|
-sv_true||5.005000|
-sv_unglob|||
-sv_uni_display||5.007003|
-sv_unmagic|||
-sv_unref_flags||5.007001|
-sv_unref|||
-sv_untaint||5.004000|
-sv_upgrade|||
-sv_usepvn_flags||5.009004|
-sv_usepvn_mg|5.004050||p
-sv_usepvn|||
-sv_utf8_decode||5.006000|
-sv_utf8_downgrade||5.006000|
-sv_utf8_encode||5.006000|
-sv_utf8_upgrade_flags_grow||5.011000|
-sv_utf8_upgrade_flags||5.007002|
-sv_utf8_upgrade_nomg||5.007002|
-sv_utf8_upgrade||5.007001|
-sv_uv|5.005000||p
-sv_vcatpvf_mg|5.006000|5.004000|p
-sv_vcatpvfn||5.004000|
-sv_vcatpvf|5.006000|5.004000|p
-sv_vsetpvf_mg|5.006000|5.004000|p
-sv_vsetpvfn||5.004000|
-sv_vsetpvf|5.006000|5.004000|p
-sv_xmlpeek|||
-svtype|||
-swallow_bom|||
-swap_match_buff|||
-swash_fetch||5.007002|
-swash_get|||
-swash_init||5.006000|
-sys_init3||5.010000|n
-sys_init||5.010000|n
-sys_intern_clear|||
-sys_intern_dup|||
-sys_intern_init|||
-sys_term||5.010000|n
-taint_env|||
-taint_proper|||
-tmps_grow||5.006000|
-toLOWER|||
-toUPPER|||
-to_byte_substr|||
-to_uni_fold||5.007003|
-to_uni_lower_lc||5.006000|
-to_uni_lower||5.007003|
-to_uni_title_lc||5.006000|
-to_uni_title||5.007003|
-to_uni_upper_lc||5.006000|
-to_uni_upper||5.007003|
-to_utf8_case||5.007003|
-to_utf8_fold||5.007003|
-to_utf8_lower||5.007003|
-to_utf8_substr|||
-to_utf8_title||5.007003|
-to_utf8_upper||5.007003|
-token_free|||
-token_getmad|||
-tokenize_use|||
-tokeq|||
-tokereport|||
-too_few_arguments|||
-too_many_arguments|||
-uiv_2buf|||n
-unlnk|||
-unpack_rec|||
-unpack_str||5.007003|
-unpackstring||5.008001|
-unshare_hek_or_pvn|||
-unshare_hek|||
-unsharepvn||5.004000|
-unwind_handler_stack|||
-update_debugger_info|||
-upg_version||5.009005|
-usage|||
-utf16_to_utf8_reversed||5.006001|
-utf16_to_utf8||5.006001|
-utf8_distance||5.006000|
-utf8_hop||5.006000|
-utf8_length||5.007001|
-utf8_mg_pos_cache_update|||
-utf8_to_bytes||5.006001|
-utf8_to_uvchr||5.007001|
-utf8_to_uvuni||5.007001|
-utf8n_to_uvchr|||
-utf8n_to_uvuni||5.007001|
-utilize|||
-uvchr_to_utf8_flags||5.007003|
-uvchr_to_utf8|||
-uvuni_to_utf8_flags||5.007003|
-uvuni_to_utf8||5.007001|
-validate_suid|||
-varname|||
-vcmp||5.009000|
-vcroak||5.006000|
-vdeb||5.007003|
-vdie_common|||
-vdie_croak_common|||
-vdie|||
-vform||5.006000|
-visit|||
-vivify_defelem|||
-vivify_ref|||
-vload_module|5.006000||p
-vmess||5.006000|
-vnewSVpvf|5.006000|5.004000|p
-vnormal||5.009002|
-vnumify||5.009000|
-vstringify||5.009000|
-vverify||5.009003|
-vwarner||5.006000|
-vwarn||5.006000|
-wait4pid|||
-warn_nocontext|||vn
-warner_nocontext|||vn
-warner|5.006000|5.004000|pv
-warn|||v
-watch|||
-whichsig|||
-write_no_mem|||
-write_to_stderr|||
-xmldump_all|||
-xmldump_attr|||
-xmldump_eval|||
-xmldump_form|||
-xmldump_indent|||v
-xmldump_packsubs|||
-xmldump_sub|||
-xmldump_vindent|||
-yyerror|||
-yylex|||
-yyparse|||
-yywarn|||
-);
-
-if (exists $opt{'list-unsupported'}) {
-  my $f;
-  for $f (sort { lc $a cmp lc $b } keys %API) {
-    next unless $API{$f}{todo};
-    print "$f ", '.'x(40-length($f)), " ", format_version($API{$f}{todo}), "\n";
-  }
-  exit 0;
-}
-
-# Scan for possible replacement candidates
-
-my(%replace, %need, %hints, %warnings, %depends);
-my $replace = 0;
-my($hint, $define, $function);
-
-sub find_api
-{
-  my $code = shift;
-  $code =~ s{
-    / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*)
-  | "[^"\\]*(?:\\.[^"\\]*)*"
-  | '[^'\\]*(?:\\.[^'\\]*)*' }{}egsx;
-  grep { exists $API{$_} } $code =~ /(\w+)/mg;
-}
-
-while (<DATA>) {
-  if ($hint) {
-    my $h = $hint->[0] eq 'Hint' ? \%hints : \%warnings;
-    if (m{^\s*\*\s(.*?)\s*$}) {
-      for (@{$hint->[1]}) {
-        $h->{$_} ||= '';  # suppress warning with older perls
-        $h->{$_} .= "$1\n";
-      }
-    }
-    else { undef $hint }
-  }
-
-  $hint = [$1, [split /,?\s+/, $2]]
-      if m{^\s*$rccs\s+(Hint|Warning):\s+(\w+(?:,?\s+\w+)*)\s*$};
-
-  if ($define) {
-    if ($define->[1] =~ /\\$/) {
-      $define->[1] .= $_;
-    }
-    else {
-      if (exists $API{$define->[0]} && $define->[1] !~ /^DPPP_\(/) {
-        my @n = find_api($define->[1]);
-        push @{$depends{$define->[0]}}, @n if @n
-      }
-      undef $define;
-    }
-  }
-
-  $define = [$1, $2] if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(.*)};
-
-  if ($function) {
-    if (/^}/) {
-      if (exists $API{$function->[0]}) {
-        my @n = find_api($function->[1]);
-        push @{$depends{$function->[0]}}, @n if @n
-      }
-      undef $function;
-    }
-    else {
-      $function->[1] .= $_;
-    }
-  }
-
-  $function = [$1, ''] if m{^DPPP_\(my_(\w+)\)};
-
-  $replace     = $1 if m{^\s*$rccs\s+Replace:\s+(\d+)\s+$rcce\s*$};
-  $replace{$2} = $1 if $replace and m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+)};
-  $replace{$2} = $1 if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+).*$rccs\s+Replace\s+$rcce};
-  $replace{$1} = $2 if m{^\s*$rccs\s+Replace (\w+) with (\w+)\s+$rcce\s*$};
-
-  if (m{^\s*$rccs\s+(\w+(\s*,\s*\w+)*)\s+depends\s+on\s+(\w+(\s*,\s*\w+)*)\s+$rcce\s*$}) {
-    my @deps = map { s/\s+//g; $_ } split /,/, $3;
-    my $d;
-    for $d (map { s/\s+//g; $_ } split /,/, $1) {
-      push @{$depends{$d}}, @deps;
-    }
-  }
-
-  $need{$1} = 1 if m{^#if\s+defined\(NEED_(\w+)(?:_GLOBAL)?\)};
-}
-
-for (values %depends) {
-  my %s;
-  $_ = [sort grep !$s{$_}++, @$_];
-}
-
-if (exists $opt{'api-info'}) {
-  my $f;
-  my $count = 0;
-  my $match = $opt{'api-info'} =~ m!^/(.*)/$! ? $1 : "^\Q$opt{'api-info'}\E\$";
-  for $f (sort { lc $a cmp lc $b } keys %API) {
-    next unless $f =~ /$match/;
-    print "\n=== $f ===\n\n";
-    my $info = 0;
-    if ($API{$f}{base} || $API{$f}{todo}) {
-      my $base = format_version($API{$f}{base} || $API{$f}{todo});
-      print "Supported at least starting from perl-$base.\n";
-      $info++;
-    }
-    if ($API{$f}{provided}) {
-      my $todo = $API{$f}{todo} ? format_version($API{$f}{todo}) : "5.003";
-      print "Support by $ppport provided back to perl-$todo.\n";
-      print "Support needs to be explicitly requested by NEED_$f.\n" if exists $need{$f};
-      print "Depends on: ", join(', ', @{$depends{$f}}), ".\n" if exists $depends{$f};
-      print "\n$hints{$f}" if exists $hints{$f};
-      print "\nWARNING:\n$warnings{$f}" if exists $warnings{$f};
-      $info++;
-    }
-    print "No portability information available.\n" unless $info;
-    $count++;
-  }
-  $count or print "Found no API matching '$opt{'api-info'}'.";
-  print "\n";
-  exit 0;
-}
-
-if (exists $opt{'list-provided'}) {
-  my $f;
-  for $f (sort { lc $a cmp lc $b } keys %API) {
-    next unless $API{$f}{provided};
-    my @flags;
-    push @flags, 'explicit' if exists $need{$f};
-    push @flags, 'depend'   if exists $depends{$f};
-    push @flags, 'hint'     if exists $hints{$f};
-    push @flags, 'warning'  if exists $warnings{$f};
-    my $flags = @flags ? '  ['.join(', ', @flags).']' : '';
-    print "$f$flags\n";
-  }
-  exit 0;
-}
-
-my @files;
-my @srcext = qw( .xs .c .h .cc .cpp -c.inc -xs.inc );
-my $srcext = join '|', map { quotemeta $_ } @srcext;
-
-if (@ARGV) {
-  my %seen;
-  for (@ARGV) {
-    if (-e) {
-      if (-f) {
-        push @files, $_ unless $seen{$_}++;
-      }
-      else { warn "'$_' is not a file.\n" }
-    }
-    else {
-      my @new = grep { -f } glob $_
-          or warn "'$_' does not exist.\n";
-      push @files, grep { !$seen{$_}++ } @new;
-    }
-  }
-}
-else {
-  eval {
-    require File::Find;
-    File::Find::find(sub {
-      $File::Find::name =~ /($srcext)$/i
-          and push @files, $File::Find::name;
-    }, '.');
-  };
-  if ($@) {
-    @files = map { glob "*$_" } @srcext;
-  }
-}
-
-if (!@ARGV || $opt{filter}) {
-  my(@in, @out);
-  my %xsc = map { /(.*)\.xs$/ ? ("$1.c" => 1, "$1.cc" => 1) : () } @files;
-  for (@files) {
-    my $out = exists $xsc{$_} || /\b\Q$ppport\E$/i || !/($srcext)$/i;
-    push @{ $out ? \@out : \@in }, $_;
-  }
-  if (@ARGV && @out) {
-    warning("Skipping the following files (use --nofilter to avoid this):\n| ", join "\n| ", @out);
-  }
-  @files = @in;
-}
-
-die "No input files given!\n" unless @files;
-
-my(%files, %global, %revreplace);
-%revreplace = reverse %replace;
-my $filename;
-my $patch_opened = 0;
-
-for $filename (@files) {
-  unless (open IN, "<$filename") {
-    warn "Unable to read from $filename: $!\n";
-    next;
-  }
-
-  info("Scanning $filename ...");
-
-  my $c = do { local $/; <IN> };
-  close IN;
-
-  my %file = (orig => $c, changes => 0);
-
-  # Temporarily remove C/XS comments and strings from the code
-  my @ccom;
-
-  $c =~ s{
-    ( ^$HS*\#$HS*include\b[^\r\n]+\b(?:\Q$ppport\E|XSUB\.h)\b[^\r\n]*
-    | ^$HS*\#$HS*(?:define|elif|if(?:def)?)\b[^\r\n]* )
-  | ( ^$HS*\#[^\r\n]*
-    | "[^"\\]*(?:\\.[^"\\]*)*"
-    | '[^'\\]*(?:\\.[^'\\]*)*'
-    | / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]* ) )
-  }{ defined $2 and push @ccom, $2;
-     defined $1 ? $1 : "$ccs$#ccom$cce" }mgsex;
-
-  $file{ccom} = \@ccom;
-  $file{code} = $c;
-  $file{has_inc_ppport} = $c =~ /^$HS*#$HS*include[^\r\n]+\b\Q$ppport\E\b/m;
-
-  my $func;
-
-  for $func (keys %API) {
-    my $match = $func;
-    $match .= "|$revreplace{$func}" if exists $revreplace{$func};
-    if ($c =~ /\b(?:Perl_)?($match)\b/) {
-      $file{uses_replace}{$1}++ if exists $revreplace{$func} && $1 eq $revreplace{$func};
-      $file{uses_Perl}{$func}++ if $c =~ /\bPerl_$func\b/;
-      if (exists $API{$func}{provided}) {
-        $file{uses_provided}{$func}++;
-        if (!exists $API{$func}{base} || $API{$func}{base} > $opt{'compat-version'}) {
-          $file{uses}{$func}++;
-          my @deps = rec_depend($func);
-          if (@deps) {
-            $file{uses_deps}{$func} = \@deps;
-            for (@deps) {
-              $file{uses}{$_} = 0 unless exists $file{uses}{$_};
-            }
-          }
-          for ($func, @deps) {
-            $file{needs}{$_} = 'static' if exists $need{$_};
-          }
-        }
-      }
-      if (exists $API{$func}{todo} && $API{$func}{todo} > $opt{'compat-version'}) {
-        if ($c =~ /\b$func\b/) {
-          $file{uses_todo}{$func}++;
-        }
-      }
-    }
-  }
-
-  while ($c =~ /^$HS*#$HS*define$HS+(NEED_(\w+?)(_GLOBAL)?)\b/mg) {
-    if (exists $need{$2}) {
-      $file{defined $3 ? 'needed_global' : 'needed_static'}{$2}++;
-    }
-    else { warning("Possibly wrong #define $1 in $filename") }
-  }
-
-  for (qw(uses needs uses_todo needed_global needed_static)) {
-    for $func (keys %{$file{$_}}) {
-      push @{$global{$_}{$func}}, $filename;
-    }
-  }
-
-  $files{$filename} = \%file;
-}
-
-# Globally resolve NEED_'s
-my $need;
-for $need (keys %{$global{needs}}) {
-  if (@{$global{needs}{$need}} > 1) {
-    my @targets = @{$global{needs}{$need}};
-    my @t = grep $files{$_}{needed_global}{$need}, @targets;
-    @targets = @t if @t;
-    @t = grep /\.xs$/i, @targets;
-    @targets = @t if @t;
-    my $target = shift @targets;
-    $files{$target}{needs}{$need} = 'global';
-    for (@{$global{needs}{$need}}) {
-      $files{$_}{needs}{$need} = 'extern' if $_ ne $target;
-    }
-  }
-}
-
-for $filename (@files) {
-  exists $files{$filename} or next;
-
-  info("=== Analyzing $filename ===");
-
-  my %file = %{$files{$filename}};
-  my $func;
-  my $c = $file{code};
-  my $warnings = 0;
-
-  for $func (sort keys %{$file{uses_Perl}}) {
-    if ($API{$func}{varargs}) {
-      unless ($API{$func}{nothxarg}) {
-        my $changes = ($c =~ s{\b(Perl_$func\s*\(\s*)(?!aTHX_?)(\)|[^\s)]*\))}
-                              { $1 . ($2 eq ')' ? 'aTHX' : 'aTHX_ ') . $2 }ge);
-        if ($changes) {
-          warning("Doesn't pass interpreter argument aTHX to Perl_$func");
-          $file{changes} += $changes;
-        }
-      }
-    }
-    else {
-      warning("Uses Perl_$func instead of $func");
-      $file{changes} += ($c =~ s{\bPerl_$func(\s*)\((\s*aTHX_?)?\s*}
-                                {$func$1(}g);
-    }
-  }
-
-  for $func (sort keys %{$file{uses_replace}}) {
-    warning("Uses $func instead of $replace{$func}");
-    $file{changes} += ($c =~ s/\b$func\b/$replace{$func}/g);
-  }
-
-  for $func (sort keys %{$file{uses_provided}}) {
-    if ($file{uses}{$func}) {
-      if (exists $file{uses_deps}{$func}) {
-        diag("Uses $func, which depends on ", join(', ', @{$file{uses_deps}{$func}}));
-      }
-      else {
-        diag("Uses $func");
-      }
-    }
-    $warnings += hint($func);
-  }
-
-  unless ($opt{quiet}) {
-    for $func (sort keys %{$file{uses_todo}}) {
-      print "*** WARNING: Uses $func, which may not be portable below perl ",
-            format_version($API{$func}{todo}), ", even with '$ppport'\n";
-      $warnings++;
-    }
-  }
-
-  for $func (sort keys %{$file{needed_static}}) {
-    my $message = '';
-    if (not exists $file{uses}{$func}) {
-      $message = "No need to define NEED_$func if $func is never used";
-    }
-    elsif (exists $file{needs}{$func} && $file{needs}{$func} ne 'static') {
-      $message = "No need to define NEED_$func when already needed globally";
-    }
-    if ($message) {
-      diag($message);
-      $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_$func\b.*$LF//mg);
-    }
-  }
-
-  for $func (sort keys %{$file{needed_global}}) {
-    my $message = '';
-    if (not exists $global{uses}{$func}) {
-      $message = "No need to define NEED_${func}_GLOBAL if $func is never used";
-    }
-    elsif (exists $file{needs}{$func}) {
-      if ($file{needs}{$func} eq 'extern') {
-        $message = "No need to define NEED_${func}_GLOBAL when already needed globally";
-      }
-      elsif ($file{needs}{$func} eq 'static') {
-        $message = "No need to define NEED_${func}_GLOBAL when only used in this file";
-      }
-    }
-    if ($message) {
-      diag($message);
-      $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_${func}_GLOBAL\b.*$LF//mg);
-    }
-  }
-
-  $file{needs_inc_ppport} = keys %{$file{uses}};
-
-  if ($file{needs_inc_ppport}) {
-    my $pp = '';
-
-    for $func (sort keys %{$file{needs}}) {
-      my $type = $file{needs}{$func};
-      next if $type eq 'extern';
-      my $suffix = $type eq 'global' ? '_GLOBAL' : '';
-      unless (exists $file{"needed_$type"}{$func}) {
-        if ($type eq 'global') {
-          diag("Files [@{$global{needs}{$func}}] need $func, adding global request");
-        }
-        else {
-          diag("File needs $func, adding static request");
-        }
-        $pp .= "#define NEED_$func$suffix\n";
-      }
-    }
-
-    if ($pp && ($c =~ s/^(?=$HS*#$HS*define$HS+NEED_\w+)/$pp/m)) {
-      $pp = '';
-      $file{changes}++;
-    }
-
-    unless ($file{has_inc_ppport}) {
-      diag("Needs to include '$ppport'");
-      $pp .= qq(#include "$ppport"\n)
-    }
-
-    if ($pp) {
-      $file{changes} += ($c =~ s/^($HS*#$HS*define$HS+NEED_\w+.*?)^/$1$pp/ms)
-                     || ($c =~ s/^(?=$HS*#$HS*include.*\Q$ppport\E)/$pp/m)
-                     || ($c =~ s/^($HS*#$HS*include.*XSUB.*\s*?)^/$1$pp/m)
-                     || ($c =~ s/^/$pp/);
-    }
-  }
-  else {
-    if ($file{has_inc_ppport}) {
-      diag("No need to include '$ppport'");
-      $file{changes} += ($c =~ s/^$HS*?#$HS*include.*\Q$ppport\E.*?$LF//m);
-    }
-  }
-
-  # put back in our C comments
-  my $ix;
-  my $cppc = 0;
-  my @ccom = @{$file{ccom}};
-  for $ix (0 .. $#ccom) {
-    if (!$opt{cplusplus} && $ccom[$ix] =~ s!^//!!) {
-      $cppc++;
-      $file{changes} += $c =~ s/$rccs$ix$rcce/$ccs$ccom[$ix] $cce/;
-    }
-    else {
-      $c =~ s/$rccs$ix$rcce/$ccom[$ix]/;
-    }
-  }
-
-  if ($cppc) {
-    my $s = $cppc != 1 ? 's' : '';
-    warning("Uses $cppc C++ style comment$s, which is not portable");
-  }
-
-  my $s = $warnings != 1 ? 's' : '';
-  my $warn = $warnings ? " ($warnings warning$s)" : '';
-  info("Analysis completed$warn");
-
-  if ($file{changes}) {
-    if (exists $opt{copy}) {
-      my $newfile = "$filename$opt{copy}";
-      if (-e $newfile) {
-        error("'$newfile' already exists, refusing to write copy of '$filename'");
-      }
-      else {
-        local *F;
-        if (open F, ">$newfile") {
-          info("Writing copy of '$filename' with changes to '$newfile'");
-          print F $c;
-          close F;
-        }
-        else {
-          error("Cannot open '$newfile' for writing: $!");
-        }
-      }
-    }
-    elsif (exists $opt{patch} || $opt{changes}) {
-      if (exists $opt{patch}) {
-        unless ($patch_opened) {
-          if (open PATCH, ">$opt{patch}") {
-            $patch_opened = 1;
-          }
-          else {
-            error("Cannot open '$opt{patch}' for writing: $!");
-            delete $opt{patch};
-            $opt{changes} = 1;
-            goto fallback;
-          }
-        }
-        mydiff(\*PATCH, $filename, $c);
-      }
-      else {
-fallback:
-        info("Suggested changes:");
-        mydiff(\*STDOUT, $filename, $c);
-      }
-    }
-    else {
-      my $s = $file{changes} == 1 ? '' : 's';
-      info("$file{changes} potentially required change$s detected");
-    }
-  }
-  else {
-    info("Looks good");
-  }
-}
-
-close PATCH if $patch_opened;
-
-exit 0;
-
-
-sub try_use { eval "use @_;"; return $@ eq '' }
-
-sub mydiff
-{
-  local *F = shift;
-  my($file, $str) = @_;
-  my $diff;
-
-  if (exists $opt{diff}) {
-    $diff = run_diff($opt{diff}, $file, $str);
-  }
-
-  if (!defined $diff and try_use('Text::Diff')) {
-    $diff = Text::Diff::diff($file, \$str, { STYLE => 'Unified' });
-    $diff = <<HEADER . $diff;
---- $file
-+++ $file.patched
-HEADER
-  }
-
-  if (!defined $diff) {
-    $diff = run_diff('diff -u', $file, $str);
-  }
-
-  if (!defined $diff) {
-    $diff = run_diff('diff', $file, $str);
-  }
-
-  if (!defined $diff) {
-    error("Cannot generate a diff. Please install Text::Diff or use --copy.");
-    return;
-  }
-
-  print F $diff;
-}
-
-sub run_diff
-{
-  my($prog, $file, $str) = @_;
-  my $tmp = 'dppptemp';
-  my $suf = 'aaa';
-  my $diff = '';
-  local *F;
-
-  while (-e "$tmp.$suf") { $suf++ }
-  $tmp = "$tmp.$suf";
-
-  if (open F, ">$tmp") {
-    print F $str;
-    close F;
-
-    if (open F, "$prog $file $tmp |") {
-      while (<F>) {
-        s/\Q$tmp\E/$file.patched/;
-        $diff .= $_;
-      }
-      close F;
-      unlink $tmp;
-      return $diff;
-    }
-
-    unlink $tmp;
-  }
-  else {
-    error("Cannot open '$tmp' for writing: $!");
-  }
-
-  return undef;
-}
-
-sub rec_depend
-{
-  my($func, $seen) = @_;
-  return () unless exists $depends{$func};
-  $seen = {%{$seen||{}}};
-  return () if $seen->{$func}++;
-  my %s;
-  grep !$s{$_}++, map { ($_, rec_depend($_, $seen)) } @{$depends{$func}};
-}
-
-sub parse_version
-{
-  my $ver = shift;
-
-  if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) {
-    return ($1, $2, $3);
-  }
-  elsif ($ver !~ /^\d+\.[\d_]+$/) {
-    die "cannot parse version '$ver'\n";
-  }
-
-  $ver =~ s/_//g;
-  $ver =~ s/$/000000/;
-
-  my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/;
-
-  $v = int $v;
-  $s = int $s;
-
-  if ($r < 5 || ($r == 5 && $v < 6)) {
-    if ($s % 10) {
-      die "cannot parse version '$ver'\n";
-    }
-  }
-
-  return ($r, $v, $s);
-}
-
-sub format_version
-{
-  my $ver = shift;
-
-  $ver =~ s/$/000000/;
-  my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/;
-
-  $v = int $v;
-  $s = int $s;
-
-  if ($r < 5 || ($r == 5 && $v < 6)) {
-    if ($s % 10) {
-      die "invalid version '$ver'\n";
-    }
-    $s /= 10;
-
-    $ver = sprintf "%d.%03d", $r, $v;
-    $s > 0 and $ver .= sprintf "_%02d", $s;
-
-    return $ver;
-  }
-
-  return sprintf "%d.%d.%d", $r, $v, $s;
-}
-
-sub info
-{
-  $opt{quiet} and return;
-  print @_, "\n";
-}
-
-sub diag
-{
-  $opt{quiet} and return;
-  $opt{diag} and print @_, "\n";
-}
-
-sub warning
-{
-  $opt{quiet} and return;
-  print "*** ", @_, "\n";
-}
-
-sub error
-{
-  print "*** ERROR: ", @_, "\n";
-}
-
-my %given_hints;
-my %given_warnings;
-sub hint
-{
-  $opt{quiet} and return;
-  my $func = shift;
-  my $rv = 0;
-  if (exists $warnings{$func} && !$given_warnings{$func}++) {
-    my $warn = $warnings{$func};
-    $warn =~ s!^!*** !mg;
-    print "*** WARNING: $func\n", $warn;
-    $rv++;
-  }
-  if ($opt{hints} && exists $hints{$func} && !$given_hints{$func}++) {
-    my $hint = $hints{$func};
-    $hint =~ s/^/   /mg;
-    print "   --- hint for $func ---\n", $hint;
-  }
-  $rv;
-}
-
-sub usage
-{
-  my($usage) = do { local(@ARGV,$/)=($0); <> } =~ /^=head\d$HS+SYNOPSIS\s*^(.*?)\s*^=/ms;
-  my %M = ( 'I' => '*' );
-  $usage =~ s/^\s*perl\s+\S+/$^X $0/;
-  $usage =~ s/([A-Z])<([^>]+)>/$M{$1}$2$M{$1}/g;
-
-  print <<ENDUSAGE;
-
-Usage: $usage
-
-See perldoc $0 for details.
-
-ENDUSAGE
-
-  exit 2;
-}
-
-sub strip
-{
-  my $self = do { local(@ARGV,$/)=($0); <> };
-  my($copy) = $self =~ /^=head\d\s+COPYRIGHT\s*^(.*?)^=\w+/ms;
-  $copy =~ s/^(?=\S+)/    /gms;
-  $self =~ s/^$HS+Do NOT edit.*?(?=^-)/$copy/ms;
-  $self =~ s/^SKIP.*(?=^__DATA__)/SKIP
-if (\@ARGV && \$ARGV[0] eq '--unstrip') {
-  eval { require Devel::PPPort };
-  \$@ and die "Cannot require Devel::PPPort, please install.\\n";
-  if (eval \$Devel::PPPort::VERSION < $VERSION) {
-    die "$0 was originally generated with Devel::PPPort $VERSION.\\n"
-      . "Your Devel::PPPort is only version \$Devel::PPPort::VERSION.\\n"
-      . "Please install a newer version, or --unstrip will not work.\\n";
-  }
-  Devel::PPPort::WriteFile(\$0);
-  exit 0;
-}
-print <<END;
-
-Sorry, but this is a stripped version of \$0.
-
-To be able to use its original script and doc functionality,
-please try to regenerate this file using:
-
-  \$^X \$0 --unstrip
-
-END
-/ms;
-  my($pl, $c) = $self =~ /(.*^__DATA__)(.*)/ms;
-  $c =~ s{
-    / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*)
-  | ( "[^"\\]*(?:\\.[^"\\]*)*"
-    | '[^'\\]*(?:\\.[^'\\]*)*' )
-  | ($HS+) }{ defined $2 ? ' ' : ($1 || '') }gsex;
-  $c =~ s!\s+$!!mg;
-  $c =~ s!^$LF!!mg;
-  $c =~ s!^\s*#\s*!#!mg;
-  $c =~ s!^\s+!!mg;
-
-  open OUT, ">$0" or die "cannot strip $0: $!\n";
-  print OUT "$pl$c\n";
-
-  exit 0;
-}
-
-__DATA__
-*/
-
-#ifndef _P_P_PORTABILITY_H_
-#define _P_P_PORTABILITY_H_
-
-#ifndef DPPP_NAMESPACE
-#  define DPPP_NAMESPACE DPPP_
-#endif
-
-#define DPPP_CAT2(x,y) CAT2(x,y)
-#define DPPP_(name) DPPP_CAT2(DPPP_NAMESPACE, name)
-
-#ifndef PERL_REVISION
-#  if !defined(__PATCHLEVEL_H_INCLUDED__) && !(defined(PATCHLEVEL) && defined(SUBVERSION))
-#    define PERL_PATCHLEVEL_H_IMPLICIT
-#    include <patchlevel.h>
-#  endif
-#  if !(defined(PERL_VERSION) || (defined(SUBVERSION) && defined(PATCHLEVEL)))
-#    include <could_not_find_Perl_patchlevel.h>
-#  endif
-#  ifndef PERL_REVISION
-#    define PERL_REVISION       (5)
-     /* Replace: 1 */
-#    define PERL_VERSION        PATCHLEVEL
-#    define PERL_SUBVERSION     SUBVERSION
-     /* Replace PERL_PATCHLEVEL with PERL_VERSION */
-     /* Replace: 0 */
-#  endif
-#endif
-
-#define _dpppDEC2BCD(dec) ((((dec)/100)<<8)|((((dec)%100)/10)<<4)|((dec)%10))
-#define PERL_BCDVERSION ((_dpppDEC2BCD(PERL_REVISION)<<24)|(_dpppDEC2BCD(PERL_VERSION)<<12)|_dpppDEC2BCD(PERL_SUBVERSION))
-
-/* It is very unlikely that anyone will try to use this with Perl 6
-   (or greater), but who knows.
- */
-#if PERL_REVISION != 5
-#  error ppport.h only works with Perl version 5
-#endif /* PERL_REVISION != 5 */
-#ifndef dTHR
-#  define dTHR                           dNOOP
-#endif
-#ifndef dTHX
-#  define dTHX                           dNOOP
-#endif
-
-#ifndef dTHXa
-#  define dTHXa(x)                       dNOOP
-#endif
-#ifndef pTHX
-#  define pTHX                           void
-#endif
-
-#ifndef pTHX_
-#  define pTHX_
-#endif
-
-#ifndef aTHX
-#  define aTHX
-#endif
-
-#ifndef aTHX_
-#  define aTHX_
-#endif
-
-#if (PERL_BCDVERSION < 0x5006000)
-#  ifdef USE_THREADS
-#    define aTHXR  thr
-#    define aTHXR_ thr,
-#  else
-#    define aTHXR
-#    define aTHXR_
-#  endif
-#  define dTHXR  dTHR
-#else
-#  define aTHXR  aTHX
-#  define aTHXR_ aTHX_
-#  define dTHXR  dTHX
-#endif
-#ifndef dTHXoa
-#  define dTHXoa(x)                      dTHXa(x)
-#endif
-
-#ifdef I_LIMITS
-#  include <limits.h>
-#endif
-
-#ifndef PERL_UCHAR_MIN
-#  define PERL_UCHAR_MIN ((unsigned char)0)
-#endif
-
-#ifndef PERL_UCHAR_MAX
-#  ifdef UCHAR_MAX
-#    define PERL_UCHAR_MAX ((unsigned char)UCHAR_MAX)
-#  else
-#    ifdef MAXUCHAR
-#      define PERL_UCHAR_MAX ((unsigned char)MAXUCHAR)
-#    else
-#      define PERL_UCHAR_MAX ((unsigned char)~(unsigned)0)
-#    endif
-#  endif
-#endif
-
-#ifndef PERL_USHORT_MIN
-#  define PERL_USHORT_MIN ((unsigned short)0)
-#endif
-
-#ifndef PERL_USHORT_MAX
-#  ifdef USHORT_MAX
-#    define PERL_USHORT_MAX ((unsigned short)USHORT_MAX)
-#  else
-#    ifdef MAXUSHORT
-#      define PERL_USHORT_MAX ((unsigned short)MAXUSHORT)
-#    else
-#      ifdef USHRT_MAX
-#        define PERL_USHORT_MAX ((unsigned short)USHRT_MAX)
-#      else
-#        define PERL_USHORT_MAX ((unsigned short)~(unsigned)0)
-#      endif
-#    endif
-#  endif
-#endif
-
-#ifndef PERL_SHORT_MAX
-#  ifdef SHORT_MAX
-#    define PERL_SHORT_MAX ((short)SHORT_MAX)
-#  else
-#    ifdef MAXSHORT    /* Often used in <values.h> */
-#      define PERL_SHORT_MAX ((short)MAXSHORT)
-#    else
-#      ifdef SHRT_MAX
-#        define PERL_SHORT_MAX ((short)SHRT_MAX)
-#      else
-#        define PERL_SHORT_MAX ((short) (PERL_USHORT_MAX >> 1))
-#      endif
-#    endif
-#  endif
-#endif
-
-#ifndef PERL_SHORT_MIN
-#  ifdef SHORT_MIN
-#    define PERL_SHORT_MIN ((short)SHORT_MIN)
-#  else
-#    ifdef MINSHORT
-#      define PERL_SHORT_MIN ((short)MINSHORT)
-#    else
-#      ifdef SHRT_MIN
-#        define PERL_SHORT_MIN ((short)SHRT_MIN)
-#      else
-#        define PERL_SHORT_MIN (-PERL_SHORT_MAX - ((3 & -1) == 3))
-#      endif
-#    endif
-#  endif
-#endif
-
-#ifndef PERL_UINT_MAX
-#  ifdef UINT_MAX
-#    define PERL_UINT_MAX ((unsigned int)UINT_MAX)
-#  else
-#    ifdef MAXUINT
-#      define PERL_UINT_MAX ((unsigned int)MAXUINT)
-#    else
-#      define PERL_UINT_MAX (~(unsigned int)0)
-#    endif
-#  endif
-#endif
-
-#ifndef PERL_UINT_MIN
-#  define PERL_UINT_MIN ((unsigned int)0)
-#endif
-
-#ifndef PERL_INT_MAX
-#  ifdef INT_MAX
-#    define PERL_INT_MAX ((int)INT_MAX)
-#  else
-#    ifdef MAXINT    /* Often used in <values.h> */
-#      define PERL_INT_MAX ((int)MAXINT)
-#    else
-#      define PERL_INT_MAX ((int)(PERL_UINT_MAX >> 1))
-#    endif
-#  endif
-#endif
-
-#ifndef PERL_INT_MIN
-#  ifdef INT_MIN
-#    define PERL_INT_MIN ((int)INT_MIN)
-#  else
-#    ifdef MININT
-#      define PERL_INT_MIN ((int)MININT)
-#    else
-#      define PERL_INT_MIN (-PERL_INT_MAX - ((3 & -1) == 3))
-#    endif
-#  endif
-#endif
-
-#ifndef PERL_ULONG_MAX
-#  ifdef ULONG_MAX
-#    define PERL_ULONG_MAX ((unsigned long)ULONG_MAX)
-#  else
-#    ifdef MAXULONG
-#      define PERL_ULONG_MAX ((unsigned long)MAXULONG)
-#    else
-#      define PERL_ULONG_MAX (~(unsigned long)0)
-#    endif
-#  endif
-#endif
-
-#ifndef PERL_ULONG_MIN
-#  define PERL_ULONG_MIN ((unsigned long)0L)
-#endif
-
-#ifndef PERL_LONG_MAX
-#  ifdef LONG_MAX
-#    define PERL_LONG_MAX ((long)LONG_MAX)
-#  else
-#    ifdef MAXLONG
-#      define PERL_LONG_MAX ((long)MAXLONG)
-#    else
-#      define PERL_LONG_MAX ((long) (PERL_ULONG_MAX >> 1))
-#    endif
-#  endif
-#endif
-
-#ifndef PERL_LONG_MIN
-#  ifdef LONG_MIN
-#    define PERL_LONG_MIN ((long)LONG_MIN)
-#  else
-#    ifdef MINLONG
-#      define PERL_LONG_MIN ((long)MINLONG)
-#    else
-#      define PERL_LONG_MIN (-PERL_LONG_MAX - ((3 & -1) == 3))
-#    endif
-#  endif
-#endif
-
-#if defined(HAS_QUAD) && (defined(convex) || defined(uts))
-#  ifndef PERL_UQUAD_MAX
-#    ifdef ULONGLONG_MAX
-#      define PERL_UQUAD_MAX ((unsigned long long)ULONGLONG_MAX)
-#    else
-#      ifdef MAXULONGLONG
-#        define PERL_UQUAD_MAX ((unsigned long long)MAXULONGLONG)
-#      else
-#        define PERL_UQUAD_MAX (~(unsigned long long)0)
-#      endif
-#    endif
-#  endif
-
-#  ifndef PERL_UQUAD_MIN
-#    define PERL_UQUAD_MIN ((unsigned long long)0L)
-#  endif
-
-#  ifndef PERL_QUAD_MAX
-#    ifdef LONGLONG_MAX
-#      define PERL_QUAD_MAX ((long long)LONGLONG_MAX)
-#    else
-#      ifdef MAXLONGLONG
-#        define PERL_QUAD_MAX ((long long)MAXLONGLONG)
-#      else
-#        define PERL_QUAD_MAX ((long long) (PERL_UQUAD_MAX >> 1))
-#      endif
-#    endif
-#  endif
-
-#  ifndef PERL_QUAD_MIN
-#    ifdef LONGLONG_MIN
-#      define PERL_QUAD_MIN ((long long)LONGLONG_MIN)
-#    else
-#      ifdef MINLONGLONG
-#        define PERL_QUAD_MIN ((long long)MINLONGLONG)
-#      else
-#        define PERL_QUAD_MIN (-PERL_QUAD_MAX - ((3 & -1) == 3))
-#      endif
-#    endif
-#  endif
-#endif
-
-/* This is based on code from 5.003 perl.h */
-#ifdef HAS_QUAD
-#  ifdef cray
-#ifndef IVTYPE
-#  define IVTYPE                         int
-#endif
-
-#ifndef IV_MIN
-#  define IV_MIN                         PERL_INT_MIN
-#endif
-
-#ifndef IV_MAX
-#  define IV_MAX                         PERL_INT_MAX
-#endif
-
-#ifndef UV_MIN
-#  define UV_MIN                         PERL_UINT_MIN
-#endif
-
-#ifndef UV_MAX
-#  define UV_MAX                         PERL_UINT_MAX
-#endif
-
-#    ifdef INTSIZE
-#ifndef IVSIZE
-#  define IVSIZE                         INTSIZE
-#endif
-
-#    endif
-#  else
-#    if defined(convex) || defined(uts)
-#ifndef IVTYPE
-#  define IVTYPE                         long long
-#endif
-
-#ifndef IV_MIN
-#  define IV_MIN                         PERL_QUAD_MIN
-#endif
-
-#ifndef IV_MAX
-#  define IV_MAX                         PERL_QUAD_MAX
-#endif
-
-#ifndef UV_MIN
-#  define UV_MIN                         PERL_UQUAD_MIN
-#endif
-
-#ifndef UV_MAX
-#  define UV_MAX                         PERL_UQUAD_MAX
-#endif
-
-#      ifdef LONGLONGSIZE
-#ifndef IVSIZE
-#  define IVSIZE                         LONGLONGSIZE
-#endif
-
-#      endif
-#    else
-#ifndef IVTYPE
-#  define IVTYPE                         long
-#endif
-
-#ifndef IV_MIN
-#  define IV_MIN                         PERL_LONG_MIN
-#endif
-
-#ifndef IV_MAX
-#  define IV_MAX                         PERL_LONG_MAX
-#endif
-
-#ifndef UV_MIN
-#  define UV_MIN                         PERL_ULONG_MIN
-#endif
-
-#ifndef UV_MAX
-#  define UV_MAX                         PERL_ULONG_MAX
-#endif
-
-#      ifdef LONGSIZE
-#ifndef IVSIZE
-#  define IVSIZE                         LONGSIZE
-#endif
-
-#      endif
-#    endif
-#  endif
-#ifndef IVSIZE
-#  define IVSIZE                         8
-#endif
-
-#ifndef PERL_QUAD_MIN
-#  define PERL_QUAD_MIN                  IV_MIN
-#endif
-
-#ifndef PERL_QUAD_MAX
-#  define PERL_QUAD_MAX                  IV_MAX
-#endif
-
-#ifndef PERL_UQUAD_MIN
-#  define PERL_UQUAD_MIN                 UV_MIN
-#endif
-
-#ifndef PERL_UQUAD_MAX
-#  define PERL_UQUAD_MAX                 UV_MAX
-#endif
-
-#else
-#ifndef IVTYPE
-#  define IVTYPE                         long
-#endif
-
-#ifndef IV_MIN
-#  define IV_MIN                         PERL_LONG_MIN
-#endif
-
-#ifndef IV_MAX
-#  define IV_MAX                         PERL_LONG_MAX
-#endif
-
-#ifndef UV_MIN
-#  define UV_MIN                         PERL_ULONG_MIN
-#endif
-
-#ifndef UV_MAX
-#  define UV_MAX                         PERL_ULONG_MAX
-#endif
-
-#endif
-
-#ifndef IVSIZE
-#  ifdef LONGSIZE
-#    define IVSIZE LONGSIZE
-#  else
-#    define IVSIZE 4 /* A bold guess, but the best we can make. */
-#  endif
-#endif
-#ifndef UVTYPE
-#  define UVTYPE                         unsigned IVTYPE
-#endif
-
-#ifndef UVSIZE
-#  define UVSIZE                         IVSIZE
-#endif
-#ifndef sv_setuv
-#  define sv_setuv(sv, uv)               \
-               STMT_START {                         \
-                 UV TeMpUv = uv;                    \
-                 if (TeMpUv <= IV_MAX)              \
-                   sv_setiv(sv, TeMpUv);            \
-                 else                               \
-                   sv_setnv(sv, (double)TeMpUv);    \
-               } STMT_END
-#endif
-#ifndef newSVuv
-#  define newSVuv(uv)                    ((uv) <= IV_MAX ? newSViv((IV)uv) : newSVnv((NV)uv))
-#endif
-#ifndef sv_2uv
-#  define sv_2uv(sv)                     ((PL_Sv = (sv)), (UV) (SvNOK(PL_Sv) ? SvNV(PL_Sv) : sv_2nv(PL_Sv)))
-#endif
-
-#ifndef SvUVX
-#  define SvUVX(sv)                      ((UV)SvIVX(sv))
-#endif
-
-#ifndef SvUVXx
-#  define SvUVXx(sv)                     SvUVX(sv)
-#endif
-
-#ifndef SvUV
-#  define SvUV(sv)                       (SvIOK(sv) ? SvUVX(sv) : sv_2uv(sv))
-#endif
-
-#ifndef SvUVx
-#  define SvUVx(sv)                      ((PL_Sv = (sv)), SvUV(PL_Sv))
-#endif
-
-/* Hint: sv_uv
- * Always use the SvUVx() macro instead of sv_uv().
- */
-#ifndef sv_uv
-#  define sv_uv(sv)                      SvUVx(sv)
-#endif
-
-#if !defined(SvUOK) && defined(SvIOK_UV)
-#  define SvUOK(sv) SvIOK_UV(sv)
-#endif
-#ifndef XST_mUV
-#  define XST_mUV(i,v)                   (ST(i) = sv_2mortal(newSVuv(v))  )
-#endif
-
-#ifndef XSRETURN_UV
-#  define XSRETURN_UV(v)                 STMT_START { XST_mUV(0,v);  XSRETURN(1); } STMT_END
-#endif
-#ifndef PUSHu
-#  define PUSHu(u)                       STMT_START { sv_setuv(TARG, (UV)(u)); PUSHTARG;  } STMT_END
-#endif
-
-#ifndef XPUSHu
-#  define XPUSHu(u)                      STMT_START { sv_setuv(TARG, (UV)(u)); XPUSHTARG; } STMT_END
-#endif
-
-#ifdef HAS_MEMCMP
-#ifndef memNE
-#  define memNE(s1,s2,l)                 (memcmp(s1,s2,l))
-#endif
-
-#ifndef memEQ
-#  define memEQ(s1,s2,l)                 (!memcmp(s1,s2,l))
-#endif
-
-#else
-#ifndef memNE
-#  define memNE(s1,s2,l)                 (bcmp(s1,s2,l))
-#endif
-
-#ifndef memEQ
-#  define memEQ(s1,s2,l)                 (!bcmp(s1,s2,l))
-#endif
-
-#endif
-#ifndef MoveD
-#  define MoveD(s,d,n,t)                 memmove((char*)(d),(char*)(s), (n) * sizeof(t))
-#endif
-
-#ifndef CopyD
-#  define CopyD(s,d,n,t)                 memcpy((char*)(d),(char*)(s), (n) * sizeof(t))
-#endif
-
-#ifdef HAS_MEMSET
-#ifndef ZeroD
-#  define ZeroD(d,n,t)                   memzero((char*)(d), (n) * sizeof(t))
-#endif
-
-#else
-#ifndef ZeroD
-#  define ZeroD(d,n,t)                   ((void)memzero((char*)(d), (n) * sizeof(t)), d)
-#endif
-
-#endif
-#ifndef PoisonWith
-#  define PoisonWith(d,n,t,b)            (void)memset((char*)(d), (U8)(b), (n) * sizeof(t))
-#endif
-
-#ifndef PoisonNew
-#  define PoisonNew(d,n,t)               PoisonWith(d,n,t,0xAB)
-#endif
-
-#ifndef PoisonFree
-#  define PoisonFree(d,n,t)              PoisonWith(d,n,t,0xEF)
-#endif
-
-#ifndef Poison
-#  define Poison(d,n,t)                  PoisonFree(d,n,t)
-#endif
-#ifndef Newx
-#  define Newx(v,n,t)                    New(0,v,n,t)
-#endif
-
-#ifndef Newxc
-#  define Newxc(v,n,t,c)                 Newc(0,v,n,t,c)
-#endif
-
-#ifndef Newxz
-#  define Newxz(v,n,t)                   Newz(0,v,n,t)
-#endif
-
-#ifndef PERL_UNUSED_DECL
-#  ifdef HASATTRIBUTE
-#    if (defined(__GNUC__) && defined(__cplusplus)) || defined(__INTEL_COMPILER)
-#      define PERL_UNUSED_DECL
-#    else
-#      define PERL_UNUSED_DECL __attribute__((unused))
-#    endif
-#  else
-#    define PERL_UNUSED_DECL
-#  endif
-#endif
-
-#ifndef PERL_UNUSED_ARG
-#  if defined(lint) && defined(S_SPLINT_S) /* www.splint.org */
-#    include <note.h>
-#    define PERL_UNUSED_ARG(x) NOTE(ARGUNUSED(x))
-#  else
-#    define PERL_UNUSED_ARG(x) ((void)x)
-#  endif
-#endif
-
-#ifndef PERL_UNUSED_VAR
-#  define PERL_UNUSED_VAR(x) ((void)x)
-#endif
-
-#ifndef PERL_UNUSED_CONTEXT
-#  ifdef USE_ITHREADS
-#    define PERL_UNUSED_CONTEXT PERL_UNUSED_ARG(my_perl)
-#  else
-#    define PERL_UNUSED_CONTEXT
-#  endif
-#endif
-#ifndef NOOP
-#  define NOOP                           /*EMPTY*/(void)0
-#endif
-
-#ifndef dNOOP
-#  define dNOOP                          extern int /*@unused@*/ Perl___notused PERL_UNUSED_DECL
-#endif
-
-#ifndef NVTYPE
-#  if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE)
-#    define NVTYPE long double
-#  else
-#    define NVTYPE double
-#  endif
-typedef NVTYPE NV;
-#endif
-
-#ifndef INT2PTR
-#  if (IVSIZE == PTRSIZE) && (UVSIZE == PTRSIZE)
-#    define PTRV                  UV
-#    define INT2PTR(any,d)        (any)(d)
-#  else
-#    if PTRSIZE == LONGSIZE
-#      define PTRV                unsigned long
-#    else
-#      define PTRV                unsigned
-#    endif
-#    define INT2PTR(any,d)        (any)(PTRV)(d)
-#  endif
-#endif
-
-#ifndef PTR2ul
-#  if PTRSIZE == LONGSIZE
-#    define PTR2ul(p)     (unsigned long)(p)
-#  else
-#    define PTR2ul(p)     INT2PTR(unsigned long,p)
-#  endif
-#endif
-#ifndef PTR2nat
-#  define PTR2nat(p)                     (PTRV)(p)
-#endif
-
-#ifndef NUM2PTR
-#  define NUM2PTR(any,d)                 (any)PTR2nat(d)
-#endif
-
-#ifndef PTR2IV
-#  define PTR2IV(p)                      INT2PTR(IV,p)
-#endif
-
-#ifndef PTR2UV
-#  define PTR2UV(p)                      INT2PTR(UV,p)
-#endif
-
-#ifndef PTR2NV
-#  define PTR2NV(p)                      NUM2PTR(NV,p)
-#endif
-
-#undef START_EXTERN_C
-#undef END_EXTERN_C
-#undef EXTERN_C
-#ifdef __cplusplus
-#  define START_EXTERN_C extern "C" {
-#  define END_EXTERN_C }
-#  define EXTERN_C extern "C"
-#else
-#  define START_EXTERN_C
-#  define END_EXTERN_C
-#  define EXTERN_C extern
-#endif
-
-#if defined(PERL_GCC_PEDANTIC)
-#  ifndef PERL_GCC_BRACE_GROUPS_FORBIDDEN
-#    define PERL_GCC_BRACE_GROUPS_FORBIDDEN
-#  endif
-#endif
-
-#if defined(__GNUC__) && !defined(PERL_GCC_BRACE_GROUPS_FORBIDDEN) && !defined(__cplusplus)
-#  ifndef PERL_USE_GCC_BRACE_GROUPS
-#    define PERL_USE_GCC_BRACE_GROUPS
-#  endif
-#endif
-
-#undef STMT_START
-#undef STMT_END
-#ifdef PERL_USE_GCC_BRACE_GROUPS
-#  define STMT_START   (void)( /* gcc supports ``({ STATEMENTS; })'' */
-#  define STMT_END     )
-#else
-#  if defined(VOIDFLAGS) && (VOIDFLAGS) && (defined(sun) || defined(__sun__)) && !defined(__GNUC__)
-#    define STMT_START if (1)
-#    define STMT_END   else (void)0
-#  else
-#    define STMT_START do
-#    define STMT_END   while (0)
-#  endif
-#endif
-#ifndef boolSV
-#  define boolSV(b)                      ((b) ? &PL_sv_yes : &PL_sv_no)
-#endif
-
-/* DEFSV appears first in 5.004_56 */
-#ifndef DEFSV
-#  define DEFSV                          GvSV(PL_defgv)
-#endif
-
-#ifndef SAVE_DEFSV
-#  define SAVE_DEFSV                     SAVESPTR(GvSV(PL_defgv))
-#endif
-
-#ifndef DEFSV_set
-#  define DEFSV_set(sv)                  (DEFSV = (sv))
-#endif
-
-/* Older perls (<=5.003) lack AvFILLp */
-#ifndef AvFILLp
-#  define AvFILLp                        AvFILL
-#endif
-#ifndef ERRSV
-#  define ERRSV                          get_sv("@",FALSE)
-#endif
-
-/* Hint: gv_stashpvn
- * This function's backport doesn't support the length parameter, but
- * rather ignores it. Portability can only be ensured if the length
- * parameter is used for speed reasons, but the length can always be
- * correctly computed from the string argument.
- */
-#ifndef gv_stashpvn
-#  define gv_stashpvn(str,len,create)    gv_stashpv(str,create)
-#endif
-
-/* Replace: 1 */
-#ifndef get_cv
-#  define get_cv                         perl_get_cv
-#endif
-
-#ifndef get_sv
-#  define get_sv                         perl_get_sv
-#endif
-
-#ifndef get_av
-#  define get_av                         perl_get_av
-#endif
-
-#ifndef get_hv
-#  define get_hv                         perl_get_hv
-#endif
-
-/* Replace: 0 */
-#ifndef dUNDERBAR
-#  define dUNDERBAR                      dNOOP
-#endif
-
-#ifndef UNDERBAR
-#  define UNDERBAR                       DEFSV
-#endif
-#ifndef dAX
-#  define dAX                            I32 ax = MARK - PL_stack_base + 1
-#endif
-
-#ifndef dITEMS
-#  define dITEMS                         I32 items = SP - MARK
-#endif
-#ifndef dXSTARG
-#  define dXSTARG                        SV * targ = sv_newmortal()
-#endif
-#ifndef dAXMARK
-#  define dAXMARK                        I32 ax = POPMARK; \
-                               register SV ** const mark = PL_stack_base + ax++
-#endif
-#ifndef XSprePUSH
-#  define XSprePUSH                      (sp = PL_stack_base + ax - 1)
-#endif
-
-#if (PERL_BCDVERSION < 0x5005000)
-#  undef XSRETURN
-#  define XSRETURN(off)                                   \
-      STMT_START {                                        \
-          PL_stack_sp = PL_stack_base + ax + ((off) - 1); \
-          return;                                         \
-      } STMT_END
-#endif
-#ifndef XSPROTO
-#  define XSPROTO(name)                  void name(pTHX_ CV* cv)
-#endif
-
-#ifndef SVfARG
-#  define SVfARG(p)                      ((void*)(p))
-#endif
-#ifndef PERL_ABS
-#  define PERL_ABS(x)                    ((x) < 0 ? -(x) : (x))
-#endif
-#ifndef dVAR
-#  define dVAR                           dNOOP
-#endif
-#ifndef SVf
-#  define SVf                            "_"
-#endif
-#ifndef UTF8_MAXBYTES
-#  define UTF8_MAXBYTES                  UTF8_MAXLEN
-#endif
-#ifndef CPERLscope
-#  define CPERLscope(x)                  x
-#endif
-#ifndef PERL_HASH
-#  define PERL_HASH(hash,str,len)        \
-     STMT_START        { \
-       const char *s_PeRlHaSh = str; \
-       I32 i_PeRlHaSh = len; \
-       U32 hash_PeRlHaSh = 0; \
-       while (i_PeRlHaSh--) \
-           hash_PeRlHaSh = hash_PeRlHaSh * 33 + *s_PeRlHaSh++; \
-       (hash) = hash_PeRlHaSh; \
-    } STMT_END
-#endif
-
-#ifndef PERLIO_FUNCS_DECL
-# ifdef PERLIO_FUNCS_CONST
-#  define PERLIO_FUNCS_DECL(funcs) const PerlIO_funcs funcs
-#  define PERLIO_FUNCS_CAST(funcs) (PerlIO_funcs*)(funcs)
-# else
-#  define PERLIO_FUNCS_DECL(funcs) PerlIO_funcs funcs
-#  define PERLIO_FUNCS_CAST(funcs) (funcs)
-# endif
-#endif
-
-/* provide these typedefs for older perls */
-#if (PERL_BCDVERSION < 0x5009003)
-
-# ifdef ARGSproto
-typedef OP* (CPERLscope(*Perl_ppaddr_t))(ARGSproto);
-# else
-typedef OP* (CPERLscope(*Perl_ppaddr_t))(pTHX);
-# endif
-
-typedef OP* (CPERLscope(*Perl_check_t)) (pTHX_ OP*);
-
-#endif
-#ifndef isPSXSPC
-#  define isPSXSPC(c)                    (isSPACE(c) || (c) == '\v')
-#endif
-
-#ifndef isBLANK
-#  define isBLANK(c)                     ((c) == ' ' || (c) == '\t')
-#endif
-
-#ifdef EBCDIC
-#ifndef isALNUMC
-#  define isALNUMC(c)                    isalnum(c)
-#endif
-
-#ifndef isASCII
-#  define isASCII(c)                     isascii(c)
-#endif
-
-#ifndef isCNTRL
-#  define isCNTRL(c)                     iscntrl(c)
-#endif
-
-#ifndef isGRAPH
-#  define isGRAPH(c)                     isgraph(c)
-#endif
-
-#ifndef isPRINT
-#  define isPRINT(c)                     isprint(c)
-#endif
-
-#ifndef isPUNCT
-#  define isPUNCT(c)                     ispunct(c)
-#endif
-
-#ifndef isXDIGIT
-#  define isXDIGIT(c)                    isxdigit(c)
-#endif
-
-#else
-# if (PERL_BCDVERSION < 0x5010000)
-/* Hint: isPRINT
- * The implementation in older perl versions includes all of the
- * isSPACE() characters, which is wrong. The version provided by
- * Devel::PPPort always overrides a present buggy version.
- */
-#  undef isPRINT
-# endif
-#ifndef isALNUMC
-#  define isALNUMC(c)                    (isALPHA(c) || isDIGIT(c))
-#endif
-
-#ifndef isASCII
-#  define isASCII(c)                     ((c) <= 127)
-#endif
-
-#ifndef isCNTRL
-#  define isCNTRL(c)                     ((c) < ' ' || (c) == 127)
-#endif
-
-#ifndef isGRAPH
-#  define isGRAPH(c)                     (isALNUM(c) || isPUNCT(c))
-#endif
-
-#ifndef isPRINT
-#  define isPRINT(c)                     (((c) >= 32 && (c) < 127))
-#endif
-
-#ifndef isPUNCT
-#  define isPUNCT(c)                     (((c) >= 33 && (c) <= 47) || ((c) >= 58 && (c) <= 64)  || ((c) >= 91 && (c) <= 96) || ((c) >= 123 && (c) <= 126))
-#endif
-
-#ifndef isXDIGIT
-#  define isXDIGIT(c)                    (isDIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
-#endif
-
-#endif
-
-#ifndef PERL_SIGNALS_UNSAFE_FLAG
-
-#define PERL_SIGNALS_UNSAFE_FLAG 0x0001
-
-#if (PERL_BCDVERSION < 0x5008000)
-#  define D_PPP_PERL_SIGNALS_INIT   PERL_SIGNALS_UNSAFE_FLAG
-#else
-#  define D_PPP_PERL_SIGNALS_INIT   0
-#endif
-
-#if defined(NEED_PL_signals)
-static U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT;
-#elif defined(NEED_PL_signals_GLOBAL)
-U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT;
-#else
-extern U32 DPPP_(my_PL_signals);
-#endif
-#define PL_signals DPPP_(my_PL_signals)
-
-#endif
-
-/* Hint: PL_ppaddr
- * Calling an op via PL_ppaddr requires passing a context argument
- * for threaded builds. Since the context argument is different for
- * 5.005 perls, you can use aTHXR (supplied by ppport.h), which will
- * automatically be defined as the correct argument.
- */
-
-#if (PERL_BCDVERSION <= 0x5005005)
-/* Replace: 1 */
-#  define PL_ppaddr                 ppaddr
-#  define PL_no_modify              no_modify
-/* Replace: 0 */
-#endif
-
-#if (PERL_BCDVERSION <= 0x5004005)
-/* Replace: 1 */
-#  define PL_DBsignal               DBsignal
-#  define PL_DBsingle               DBsingle
-#  define PL_DBsub                  DBsub
-#  define PL_DBtrace                DBtrace
-#  define PL_Sv                     Sv
-#  define PL_bufend                 bufend
-#  define PL_bufptr                 bufptr
-#  define PL_compiling              compiling
-#  define PL_copline                copline
-#  define PL_curcop                 curcop
-#  define PL_curstash               curstash
-#  define PL_debstash               debstash
-#  define PL_defgv                  defgv
-#  define PL_diehook                diehook
-#  define PL_dirty                  dirty
-#  define PL_dowarn                 dowarn
-#  define PL_errgv                  errgv
-#  define PL_error_count            error_count
-#  define PL_expect                 expect
-#  define PL_hexdigit               hexdigit
-#  define PL_hints                  hints
-#  define PL_in_my                  in_my
-#  define PL_laststatval            laststatval
-#  define PL_lex_state              lex_state
-#  define PL_lex_stuff              lex_stuff
-#  define PL_linestr                linestr
-#  define PL_na                     na
-#  define PL_perl_destruct_level    perl_destruct_level
-#  define PL_perldb                 perldb
-#  define PL_rsfp_filters           rsfp_filters
-#  define PL_rsfp                   rsfp
-#  define PL_stack_base             stack_base
-#  define PL_stack_sp               stack_sp
-#  define PL_statcache              statcache
-#  define PL_stdingv                stdingv
-#  define PL_sv_arenaroot           sv_arenaroot
-#  define PL_sv_no                  sv_no
-#  define PL_sv_undef               sv_undef
-#  define PL_sv_yes                 sv_yes
-#  define PL_tainted                tainted
-#  define PL_tainting               tainting
-#  define PL_tokenbuf               tokenbuf
-/* Replace: 0 */
-#endif
-
-/* Warning: PL_parser
- * For perl versions earlier than 5.9.5, this is an always
- * non-NULL dummy. Also, it cannot be dereferenced. Don't
- * use it if you can avoid is and unless you absolutely know
- * what you're doing.
- * If you always check that PL_parser is non-NULL, you can
- * define DPPP_PL_parser_NO_DUMMY to avoid the creation of
- * a dummy parser structure.
- */
-
-#if (PERL_BCDVERSION >= 0x5009005)
-# ifdef DPPP_PL_parser_NO_DUMMY
-#  define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \
-                (croak("panic: PL_parser == NULL in %s:%d", \
-                       __FILE__, __LINE__), (yy_parser *) NULL))->var)
-# else
-#  ifdef DPPP_PL_parser_NO_DUMMY_WARNING
-#   define D_PPP_parser_dummy_warning(var)
-#  else
-#   define D_PPP_parser_dummy_warning(var) \
-             warn("warning: dummy PL_" #var " used in %s:%d", __FILE__, __LINE__),
-#  endif
-#  define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \
-                (D_PPP_parser_dummy_warning(var) &DPPP_(dummy_PL_parser)))->var)
-#if defined(NEED_PL_parser)
-static yy_parser DPPP_(dummy_PL_parser);
-#elif defined(NEED_PL_parser_GLOBAL)
-yy_parser DPPP_(dummy_PL_parser);
-#else
-extern yy_parser DPPP_(dummy_PL_parser);
-#endif
-
-# endif
-
-/* PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf depends on PL_parser */
-/* Warning: PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf
- * Do not use this variable unless you know exactly what you're
- * doint. It is internal to the perl parser and may change or even
- * be removed in the future. As of perl 5.9.5, you have to check
- * for (PL_parser != NULL) for this variable to have any effect.
- * An always non-NULL PL_parser dummy is provided for earlier
- * perl versions.
- * If PL_parser is NULL when you try to access this variable, a
- * dummy is being accessed instead and a warning is issued unless
- * you define DPPP_PL_parser_NO_DUMMY_WARNING.
- * If DPPP_PL_parser_NO_DUMMY is defined, the code trying to access
- * this variable will croak with a panic message.
- */
-
-# define PL_expect         D_PPP_my_PL_parser_var(expect)
-# define PL_copline        D_PPP_my_PL_parser_var(copline)
-# define PL_rsfp           D_PPP_my_PL_parser_var(rsfp)
-# define PL_rsfp_filters   D_PPP_my_PL_parser_var(rsfp_filters)
-# define PL_linestr        D_PPP_my_PL_parser_var(linestr)
-# define PL_bufptr         D_PPP_my_PL_parser_var(bufptr)
-# define PL_bufend         D_PPP_my_PL_parser_var(bufend)
-# define PL_lex_state      D_PPP_my_PL_parser_var(lex_state)
-# define PL_lex_stuff      D_PPP_my_PL_parser_var(lex_stuff)
-# define PL_tokenbuf       D_PPP_my_PL_parser_var(tokenbuf)
-# define PL_in_my          D_PPP_my_PL_parser_var(in_my)
-# define PL_in_my_stash    D_PPP_my_PL_parser_var(in_my_stash)
-# define PL_error_count    D_PPP_my_PL_parser_var(error_count)
-
-
-#else
-
-/* ensure that PL_parser != NULL and cannot be dereferenced */
-# define PL_parser         ((void *) 1)
-
-#endif
-#ifndef mPUSHs
-#  define mPUSHs(s)                      PUSHs(sv_2mortal(s))
-#endif
-
-#ifndef PUSHmortal
-#  define PUSHmortal                     PUSHs(sv_newmortal())
-#endif
-
-#ifndef mPUSHp
-#  define mPUSHp(p,l)                    sv_setpvn(PUSHmortal, (p), (l))
-#endif
-
-#ifndef mPUSHn
-#  define mPUSHn(n)                      sv_setnv(PUSHmortal, (NV)(n))
-#endif
-
-#ifndef mPUSHi
-#  define mPUSHi(i)                      sv_setiv(PUSHmortal, (IV)(i))
-#endif
-
-#ifndef mPUSHu
-#  define mPUSHu(u)                      sv_setuv(PUSHmortal, (UV)(u))
-#endif
-#ifndef mXPUSHs
-#  define mXPUSHs(s)                     XPUSHs(sv_2mortal(s))
-#endif
-
-#ifndef XPUSHmortal
-#  define XPUSHmortal                    XPUSHs(sv_newmortal())
-#endif
-
-#ifndef mXPUSHp
-#  define mXPUSHp(p,l)                   STMT_START { EXTEND(sp,1); sv_setpvn(PUSHmortal, (p), (l)); } STMT_END
-#endif
-
-#ifndef mXPUSHn
-#  define mXPUSHn(n)                     STMT_START { EXTEND(sp,1); sv_setnv(PUSHmortal, (NV)(n)); } STMT_END
-#endif
-
-#ifndef mXPUSHi
-#  define mXPUSHi(i)                     STMT_START { EXTEND(sp,1); sv_setiv(PUSHmortal, (IV)(i)); } STMT_END
-#endif
-
-#ifndef mXPUSHu
-#  define mXPUSHu(u)                     STMT_START { EXTEND(sp,1); sv_setuv(PUSHmortal, (UV)(u)); } STMT_END
-#endif
-
-/* Replace: 1 */
-#ifndef call_sv
-#  define call_sv                        perl_call_sv
-#endif
-
-#ifndef call_pv
-#  define call_pv                        perl_call_pv
-#endif
-
-#ifndef call_argv
-#  define call_argv                      perl_call_argv
-#endif
-
-#ifndef call_method
-#  define call_method                    perl_call_method
-#endif
-#ifndef eval_sv
-#  define eval_sv                        perl_eval_sv
-#endif
-
-/* Replace: 0 */
-#ifndef PERL_LOADMOD_DENY
-#  define PERL_LOADMOD_DENY              0x1
-#endif
-
-#ifndef PERL_LOADMOD_NOIMPORT
-#  define PERL_LOADMOD_NOIMPORT          0x2
-#endif
-
-#ifndef PERL_LOADMOD_IMPORT_OPS
-#  define PERL_LOADMOD_IMPORT_OPS        0x4
-#endif
-
-#ifndef G_METHOD
-# define G_METHOD              64
-# ifdef call_sv
-#  undef call_sv
-# endif
-# if (PERL_BCDVERSION < 0x5006000)
-#  define call_sv(sv, flags)  ((flags) & G_METHOD ? perl_call_method((char *) SvPV_nolen_const(sv), \
-                               (flags) & ~G_METHOD) : perl_call_sv(sv, flags))
-# else
-#  define call_sv(sv, flags)  ((flags) & G_METHOD ? Perl_call_method(aTHX_ (char *) SvPV_nolen_const(sv), \
-                               (flags) & ~G_METHOD) : Perl_call_sv(aTHX_ sv, flags))
-# endif
-#endif
-
-/* Replace perl_eval_pv with eval_pv */
-
-#ifndef eval_pv
-#if defined(NEED_eval_pv)
-static SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error);
-static
-#else
-extern SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error);
-#endif
-
-#ifdef eval_pv
-#  undef eval_pv
-#endif
-#define eval_pv(a,b) DPPP_(my_eval_pv)(aTHX_ a,b)
-#define Perl_eval_pv DPPP_(my_eval_pv)
-
-#if defined(NEED_eval_pv) || defined(NEED_eval_pv_GLOBAL)
-
-SV*
-DPPP_(my_eval_pv)(char *p, I32 croak_on_error)
-{
-    dSP;
-    SV* sv = newSVpv(p, 0);
-
-    PUSHMARK(sp);
-    eval_sv(sv, G_SCALAR);
-    SvREFCNT_dec(sv);
-
-    SPAGAIN;
-    sv = POPs;
-    PUTBACK;
-
-    if (croak_on_error && SvTRUE(GvSV(errgv)))
-       croak(SvPVx(GvSV(errgv), na));
-
-    return sv;
-}
-
-#endif
-#endif
-
-#ifndef vload_module
-#if defined(NEED_vload_module)
-static void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args);
-static
-#else
-extern void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args);
-#endif
-
-#ifdef vload_module
-#  undef vload_module
-#endif
-#define vload_module(a,b,c,d) DPPP_(my_vload_module)(aTHX_ a,b,c,d)
-#define Perl_vload_module DPPP_(my_vload_module)
-
-#if defined(NEED_vload_module) || defined(NEED_vload_module_GLOBAL)
-
-void
-DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args)
-{
-    dTHR;
-    dVAR;
-    OP *veop, *imop;
-
-    OP * const modname = newSVOP(OP_CONST, 0, name);
-    /* 5.005 has a somewhat hacky force_normal that doesn't croak on
-       SvREADONLY() if PL_compling is true. Current perls take care in
-       ck_require() to correctly turn off SvREADONLY before calling
-       force_normal_flags(). This seems a better fix than fudging PL_compling
-     */
-    SvREADONLY_off(((SVOP*)modname)->op_sv);
-    modname->op_private |= OPpCONST_BARE;
-    if (ver) {
-       veop = newSVOP(OP_CONST, 0, ver);
-    }
-    else
-       veop = NULL;
-    if (flags & PERL_LOADMOD_NOIMPORT) {
-       imop = sawparens(newNULLLIST());
-    }
-    else if (flags & PERL_LOADMOD_IMPORT_OPS) {
-       imop = va_arg(*args, OP*);
-    }
-    else {
-       SV *sv;
-       imop = NULL;
-       sv = va_arg(*args, SV*);
-       while (sv) {
-           imop = append_elem(OP_LIST, imop, newSVOP(OP_CONST, 0, sv));
-           sv = va_arg(*args, SV*);
-       }
-    }
-    {
-       const line_t ocopline = PL_copline;
-       COP * const ocurcop = PL_curcop;
-       const int oexpect = PL_expect;
-
-#if (PERL_BCDVERSION >= 0x5004000)
-       utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(FALSE, 0),
-               veop, modname, imop);
-#else
-       utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(),
-               modname, imop);
-#endif
-       PL_expect = oexpect;
-       PL_copline = ocopline;
-       PL_curcop = ocurcop;
-    }
-}
-
-#endif
-#endif
-
-#ifndef load_module
-#if defined(NEED_load_module)
-static void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...);
-static
-#else
-extern void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...);
-#endif
-
-#ifdef load_module
-#  undef load_module
-#endif
-#define load_module DPPP_(my_load_module)
-#define Perl_load_module DPPP_(my_load_module)
-
-#if defined(NEED_load_module) || defined(NEED_load_module_GLOBAL)
-
-void
-DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...)
-{
-    va_list args;
-    va_start(args, ver);
-    vload_module(flags, name, ver, &args);
-    va_end(args);
-}
-
-#endif
-#endif
-#ifndef newRV_inc
-#  define newRV_inc(sv)                  newRV(sv)   /* Replace */
-#endif
-
-#ifndef newRV_noinc
-#if defined(NEED_newRV_noinc)
-static SV * DPPP_(my_newRV_noinc)(SV *sv);
-static
-#else
-extern SV * DPPP_(my_newRV_noinc)(SV *sv);
-#endif
-
-#ifdef newRV_noinc
-#  undef newRV_noinc
-#endif
-#define newRV_noinc(a) DPPP_(my_newRV_noinc)(aTHX_ a)
-#define Perl_newRV_noinc DPPP_(my_newRV_noinc)
-
-#if defined(NEED_newRV_noinc) || defined(NEED_newRV_noinc_GLOBAL)
-SV *
-DPPP_(my_newRV_noinc)(SV *sv)
-{
-  SV *rv = (SV *)newRV(sv);
-  SvREFCNT_dec(sv);
-  return rv;
-}
-#endif
-#endif
-
-/* Hint: newCONSTSUB
- * Returns a CV* as of perl-5.7.1. This return value is not supported
- * by Devel::PPPort.
- */
-
-/* newCONSTSUB from IO.xs is in the core starting with 5.004_63 */
-#if (PERL_BCDVERSION < 0x5004063) && (PERL_BCDVERSION != 0x5004005)
-#if defined(NEED_newCONSTSUB)
-static void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv);
-static
-#else
-extern void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv);
-#endif
-
-#ifdef newCONSTSUB
-#  undef newCONSTSUB
-#endif
-#define newCONSTSUB(a,b,c) DPPP_(my_newCONSTSUB)(aTHX_ a,b,c)
-#define Perl_newCONSTSUB DPPP_(my_newCONSTSUB)
-
-#if defined(NEED_newCONSTSUB) || defined(NEED_newCONSTSUB_GLOBAL)
-
-/* This is just a trick to avoid a dependency of newCONSTSUB on PL_parser */
-/* (There's no PL_parser in perl < 5.005, so this is completely safe)     */
-#define D_PPP_PL_copline PL_copline
-
-void
-DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv)
-{
-       U32 oldhints = PL_hints;
-       HV *old_cop_stash = PL_curcop->cop_stash;
-       HV *old_curstash = PL_curstash;
-       line_t oldline = PL_curcop->cop_line;
-       PL_curcop->cop_line = D_PPP_PL_copline;
-
-       PL_hints &= ~HINT_BLOCK_SCOPE;
-       if (stash)
-               PL_curstash = PL_curcop->cop_stash = stash;
-
-       newSUB(
-
-#if   (PERL_BCDVERSION < 0x5003022)
-               start_subparse(),
-#elif (PERL_BCDVERSION == 0x5003022)
-               start_subparse(0),
-#else  /* 5.003_23  onwards */
-               start_subparse(FALSE, 0),
-#endif
-
-               newSVOP(OP_CONST, 0, newSVpv((char *) name, 0)),
-               newSVOP(OP_CONST, 0, &PL_sv_no),   /* SvPV(&PL_sv_no) == "" -- GMB */
-               newSTATEOP(0, Nullch, newSVOP(OP_CONST, 0, sv))
-       );
-
-       PL_hints = oldhints;
-       PL_curcop->cop_stash = old_cop_stash;
-       PL_curstash = old_curstash;
-       PL_curcop->cop_line = oldline;
-}
-#endif
-#endif
-
-/*
- * Boilerplate macros for initializing and accessing interpreter-local
- * data from C.  All statics in extensions should be reworked to use
- * this, if you want to make the extension thread-safe.  See ext/re/re.xs
- * for an example of the use of these macros.
- *
- * Code that uses these macros is responsible for the following:
- * 1. #define MY_CXT_KEY to a unique string, e.g. "DynaLoader_guts"
- * 2. Declare a typedef named my_cxt_t that is a structure that contains
- *    all the data that needs to be interpreter-local.
- * 3. Use the START_MY_CXT macro after the declaration of my_cxt_t.
- * 4. Use the MY_CXT_INIT macro such that it is called exactly once
- *    (typically put in the BOOT: section).
- * 5. Use the members of the my_cxt_t structure everywhere as
- *    MY_CXT.member.
- * 6. Use the dMY_CXT macro (a declaration) in all the functions that
- *    access MY_CXT.
- */
-
-#if defined(MULTIPLICITY) || defined(PERL_OBJECT) || \
-    defined(PERL_CAPI)    || defined(PERL_IMPLICIT_CONTEXT)
-
-#ifndef START_MY_CXT
-
-/* This must appear in all extensions that define a my_cxt_t structure,
- * right after the definition (i.e. at file scope).  The non-threads
- * case below uses it to declare the data as static. */
-#define START_MY_CXT
-
-#if (PERL_BCDVERSION < 0x5004068)
-/* Fetches the SV that keeps the per-interpreter data. */
-#define dMY_CXT_SV \
-       SV *my_cxt_sv = get_sv(MY_CXT_KEY, FALSE)
-#else /* >= perl5.004_68 */
-#define dMY_CXT_SV \
-       SV *my_cxt_sv = *hv_fetch(PL_modglobal, MY_CXT_KEY,             \
-                                 sizeof(MY_CXT_KEY)-1, TRUE)
-#endif /* < perl5.004_68 */
-
-/* This declaration should be used within all functions that use the
- * interpreter-local data. */
-#define dMY_CXT        \
-       dMY_CXT_SV;                                                     \
-       my_cxt_t *my_cxtp = INT2PTR(my_cxt_t*,SvUV(my_cxt_sv))
-
-/* Creates and zeroes the per-interpreter data.
- * (We allocate my_cxtp in a Perl SV so that it will be released when
- * the interpreter goes away.) */
-#define MY_CXT_INIT \
-       dMY_CXT_SV;                                                     \
-       /* newSV() allocates one more than needed */                    \
-       my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\
-       Zero(my_cxtp, 1, my_cxt_t);                                     \
-       sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
-
-/* This macro must be used to access members of the my_cxt_t structure.
- * e.g. MYCXT.some_data */
-#define MY_CXT         (*my_cxtp)
-
-/* Judicious use of these macros can reduce the number of times dMY_CXT
- * is used.  Use is similar to pTHX, aTHX etc. */
-#define pMY_CXT                my_cxt_t *my_cxtp
-#define pMY_CXT_       pMY_CXT,
-#define _pMY_CXT       ,pMY_CXT
-#define aMY_CXT                my_cxtp
-#define aMY_CXT_       aMY_CXT,
-#define _aMY_CXT       ,aMY_CXT
-
-#endif /* START_MY_CXT */
-
-#ifndef MY_CXT_CLONE
-/* Clones the per-interpreter data. */
-#define MY_CXT_CLONE \
-       dMY_CXT_SV;                                                     \
-       my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\
-       Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t);\
-       sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
-#endif
-
-#else /* single interpreter */
-
-#ifndef START_MY_CXT
-
-#define START_MY_CXT   static my_cxt_t my_cxt;
-#define dMY_CXT_SV     dNOOP
-#define dMY_CXT                dNOOP
-#define MY_CXT_INIT    NOOP
-#define MY_CXT         my_cxt
-
-#define pMY_CXT                void
-#define pMY_CXT_
-#define _pMY_CXT
-#define aMY_CXT
-#define aMY_CXT_
-#define _aMY_CXT
-
-#endif /* START_MY_CXT */
-
-#ifndef MY_CXT_CLONE
-#define MY_CXT_CLONE   NOOP
-#endif
-
-#endif
-
-#ifndef IVdf
-#  if IVSIZE == LONGSIZE
-#    define    IVdf      "ld"
-#    define    UVuf      "lu"
-#    define    UVof      "lo"
-#    define    UVxf      "lx"
-#    define    UVXf      "lX"
-#  else
-#    if IVSIZE == INTSIZE
-#      define  IVdf      "d"
-#      define  UVuf      "u"
-#      define  UVof      "o"
-#      define  UVxf      "x"
-#      define  UVXf      "X"
-#    endif
-#  endif
-#endif
-
-#ifndef NVef
-#  if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) && \
-      defined(PERL_PRIfldbl) && (PERL_BCDVERSION != 0x5006000)
-            /* Not very likely, but let's try anyway. */
-#    define NVef          PERL_PRIeldbl
-#    define NVff          PERL_PRIfldbl
-#    define NVgf          PERL_PRIgldbl
-#  else
-#    define NVef          "e"
-#    define NVff          "f"
-#    define NVgf          "g"
-#  endif
-#endif
-
-#ifndef SvREFCNT_inc
-#  ifdef PERL_USE_GCC_BRACE_GROUPS
-#    define SvREFCNT_inc(sv)           \
-      ({                               \
-          SV * const _sv = (SV*)(sv);  \
-          if (_sv)                     \
-               (SvREFCNT(_sv))++;      \
-          _sv;                         \
-      })
-#  else
-#    define SvREFCNT_inc(sv)   \
-          ((PL_Sv=(SV*)(sv)) ? (++(SvREFCNT(PL_Sv)),PL_Sv) : NULL)
-#  endif
-#endif
-
-#ifndef SvREFCNT_inc_simple
-#  ifdef PERL_USE_GCC_BRACE_GROUPS
-#    define SvREFCNT_inc_simple(sv)    \
-      ({                                       \
-          if (sv)                              \
-               (SvREFCNT(sv))++;               \
-          (SV *)(sv);                          \
-      })
-#  else
-#    define SvREFCNT_inc_simple(sv) \
-          ((sv) ? (SvREFCNT(sv)++,(SV*)(sv)) : NULL)
-#  endif
-#endif
-
-#ifndef SvREFCNT_inc_NN
-#  ifdef PERL_USE_GCC_BRACE_GROUPS
-#    define SvREFCNT_inc_NN(sv)                \
-      ({                                       \
-          SV * const _sv = (SV*)(sv);  \
-          SvREFCNT(_sv)++;             \
-          _sv;                         \
-      })
-#  else
-#    define SvREFCNT_inc_NN(sv) \
-          (PL_Sv=(SV*)(sv),++(SvREFCNT(PL_Sv)),PL_Sv)
-#  endif
-#endif
-
-#ifndef SvREFCNT_inc_void
-#  ifdef PERL_USE_GCC_BRACE_GROUPS
-#    define SvREFCNT_inc_void(sv)              \
-      ({                                       \
-          SV * const _sv = (SV*)(sv);  \
-          if (_sv)                     \
-              (void)(SvREFCNT(_sv)++); \
-      })
-#  else
-#    define SvREFCNT_inc_void(sv) \
-          (void)((PL_Sv=(SV*)(sv)) ? ++(SvREFCNT(PL_Sv)) : 0)
-#  endif
-#endif
-#ifndef SvREFCNT_inc_simple_void
-#  define SvREFCNT_inc_simple_void(sv)   STMT_START { if (sv) SvREFCNT(sv)++; } STMT_END
-#endif
-
-#ifndef SvREFCNT_inc_simple_NN
-#  define SvREFCNT_inc_simple_NN(sv)     (++SvREFCNT(sv), (SV*)(sv))
-#endif
-
-#ifndef SvREFCNT_inc_void_NN
-#  define SvREFCNT_inc_void_NN(sv)       (void)(++SvREFCNT((SV*)(sv)))
-#endif
-
-#ifndef SvREFCNT_inc_simple_void_NN
-#  define SvREFCNT_inc_simple_void_NN(sv) (void)(++SvREFCNT((SV*)(sv)))
-#endif
-
-#ifndef newSV_type
-
-#if defined(NEED_newSV_type)
-static SV* DPPP_(my_newSV_type)(pTHX_ svtype const t);
-static
-#else
-extern SV* DPPP_(my_newSV_type)(pTHX_ svtype const t);
-#endif
-
-#ifdef newSV_type
-#  undef newSV_type
-#endif
-#define newSV_type(a) DPPP_(my_newSV_type)(aTHX_ a)
-#define Perl_newSV_type DPPP_(my_newSV_type)
-
-#if defined(NEED_newSV_type) || defined(NEED_newSV_type_GLOBAL)
-
-SV*
-DPPP_(my_newSV_type)(pTHX_ svtype const t)
-{
-  SV* const sv = newSV(0);
-  sv_upgrade(sv, t);
-  return sv;
-}
-
-#endif
-
-#endif
-
-#if (PERL_BCDVERSION < 0x5006000)
-# define D_PPP_CONSTPV_ARG(x)  ((char *) (x))
-#else
-# define D_PPP_CONSTPV_ARG(x)  (x)
-#endif
-#ifndef newSVpvn
-#  define newSVpvn(data,len)             ((data)                                              \
-                                    ? ((len) ? newSVpv((data), (len)) : newSVpv("", 0)) \
-                                    : newSV(0))
-#endif
-#ifndef newSVpvn_utf8
-#  define newSVpvn_utf8(s, len, u)       newSVpvn_flags((s), (len), (u) ? SVf_UTF8 : 0)
-#endif
-#ifndef SVf_UTF8
-#  define SVf_UTF8                       0
-#endif
-
-#ifndef newSVpvn_flags
-
-#if defined(NEED_newSVpvn_flags)
-static SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags);
-static
-#else
-extern SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags);
-#endif
-
-#ifdef newSVpvn_flags
-#  undef newSVpvn_flags
-#endif
-#define newSVpvn_flags(a,b,c) DPPP_(my_newSVpvn_flags)(aTHX_ a,b,c)
-#define Perl_newSVpvn_flags DPPP_(my_newSVpvn_flags)
-
-#if defined(NEED_newSVpvn_flags) || defined(NEED_newSVpvn_flags_GLOBAL)
-
-SV *
-DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags)
-{
-  SV *sv = newSVpvn(D_PPP_CONSTPV_ARG(s), len);
-  SvFLAGS(sv) |= (flags & SVf_UTF8);
-  return (flags & SVs_TEMP) ? sv_2mortal(sv) : sv;
-}
-
-#endif
-
-#endif
-
-/* Backwards compatibility stuff... :-( */
-#if !defined(NEED_sv_2pv_flags) && defined(NEED_sv_2pv_nolen)
-#  define NEED_sv_2pv_flags
-#endif
-#if !defined(NEED_sv_2pv_flags_GLOBAL) && defined(NEED_sv_2pv_nolen_GLOBAL)
-#  define NEED_sv_2pv_flags_GLOBAL
-#endif
-
-/* Hint: sv_2pv_nolen
- * Use the SvPV_nolen() or SvPV_nolen_const() macros instead of sv_2pv_nolen().
- */
-#ifndef sv_2pv_nolen
-#  define sv_2pv_nolen(sv)               SvPV_nolen(sv)
-#endif
-
-#ifdef SvPVbyte
-
-/* Hint: SvPVbyte
- * Does not work in perl-5.6.1, ppport.h implements a version
- * borrowed from perl-5.7.3.
- */
-
-#if (PERL_BCDVERSION < 0x5007000)
-
-#if defined(NEED_sv_2pvbyte)
-static char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp);
-static
-#else
-extern char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp);
-#endif
-
-#ifdef sv_2pvbyte
-#  undef sv_2pvbyte
-#endif
-#define sv_2pvbyte(a,b) DPPP_(my_sv_2pvbyte)(aTHX_ a,b)
-#define Perl_sv_2pvbyte DPPP_(my_sv_2pvbyte)
-
-#if defined(NEED_sv_2pvbyte) || defined(NEED_sv_2pvbyte_GLOBAL)
-
-char *
-DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp)
-{
-  sv_utf8_downgrade(sv,0);
-  return SvPV(sv,*lp);
-}
-
-#endif
-
-/* Hint: sv_2pvbyte
- * Use the SvPVbyte() macro instead of sv_2pvbyte().
- */
-
-#undef SvPVbyte
-
-#define SvPVbyte(sv, lp)                                                \
-        ((SvFLAGS(sv) & (SVf_POK|SVf_UTF8)) == (SVf_POK)                \
-         ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pvbyte(sv, &lp))
-
-#endif
-
-#else
-
-#  define SvPVbyte          SvPV
-#  define sv_2pvbyte        sv_2pv
-
-#endif
-#ifndef sv_2pvbyte_nolen
-#  define sv_2pvbyte_nolen(sv)           sv_2pv_nolen(sv)
-#endif
-
-/* Hint: sv_pvn
- * Always use the SvPV() macro instead of sv_pvn().
- */
-
-/* Hint: sv_pvn_force
- * Always use the SvPV_force() macro instead of sv_pvn_force().
- */
-
-/* If these are undefined, they're not handled by the core anyway */
-#ifndef SV_IMMEDIATE_UNREF
-#  define SV_IMMEDIATE_UNREF             0
-#endif
-
-#ifndef SV_GMAGIC
-#  define SV_GMAGIC                      0
-#endif
-
-#ifndef SV_COW_DROP_PV
-#  define SV_COW_DROP_PV                 0
-#endif
-
-#ifndef SV_UTF8_NO_ENCODING
-#  define SV_UTF8_NO_ENCODING            0
-#endif
-
-#ifndef SV_NOSTEAL
-#  define SV_NOSTEAL                     0
-#endif
-
-#ifndef SV_CONST_RETURN
-#  define SV_CONST_RETURN                0
-#endif
-
-#ifndef SV_MUTABLE_RETURN
-#  define SV_MUTABLE_RETURN              0
-#endif
-
-#ifndef SV_SMAGIC
-#  define SV_SMAGIC                      0
-#endif
-
-#ifndef SV_HAS_TRAILING_NUL
-#  define SV_HAS_TRAILING_NUL            0
-#endif
-
-#ifndef SV_COW_SHARED_HASH_KEYS
-#  define SV_COW_SHARED_HASH_KEYS        0
-#endif
-
-#if (PERL_BCDVERSION < 0x5007002)
-
-#if defined(NEED_sv_2pv_flags)
-static char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
-static
-#else
-extern char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
-#endif
-
-#ifdef sv_2pv_flags
-#  undef sv_2pv_flags
-#endif
-#define sv_2pv_flags(a,b,c) DPPP_(my_sv_2pv_flags)(aTHX_ a,b,c)
-#define Perl_sv_2pv_flags DPPP_(my_sv_2pv_flags)
-
-#if defined(NEED_sv_2pv_flags) || defined(NEED_sv_2pv_flags_GLOBAL)
-
-char *
-DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags)
-{
-  STRLEN n_a = (STRLEN) flags;
-  return sv_2pv(sv, lp ? lp : &n_a);
-}
-
-#endif
-
-#if defined(NEED_sv_pvn_force_flags)
-static char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
-static
-#else
-extern char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
-#endif
-
-#ifdef sv_pvn_force_flags
-#  undef sv_pvn_force_flags
-#endif
-#define sv_pvn_force_flags(a,b,c) DPPP_(my_sv_pvn_force_flags)(aTHX_ a,b,c)
-#define Perl_sv_pvn_force_flags DPPP_(my_sv_pvn_force_flags)
-
-#if defined(NEED_sv_pvn_force_flags) || defined(NEED_sv_pvn_force_flags_GLOBAL)
-
-char *
-DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags)
-{
-  STRLEN n_a = (STRLEN) flags;
-  return sv_pvn_force(sv, lp ? lp : &n_a);
-}
-
-#endif
-
-#endif
-
-#if (PERL_BCDVERSION < 0x5008008) || ( (PERL_BCDVERSION >= 0x5009000) && (PERL_BCDVERSION < 0x5009003) )
-# define DPPP_SVPV_NOLEN_LP_ARG &PL_na
-#else
-# define DPPP_SVPV_NOLEN_LP_ARG 0
-#endif
-#ifndef SvPV_const
-#  define SvPV_const(sv, lp)             SvPV_flags_const(sv, lp, SV_GMAGIC)
-#endif
-
-#ifndef SvPV_mutable
-#  define SvPV_mutable(sv, lp)           SvPV_flags_mutable(sv, lp, SV_GMAGIC)
-#endif
-#ifndef SvPV_flags
-#  define SvPV_flags(sv, lp, flags)      \
-                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
-                  ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pv_flags(sv, &lp, flags))
-#endif
-#ifndef SvPV_flags_const
-#  define SvPV_flags_const(sv, lp, flags) \
-                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
-                  ? ((lp = SvCUR(sv)), SvPVX_const(sv)) : \
-                  (const char*) sv_2pv_flags(sv, &lp, flags|SV_CONST_RETURN))
-#endif
-#ifndef SvPV_flags_const_nolen
-#  define SvPV_flags_const_nolen(sv, flags) \
-                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
-                  ? SvPVX_const(sv) : \
-                  (const char*) sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags|SV_CONST_RETURN))
-#endif
-#ifndef SvPV_flags_mutable
-#  define SvPV_flags_mutable(sv, lp, flags) \
-                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
-                  ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) : \
-                  sv_2pv_flags(sv, &lp, flags|SV_MUTABLE_RETURN))
-#endif
-#ifndef SvPV_force
-#  define SvPV_force(sv, lp)             SvPV_force_flags(sv, lp, SV_GMAGIC)
-#endif
-
-#ifndef SvPV_force_nolen
-#  define SvPV_force_nolen(sv)           SvPV_force_flags_nolen(sv, SV_GMAGIC)
-#endif
-
-#ifndef SvPV_force_mutable
-#  define SvPV_force_mutable(sv, lp)     SvPV_force_flags_mutable(sv, lp, SV_GMAGIC)
-#endif
-
-#ifndef SvPV_force_nomg
-#  define SvPV_force_nomg(sv, lp)        SvPV_force_flags(sv, lp, 0)
-#endif
-
-#ifndef SvPV_force_nomg_nolen
-#  define SvPV_force_nomg_nolen(sv)      SvPV_force_flags_nolen(sv, 0)
-#endif
-#ifndef SvPV_force_flags
-#  define SvPV_force_flags(sv, lp, flags) \
-                 ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
-                 ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_pvn_force_flags(sv, &lp, flags))
-#endif
-#ifndef SvPV_force_flags_nolen
-#  define SvPV_force_flags_nolen(sv, flags) \
-                 ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
-                 ? SvPVX(sv) : sv_pvn_force_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags))
-#endif
-#ifndef SvPV_force_flags_mutable
-#  define SvPV_force_flags_mutable(sv, lp, flags) \
-                 ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
-                 ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) \
-                  : sv_pvn_force_flags(sv, &lp, flags|SV_MUTABLE_RETURN))
-#endif
-#ifndef SvPV_nolen
-#  define SvPV_nolen(sv)                 \
-                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
-                  ? SvPVX(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC))
-#endif
-#ifndef SvPV_nolen_const
-#  define SvPV_nolen_const(sv)           \
-                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
-                  ? SvPVX_const(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC|SV_CONST_RETURN))
-#endif
-#ifndef SvPV_nomg
-#  define SvPV_nomg(sv, lp)              SvPV_flags(sv, lp, 0)
-#endif
-
-#ifndef SvPV_nomg_const
-#  define SvPV_nomg_const(sv, lp)        SvPV_flags_const(sv, lp, 0)
-#endif
-
-#ifndef SvPV_nomg_const_nolen
-#  define SvPV_nomg_const_nolen(sv)      SvPV_flags_const_nolen(sv, 0)
-#endif
-#ifndef SvPV_renew
-#  define SvPV_renew(sv,n)               STMT_START { SvLEN_set(sv, n); \
-                 SvPV_set((sv), (char *) saferealloc(          \
-                       (Malloc_t)SvPVX(sv), (MEM_SIZE)((n)))); \
-               } STMT_END
-#endif
-#ifndef SvMAGIC_set
-#  define SvMAGIC_set(sv, val)           \
-                STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \
-                (((XPVMG*) SvANY(sv))->xmg_magic = (val)); } STMT_END
-#endif
-
-#if (PERL_BCDVERSION < 0x5009003)
-#ifndef SvPVX_const
-#  define SvPVX_const(sv)                ((const char*) (0 + SvPVX(sv)))
-#endif
-
-#ifndef SvPVX_mutable
-#  define SvPVX_mutable(sv)              (0 + SvPVX(sv))
-#endif
-#ifndef SvRV_set
-#  define SvRV_set(sv, val)              \
-                STMT_START { assert(SvTYPE(sv) >=  SVt_RV); \
-                (((XRV*) SvANY(sv))->xrv_rv = (val)); } STMT_END
-#endif
-
-#else
-#ifndef SvPVX_const
-#  define SvPVX_const(sv)                ((const char*)((sv)->sv_u.svu_pv))
-#endif
-
-#ifndef SvPVX_mutable
-#  define SvPVX_mutable(sv)              ((sv)->sv_u.svu_pv)
-#endif
-#ifndef SvRV_set
-#  define SvRV_set(sv, val)              \
-                STMT_START { assert(SvTYPE(sv) >=  SVt_RV); \
-                ((sv)->sv_u.svu_rv = (val)); } STMT_END
-#endif
-
-#endif
-#ifndef SvSTASH_set
-#  define SvSTASH_set(sv, val)           \
-                STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \
-                (((XPVMG*) SvANY(sv))->xmg_stash = (val)); } STMT_END
-#endif
-
-#if (PERL_BCDVERSION < 0x5004000)
-#ifndef SvUV_set
-#  define SvUV_set(sv, val)              \
-                STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \
-                (((XPVIV*) SvANY(sv))->xiv_iv = (IV) (val)); } STMT_END
-#endif
-
-#else
-#ifndef SvUV_set
-#  define SvUV_set(sv, val)              \
-                STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \
-                (((XPVUV*) SvANY(sv))->xuv_uv = (val)); } STMT_END
-#endif
-
-#endif
-
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(vnewSVpvf)
-#if defined(NEED_vnewSVpvf)
-static SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args);
-static
-#else
-extern SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args);
-#endif
-
-#ifdef vnewSVpvf
-#  undef vnewSVpvf
-#endif
-#define vnewSVpvf(a,b) DPPP_(my_vnewSVpvf)(aTHX_ a,b)
-#define Perl_vnewSVpvf DPPP_(my_vnewSVpvf)
-
-#if defined(NEED_vnewSVpvf) || defined(NEED_vnewSVpvf_GLOBAL)
-
-SV *
-DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args)
-{
-  register SV *sv = newSV(0);
-  sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*));
-  return sv;
-}
-
-#endif
-#endif
-
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf)
-#  define sv_vcatpvf(sv, pat, args)  sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*))
-#endif
-
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf)
-#  define sv_vsetpvf(sv, pat, args)  sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*))
-#endif
-
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg)
-#if defined(NEED_sv_catpvf_mg)
-static void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
-static
-#else
-extern void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
-#endif
-
-#define Perl_sv_catpvf_mg DPPP_(my_sv_catpvf_mg)
-
-#if defined(NEED_sv_catpvf_mg) || defined(NEED_sv_catpvf_mg_GLOBAL)
-
-void
-DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...)
-{
-  va_list args;
-  va_start(args, pat);
-  sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
-  SvSETMAGIC(sv);
-  va_end(args);
-}
-
-#endif
-#endif
-
-#ifdef PERL_IMPLICIT_CONTEXT
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg_nocontext)
-#if defined(NEED_sv_catpvf_mg_nocontext)
-static void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...);
-static
-#else
-extern void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...);
-#endif
-
-#define sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext)
-#define Perl_sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext)
-
-#if defined(NEED_sv_catpvf_mg_nocontext) || defined(NEED_sv_catpvf_mg_nocontext_GLOBAL)
-
-void
-DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...)
-{
-  dTHX;
-  va_list args;
-  va_start(args, pat);
-  sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
-  SvSETMAGIC(sv);
-  va_end(args);
-}
-
-#endif
-#endif
-#endif
-
-/* sv_catpvf_mg depends on sv_catpvf_mg_nocontext */
-#ifndef sv_catpvf_mg
-#  ifdef PERL_IMPLICIT_CONTEXT
-#    define sv_catpvf_mg   Perl_sv_catpvf_mg_nocontext
-#  else
-#    define sv_catpvf_mg   Perl_sv_catpvf_mg
-#  endif
-#endif
-
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf_mg)
-#  define sv_vcatpvf_mg(sv, pat, args)                                     \
-   STMT_START {                                                            \
-     sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*));  \
-     SvSETMAGIC(sv);                                                       \
-   } STMT_END
-#endif
-
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg)
-#if defined(NEED_sv_setpvf_mg)
-static void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
-static
-#else
-extern void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
-#endif
-
-#define Perl_sv_setpvf_mg DPPP_(my_sv_setpvf_mg)
-
-#if defined(NEED_sv_setpvf_mg) || defined(NEED_sv_setpvf_mg_GLOBAL)
-
-void
-DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...)
-{
-  va_list args;
-  va_start(args, pat);
-  sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
-  SvSETMAGIC(sv);
-  va_end(args);
-}
-
-#endif
-#endif
-
-#ifdef PERL_IMPLICIT_CONTEXT
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg_nocontext)
-#if defined(NEED_sv_setpvf_mg_nocontext)
-static void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...);
-static
-#else
-extern void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...);
-#endif
-
-#define sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext)
-#define Perl_sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext)
-
-#if defined(NEED_sv_setpvf_mg_nocontext) || defined(NEED_sv_setpvf_mg_nocontext_GLOBAL)
-
-void
-DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...)
-{
-  dTHX;
-  va_list args;
-  va_start(args, pat);
-  sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
-  SvSETMAGIC(sv);
-  va_end(args);
-}
-
-#endif
-#endif
-#endif
-
-/* sv_setpvf_mg depends on sv_setpvf_mg_nocontext */
-#ifndef sv_setpvf_mg
-#  ifdef PERL_IMPLICIT_CONTEXT
-#    define sv_setpvf_mg   Perl_sv_setpvf_mg_nocontext
-#  else
-#    define sv_setpvf_mg   Perl_sv_setpvf_mg
-#  endif
-#endif
-
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf_mg)
-#  define sv_vsetpvf_mg(sv, pat, args)                                     \
-   STMT_START {                                                            \
-     sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*));  \
-     SvSETMAGIC(sv);                                                       \
-   } STMT_END
-#endif
-
-#ifndef newSVpvn_share
-
-#if defined(NEED_newSVpvn_share)
-static SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash);
-static
-#else
-extern SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash);
-#endif
-
-#ifdef newSVpvn_share
-#  undef newSVpvn_share
-#endif
-#define newSVpvn_share(a,b,c) DPPP_(my_newSVpvn_share)(aTHX_ a,b,c)
-#define Perl_newSVpvn_share DPPP_(my_newSVpvn_share)
-
-#if defined(NEED_newSVpvn_share) || defined(NEED_newSVpvn_share_GLOBAL)
-
-SV *
-DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash)
-{
-  SV *sv;
-  if (len < 0)
-    len = -len;
-  if (!hash)
-    PERL_HASH(hash, (char*) src, len);
-  sv = newSVpvn((char *) src, len);
-  sv_upgrade(sv, SVt_PVIV);
-  SvIVX(sv) = hash;
-  SvREADONLY_on(sv);
-  SvPOK_on(sv);
-  return sv;
-}
-
-#endif
-
-#endif
-#ifndef SvSHARED_HASH
-#  define SvSHARED_HASH(sv)              (0 + SvUVX(sv))
-#endif
-#ifndef HvNAME_get
-#  define HvNAME_get(hv)                 HvNAME(hv)
-#endif
-#ifndef HvNAMELEN_get
-#  define HvNAMELEN_get(hv)              (HvNAME_get(hv) ? (I32)strlen(HvNAME_get(hv)) : 0)
-#endif
-#ifndef GvSVn
-#  define GvSVn(gv)                      GvSV(gv)
-#endif
-
-#ifndef isGV_with_GP
-#  define isGV_with_GP(gv)               isGV(gv)
-#endif
-#ifndef WARN_ALL
-#  define WARN_ALL                       0
-#endif
-
-#ifndef WARN_CLOSURE
-#  define WARN_CLOSURE                   1
-#endif
-
-#ifndef WARN_DEPRECATED
-#  define WARN_DEPRECATED                2
-#endif
-
-#ifndef WARN_EXITING
-#  define WARN_EXITING                   3
-#endif
-
-#ifndef WARN_GLOB
-#  define WARN_GLOB                      4
-#endif
-
-#ifndef WARN_IO
-#  define WARN_IO                        5
-#endif
-
-#ifndef WARN_CLOSED
-#  define WARN_CLOSED                    6
-#endif
-
-#ifndef WARN_EXEC
-#  define WARN_EXEC                      7
-#endif
-
-#ifndef WARN_LAYER
-#  define WARN_LAYER                     8
-#endif
-
-#ifndef WARN_NEWLINE
-#  define WARN_NEWLINE                   9
-#endif
-
-#ifndef WARN_PIPE
-#  define WARN_PIPE                      10
-#endif
-
-#ifndef WARN_UNOPENED
-#  define WARN_UNOPENED                  11
-#endif
-
-#ifndef WARN_MISC
-#  define WARN_MISC                      12
-#endif
-
-#ifndef WARN_NUMERIC
-#  define WARN_NUMERIC                   13
-#endif
-
-#ifndef WARN_ONCE
-#  define WARN_ONCE                      14
-#endif
-
-#ifndef WARN_OVERFLOW
-#  define WARN_OVERFLOW                  15
-#endif
-
-#ifndef WARN_PACK
-#  define WARN_PACK                      16
-#endif
-
-#ifndef WARN_PORTABLE
-#  define WARN_PORTABLE                  17
-#endif
-
-#ifndef WARN_RECURSION
-#  define WARN_RECURSION                 18
-#endif
-
-#ifndef WARN_REDEFINE
-#  define WARN_REDEFINE                  19
-#endif
-
-#ifndef WARN_REGEXP
-#  define WARN_REGEXP                    20
-#endif
-
-#ifndef WARN_SEVERE
-#  define WARN_SEVERE                    21
-#endif
-
-#ifndef WARN_DEBUGGING
-#  define WARN_DEBUGGING                 22
-#endif
-
-#ifndef WARN_INPLACE
-#  define WARN_INPLACE                   23
-#endif
-
-#ifndef WARN_INTERNAL
-#  define WARN_INTERNAL                  24
-#endif
-
-#ifndef WARN_MALLOC
-#  define WARN_MALLOC                    25
-#endif
-
-#ifndef WARN_SIGNAL
-#  define WARN_SIGNAL                    26
-#endif
-
-#ifndef WARN_SUBSTR
-#  define WARN_SUBSTR                    27
-#endif
-
-#ifndef WARN_SYNTAX
-#  define WARN_SYNTAX                    28
-#endif
-
-#ifndef WARN_AMBIGUOUS
-#  define WARN_AMBIGUOUS                 29
-#endif
-
-#ifndef WARN_BAREWORD
-#  define WARN_BAREWORD                  30
-#endif
-
-#ifndef WARN_DIGIT
-#  define WARN_DIGIT                     31
-#endif
-
-#ifndef WARN_PARENTHESIS
-#  define WARN_PARENTHESIS               32
-#endif
-
-#ifndef WARN_PRECEDENCE
-#  define WARN_PRECEDENCE                33
-#endif
-
-#ifndef WARN_PRINTF
-#  define WARN_PRINTF                    34
-#endif
-
-#ifndef WARN_PROTOTYPE
-#  define WARN_PROTOTYPE                 35
-#endif
-
-#ifndef WARN_QW
-#  define WARN_QW                        36
-#endif
-
-#ifndef WARN_RESERVED
-#  define WARN_RESERVED                  37
-#endif
-
-#ifndef WARN_SEMICOLON
-#  define WARN_SEMICOLON                 38
-#endif
-
-#ifndef WARN_TAINT
-#  define WARN_TAINT                     39
-#endif
-
-#ifndef WARN_THREADS
-#  define WARN_THREADS                   40
-#endif
-
-#ifndef WARN_UNINITIALIZED
-#  define WARN_UNINITIALIZED             41
-#endif
-
-#ifndef WARN_UNPACK
-#  define WARN_UNPACK                    42
-#endif
-
-#ifndef WARN_UNTIE
-#  define WARN_UNTIE                     43
-#endif
-
-#ifndef WARN_UTF8
-#  define WARN_UTF8                      44
-#endif
-
-#ifndef WARN_VOID
-#  define WARN_VOID                      45
-#endif
-
-#ifndef WARN_ASSERTIONS
-#  define WARN_ASSERTIONS                46
-#endif
-#ifndef packWARN
-#  define packWARN(a)                    (a)
-#endif
-
-#ifndef ckWARN
-#  ifdef G_WARN_ON
-#    define  ckWARN(a)                  (PL_dowarn & G_WARN_ON)
-#  else
-#    define  ckWARN(a)                  PL_dowarn
-#  endif
-#endif
-
-#if (PERL_BCDVERSION >= 0x5004000) && !defined(warner)
-#if defined(NEED_warner)
-static void DPPP_(my_warner)(U32 err, const char *pat, ...);
-static
-#else
-extern void DPPP_(my_warner)(U32 err, const char *pat, ...);
-#endif
-
-#define Perl_warner DPPP_(my_warner)
-
-#if defined(NEED_warner) || defined(NEED_warner_GLOBAL)
-
-void
-DPPP_(my_warner)(U32 err, const char *pat, ...)
-{
-  SV *sv;
-  va_list args;
-
-  PERL_UNUSED_ARG(err);
-
-  va_start(args, pat);
-  sv = vnewSVpvf(pat, &args);
-  va_end(args);
-  sv_2mortal(sv);
-  warn("%s", SvPV_nolen(sv));
-}
-
-#define warner  Perl_warner
-
-#define Perl_warner_nocontext  Perl_warner
-
-#endif
-#endif
-
-/* concatenating with "" ensures that only literal strings are accepted as argument
- * note that STR_WITH_LEN() can't be used as argument to macros or functions that
- * under some configurations might be macros
- */
-#ifndef STR_WITH_LEN
-#  define STR_WITH_LEN(s)                (s ""), (sizeof(s)-1)
-#endif
-#ifndef newSVpvs
-#  define newSVpvs(str)                  newSVpvn(str "", sizeof(str) - 1)
-#endif
-
-#ifndef newSVpvs_flags
-#  define newSVpvs_flags(str, flags)     newSVpvn_flags(str "", sizeof(str) - 1, flags)
-#endif
-
-#ifndef sv_catpvs
-#  define sv_catpvs(sv, str)             sv_catpvn(sv, str "", sizeof(str) - 1)
-#endif
-
-#ifndef sv_setpvs
-#  define sv_setpvs(sv, str)             sv_setpvn(sv, str "", sizeof(str) - 1)
-#endif
-
-#ifndef hv_fetchs
-#  define hv_fetchs(hv, key, lval)       hv_fetch(hv, key "", sizeof(key) - 1, lval)
-#endif
-
-#ifndef hv_stores
-#  define hv_stores(hv, key, val)        hv_store(hv, key "", sizeof(key) - 1, val, 0)
-#endif
-#ifndef gv_fetchpvn_flags
-#  define gv_fetchpvn_flags(name, len, flags, svt) gv_fetchpv(name, flags, svt)
-#endif
-
-#ifndef gv_fetchpvs
-#  define gv_fetchpvs(name, flags, svt)  gv_fetchpvn_flags(name "", sizeof(name) - 1, flags, svt)
-#endif
-
-#ifndef gv_stashpvs
-#  define gv_stashpvs(name, flags)       gv_stashpvn(name "", sizeof(name) - 1, flags)
-#endif
-#ifndef SvGETMAGIC
-#  define SvGETMAGIC(x)                  STMT_START { if (SvGMAGICAL(x)) mg_get(x); } STMT_END
-#endif
-#ifndef PERL_MAGIC_sv
-#  define PERL_MAGIC_sv                  '\0'
-#endif
-
-#ifndef PERL_MAGIC_overload
-#  define PERL_MAGIC_overload            'A'
-#endif
-
-#ifndef PERL_MAGIC_overload_elem
-#  define PERL_MAGIC_overload_elem       'a'
-#endif
-
-#ifndef PERL_MAGIC_overload_table
-#  define PERL_MAGIC_overload_table      'c'
-#endif
-
-#ifndef PERL_MAGIC_bm
-#  define PERL_MAGIC_bm                  'B'
-#endif
-
-#ifndef PERL_MAGIC_regdata
-#  define PERL_MAGIC_regdata             'D'
-#endif
-
-#ifndef PERL_MAGIC_regdatum
-#  define PERL_MAGIC_regdatum            'd'
-#endif
-
-#ifndef PERL_MAGIC_env
-#  define PERL_MAGIC_env                 'E'
-#endif
-
-#ifndef PERL_MAGIC_envelem
-#  define PERL_MAGIC_envelem             'e'
-#endif
-
-#ifndef PERL_MAGIC_fm
-#  define PERL_MAGIC_fm                  'f'
-#endif
-
-#ifndef PERL_MAGIC_regex_global
-#  define PERL_MAGIC_regex_global        'g'
-#endif
-
-#ifndef PERL_MAGIC_isa
-#  define PERL_MAGIC_isa                 'I'
-#endif
-
-#ifndef PERL_MAGIC_isaelem
-#  define PERL_MAGIC_isaelem             'i'
-#endif
-
-#ifndef PERL_MAGIC_nkeys
-#  define PERL_MAGIC_nkeys               'k'
-#endif
-
-#ifndef PERL_MAGIC_dbfile
-#  define PERL_MAGIC_dbfile              'L'
-#endif
-
-#ifndef PERL_MAGIC_dbline
-#  define PERL_MAGIC_dbline              'l'
-#endif
-
-#ifndef PERL_MAGIC_mutex
-#  define PERL_MAGIC_mutex               'm'
-#endif
-
-#ifndef PERL_MAGIC_shared
-#  define PERL_MAGIC_shared              'N'
-#endif
-
-#ifndef PERL_MAGIC_shared_scalar
-#  define PERL_MAGIC_shared_scalar       'n'
-#endif
-
-#ifndef PERL_MAGIC_collxfrm
-#  define PERL_MAGIC_collxfrm            'o'
-#endif
-
-#ifndef PERL_MAGIC_tied
-#  define PERL_MAGIC_tied                'P'
-#endif
-
-#ifndef PERL_MAGIC_tiedelem
-#  define PERL_MAGIC_tiedelem            'p'
-#endif
-
-#ifndef PERL_MAGIC_tiedscalar
-#  define PERL_MAGIC_tiedscalar          'q'
-#endif
-
-#ifndef PERL_MAGIC_qr
-#  define PERL_MAGIC_qr                  'r'
-#endif
-
-#ifndef PERL_MAGIC_sig
-#  define PERL_MAGIC_sig                 'S'
-#endif
-
-#ifndef PERL_MAGIC_sigelem
-#  define PERL_MAGIC_sigelem             's'
-#endif
-
-#ifndef PERL_MAGIC_taint
-#  define PERL_MAGIC_taint               't'
-#endif
-
-#ifndef PERL_MAGIC_uvar
-#  define PERL_MAGIC_uvar                'U'
-#endif
-
-#ifndef PERL_MAGIC_uvar_elem
-#  define PERL_MAGIC_uvar_elem           'u'
-#endif
-
-#ifndef PERL_MAGIC_vstring
-#  define PERL_MAGIC_vstring             'V'
-#endif
-
-#ifndef PERL_MAGIC_vec
-#  define PERL_MAGIC_vec                 'v'
-#endif
-
-#ifndef PERL_MAGIC_utf8
-#  define PERL_MAGIC_utf8                'w'
-#endif
-
-#ifndef PERL_MAGIC_substr
-#  define PERL_MAGIC_substr              'x'
-#endif
-
-#ifndef PERL_MAGIC_defelem
-#  define PERL_MAGIC_defelem             'y'
-#endif
-
-#ifndef PERL_MAGIC_glob
-#  define PERL_MAGIC_glob                '*'
-#endif
-
-#ifndef PERL_MAGIC_arylen
-#  define PERL_MAGIC_arylen              '#'
-#endif
-
-#ifndef PERL_MAGIC_pos
-#  define PERL_MAGIC_pos                 '.'
-#endif
-
-#ifndef PERL_MAGIC_backref
-#  define PERL_MAGIC_backref             '<'
-#endif
-
-#ifndef PERL_MAGIC_ext
-#  define PERL_MAGIC_ext                 '~'
-#endif
-
-/* That's the best we can do... */
-#ifndef sv_catpvn_nomg
-#  define sv_catpvn_nomg                 sv_catpvn
-#endif
-
-#ifndef sv_catsv_nomg
-#  define sv_catsv_nomg                  sv_catsv
-#endif
-
-#ifndef sv_setsv_nomg
-#  define sv_setsv_nomg                  sv_setsv
-#endif
-
-#ifndef sv_pvn_nomg
-#  define sv_pvn_nomg                    sv_pvn
-#endif
-
-#ifndef SvIV_nomg
-#  define SvIV_nomg                      SvIV
-#endif
-
-#ifndef SvUV_nomg
-#  define SvUV_nomg                      SvUV
-#endif
-
-#ifndef sv_catpv_mg
-#  define sv_catpv_mg(sv, ptr)          \
-   STMT_START {                         \
-     SV *TeMpSv = sv;                   \
-     sv_catpv(TeMpSv,ptr);              \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-
-#ifndef sv_catpvn_mg
-#  define sv_catpvn_mg(sv, ptr, len)    \
-   STMT_START {                         \
-     SV *TeMpSv = sv;                   \
-     sv_catpvn(TeMpSv,ptr,len);         \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-
-#ifndef sv_catsv_mg
-#  define sv_catsv_mg(dsv, ssv)         \
-   STMT_START {                         \
-     SV *TeMpSv = dsv;                  \
-     sv_catsv(TeMpSv,ssv);              \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-
-#ifndef sv_setiv_mg
-#  define sv_setiv_mg(sv, i)            \
-   STMT_START {                         \
-     SV *TeMpSv = sv;                   \
-     sv_setiv(TeMpSv,i);                \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-
-#ifndef sv_setnv_mg
-#  define sv_setnv_mg(sv, num)          \
-   STMT_START {                         \
-     SV *TeMpSv = sv;                   \
-     sv_setnv(TeMpSv,num);              \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-
-#ifndef sv_setpv_mg
-#  define sv_setpv_mg(sv, ptr)          \
-   STMT_START {                         \
-     SV *TeMpSv = sv;                   \
-     sv_setpv(TeMpSv,ptr);              \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-
-#ifndef sv_setpvn_mg
-#  define sv_setpvn_mg(sv, ptr, len)    \
-   STMT_START {                         \
-     SV *TeMpSv = sv;                   \
-     sv_setpvn(TeMpSv,ptr,len);         \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-
-#ifndef sv_setsv_mg
-#  define sv_setsv_mg(dsv, ssv)         \
-   STMT_START {                         \
-     SV *TeMpSv = dsv;                  \
-     sv_setsv(TeMpSv,ssv);              \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-
-#ifndef sv_setuv_mg
-#  define sv_setuv_mg(sv, i)            \
-   STMT_START {                         \
-     SV *TeMpSv = sv;                   \
-     sv_setuv(TeMpSv,i);                \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-
-#ifndef sv_usepvn_mg
-#  define sv_usepvn_mg(sv, ptr, len)    \
-   STMT_START {                         \
-     SV *TeMpSv = sv;                   \
-     sv_usepvn(TeMpSv,ptr,len);         \
-     SvSETMAGIC(TeMpSv);                \
-   } STMT_END
-#endif
-#ifndef SvVSTRING_mg
-#  define SvVSTRING_mg(sv)               (SvMAGICAL(sv) ? mg_find(sv, PERL_MAGIC_vstring) : NULL)
-#endif
-
-/* Hint: sv_magic_portable
- * This is a compatibility function that is only available with
- * Devel::PPPort. It is NOT in the perl core.
- * Its purpose is to mimic the 5.8.0 behaviour of sv_magic() when
- * it is being passed a name pointer with namlen == 0. In that
- * case, perl 5.8.0 and later store the pointer, not a copy of it.
- * The compatibility can be provided back to perl 5.004. With
- * earlier versions, the code will not compile.
- */
-
-#if (PERL_BCDVERSION < 0x5004000)
-
-  /* code that uses sv_magic_portable will not compile */
-
-#elif (PERL_BCDVERSION < 0x5008000)
-
-#  define sv_magic_portable(sv, obj, how, name, namlen)     \
-   STMT_START {                                             \
-     SV *SvMp_sv = (sv);                                    \
-     char *SvMp_name = (char *) (name);                     \
-     I32 SvMp_namlen = (namlen);                            \
-     if (SvMp_name && SvMp_namlen == 0)                     \
-     {                                                      \
-       MAGIC *mg;                                           \
-       sv_magic(SvMp_sv, obj, how, 0, 0);                   \
-       mg = SvMAGIC(SvMp_sv);                               \
-       mg->mg_len = -42; /* XXX: this is the tricky part */ \
-       mg->mg_ptr = SvMp_name;                              \
-     }                                                      \
-     else                                                   \
-     {                                                      \
-       sv_magic(SvMp_sv, obj, how, SvMp_name, SvMp_namlen); \
-     }                                                      \
-   } STMT_END
-
-#else
-
-#  define sv_magic_portable(a, b, c, d, e)  sv_magic(a, b, c, d, e)
-
-#endif
-
-#ifdef USE_ITHREADS
-#ifndef CopFILE
-#  define CopFILE(c)                     ((c)->cop_file)
-#endif
-
-#ifndef CopFILEGV
-#  define CopFILEGV(c)                   (CopFILE(c) ? gv_fetchfile(CopFILE(c)) : Nullgv)
-#endif
-
-#ifndef CopFILE_set
-#  define CopFILE_set(c,pv)              ((c)->cop_file = savepv(pv))
-#endif
-
-#ifndef CopFILESV
-#  define CopFILESV(c)                   (CopFILE(c) ? GvSV(gv_fetchfile(CopFILE(c))) : Nullsv)
-#endif
-
-#ifndef CopFILEAV
-#  define CopFILEAV(c)                   (CopFILE(c) ? GvAV(gv_fetchfile(CopFILE(c))) : Nullav)
-#endif
-
-#ifndef CopSTASHPV
-#  define CopSTASHPV(c)                  ((c)->cop_stashpv)
-#endif
-
-#ifndef CopSTASHPV_set
-#  define CopSTASHPV_set(c,pv)           ((c)->cop_stashpv = ((pv) ? savepv(pv) : Nullch))
-#endif
-
-#ifndef CopSTASH
-#  define CopSTASH(c)                    (CopSTASHPV(c) ? gv_stashpv(CopSTASHPV(c),GV_ADD) : Nullhv)
-#endif
-
-#ifndef CopSTASH_set
-#  define CopSTASH_set(c,hv)             CopSTASHPV_set(c, (hv) ? HvNAME(hv) : Nullch)
-#endif
-
-#ifndef CopSTASH_eq
-#  define CopSTASH_eq(c,hv)              ((hv) && (CopSTASHPV(c) == HvNAME(hv) \
-                                       || (CopSTASHPV(c) && HvNAME(hv) \
-                                       && strEQ(CopSTASHPV(c), HvNAME(hv)))))
-#endif
-
-#else
-#ifndef CopFILEGV
-#  define CopFILEGV(c)                   ((c)->cop_filegv)
-#endif
-
-#ifndef CopFILEGV_set
-#  define CopFILEGV_set(c,gv)            ((c)->cop_filegv = (GV*)SvREFCNT_inc(gv))
-#endif
-
-#ifndef CopFILE_set
-#  define CopFILE_set(c,pv)              CopFILEGV_set((c), gv_fetchfile(pv))
-#endif
-
-#ifndef CopFILESV
-#  define CopFILESV(c)                   (CopFILEGV(c) ? GvSV(CopFILEGV(c)) : Nullsv)
-#endif
-
-#ifndef CopFILEAV
-#  define CopFILEAV(c)                   (CopFILEGV(c) ? GvAV(CopFILEGV(c)) : Nullav)
-#endif
-
-#ifndef CopFILE
-#  define CopFILE(c)                     (CopFILESV(c) ? SvPVX(CopFILESV(c)) : Nullch)
-#endif
-
-#ifndef CopSTASH
-#  define CopSTASH(c)                    ((c)->cop_stash)
-#endif
-
-#ifndef CopSTASH_set
-#  define CopSTASH_set(c,hv)             ((c)->cop_stash = (hv))
-#endif
-
-#ifndef CopSTASHPV
-#  define CopSTASHPV(c)                  (CopSTASH(c) ? HvNAME(CopSTASH(c)) : Nullch)
-#endif
-
-#ifndef CopSTASHPV_set
-#  define CopSTASHPV_set(c,pv)           CopSTASH_set((c), gv_stashpv(pv,GV_ADD))
-#endif
-
-#ifndef CopSTASH_eq
-#  define CopSTASH_eq(c,hv)              (CopSTASH(c) == (hv))
-#endif
-
-#endif /* USE_ITHREADS */
-#ifndef IN_PERL_COMPILETIME
-#  define IN_PERL_COMPILETIME            (PL_curcop == &PL_compiling)
-#endif
-
-#ifndef IN_LOCALE_RUNTIME
-#  define IN_LOCALE_RUNTIME              (PL_curcop->op_private & HINT_LOCALE)
-#endif
-
-#ifndef IN_LOCALE_COMPILETIME
-#  define IN_LOCALE_COMPILETIME          (PL_hints & HINT_LOCALE)
-#endif
-
-#ifndef IN_LOCALE
-#  define IN_LOCALE                      (IN_PERL_COMPILETIME ? IN_LOCALE_COMPILETIME : IN_LOCALE_RUNTIME)
-#endif
-#ifndef IS_NUMBER_IN_UV
-#  define IS_NUMBER_IN_UV                0x01
-#endif
-
-#ifndef IS_NUMBER_GREATER_THAN_UV_MAX
-#  define IS_NUMBER_GREATER_THAN_UV_MAX  0x02
-#endif
-
-#ifndef IS_NUMBER_NOT_INT
-#  define IS_NUMBER_NOT_INT              0x04
-#endif
-
-#ifndef IS_NUMBER_NEG
-#  define IS_NUMBER_NEG                  0x08
-#endif
-
-#ifndef IS_NUMBER_INFINITY
-#  define IS_NUMBER_INFINITY             0x10
-#endif
-
-#ifndef IS_NUMBER_NAN
-#  define IS_NUMBER_NAN                  0x20
-#endif
-#ifndef GROK_NUMERIC_RADIX
-#  define GROK_NUMERIC_RADIX(sp, send)   grok_numeric_radix(sp, send)
-#endif
-#ifndef PERL_SCAN_GREATER_THAN_UV_MAX
-#  define PERL_SCAN_GREATER_THAN_UV_MAX  0x02
-#endif
-
-#ifndef PERL_SCAN_SILENT_ILLDIGIT
-#  define PERL_SCAN_SILENT_ILLDIGIT      0x04
-#endif
-
-#ifndef PERL_SCAN_ALLOW_UNDERSCORES
-#  define PERL_SCAN_ALLOW_UNDERSCORES    0x01
-#endif
-
-#ifndef PERL_SCAN_DISALLOW_PREFIX
-#  define PERL_SCAN_DISALLOW_PREFIX      0x02
-#endif
-
-#ifndef grok_numeric_radix
-#if defined(NEED_grok_numeric_radix)
-static bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send);
-static
-#else
-extern bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send);
-#endif
-
-#ifdef grok_numeric_radix
-#  undef grok_numeric_radix
-#endif
-#define grok_numeric_radix(a,b) DPPP_(my_grok_numeric_radix)(aTHX_ a,b)
-#define Perl_grok_numeric_radix DPPP_(my_grok_numeric_radix)
-
-#if defined(NEED_grok_numeric_radix) || defined(NEED_grok_numeric_radix_GLOBAL)
-bool
-DPPP_(my_grok_numeric_radix)(pTHX_ const char **sp, const char *send)
-{
-#ifdef USE_LOCALE_NUMERIC
-#ifdef PL_numeric_radix_sv
-    if (PL_numeric_radix_sv && IN_LOCALE) {
-        STRLEN len;
-        char* radix = SvPV(PL_numeric_radix_sv, len);
-        if (*sp + len <= send && memEQ(*sp, radix, len)) {
-            *sp += len;
-            return TRUE;
-        }
-    }
-#else
-    /* older perls don't have PL_numeric_radix_sv so the radix
-     * must manually be requested from locale.h
-     */
-#include <locale.h>
-    dTHR;  /* needed for older threaded perls */
-    struct lconv *lc = localeconv();
-    char *radix = lc->decimal_point;
-    if (radix && IN_LOCALE) {
-        STRLEN len = strlen(radix);
-        if (*sp + len <= send && memEQ(*sp, radix, len)) {
-            *sp += len;
-            return TRUE;
-        }
-    }
-#endif
-#endif /* USE_LOCALE_NUMERIC */
-    /* always try "." if numeric radix didn't match because
-     * we may have data from different locales mixed */
-    if (*sp < send && **sp == '.') {
-        ++*sp;
-        return TRUE;
-    }
-    return FALSE;
-}
-#endif
-#endif
-
-#ifndef grok_number
-#if defined(NEED_grok_number)
-static int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep);
-static
-#else
-extern int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep);
-#endif
-
-#ifdef grok_number
-#  undef grok_number
-#endif
-#define grok_number(a,b,c) DPPP_(my_grok_number)(aTHX_ a,b,c)
-#define Perl_grok_number DPPP_(my_grok_number)
-
-#if defined(NEED_grok_number) || defined(NEED_grok_number_GLOBAL)
-int
-DPPP_(my_grok_number)(pTHX_ const char *pv, STRLEN len, UV *valuep)
-{
-  const char *s = pv;
-  const char *send = pv + len;
-  const UV max_div_10 = UV_MAX / 10;
-  const char max_mod_10 = UV_MAX % 10;
-  int numtype = 0;
-  int sawinf = 0;
-  int sawnan = 0;
-
-  while (s < send && isSPACE(*s))
-    s++;
-  if (s == send) {
-    return 0;
-  } else if (*s == '-') {
-    s++;
-    numtype = IS_NUMBER_NEG;
-  }
-  else if (*s == '+')
-  s++;
-
-  if (s == send)
-    return 0;
-
-  /* next must be digit or the radix separator or beginning of infinity */
-  if (isDIGIT(*s)) {
-    /* UVs are at least 32 bits, so the first 9 decimal digits cannot
-       overflow.  */
-    UV value = *s - '0';
-    /* This construction seems to be more optimiser friendly.
-       (without it gcc does the isDIGIT test and the *s - '0' separately)
-       With it gcc on arm is managing 6 instructions (6 cycles) per digit.
-       In theory the optimiser could deduce how far to unroll the loop
-       before checking for overflow.  */
-    if (++s < send) {
-      int digit = *s - '0';
-      if (digit >= 0 && digit <= 9) {
-        value = value * 10 + digit;
-        if (++s < send) {
-          digit = *s - '0';
-          if (digit >= 0 && digit <= 9) {
-            value = value * 10 + digit;
-            if (++s < send) {
-              digit = *s - '0';
-              if (digit >= 0 && digit <= 9) {
-                value = value * 10 + digit;
-               if (++s < send) {
-                  digit = *s - '0';
-                  if (digit >= 0 && digit <= 9) {
-                    value = value * 10 + digit;
-                    if (++s < send) {
-                      digit = *s - '0';
-                      if (digit >= 0 && digit <= 9) {
-                        value = value * 10 + digit;
-                        if (++s < send) {
-                          digit = *s - '0';
-                          if (digit >= 0 && digit <= 9) {
-                            value = value * 10 + digit;
-                            if (++s < send) {
-                              digit = *s - '0';
-                              if (digit >= 0 && digit <= 9) {
-                                value = value * 10 + digit;
-                                if (++s < send) {
-                                  digit = *s - '0';
-                                  if (digit >= 0 && digit <= 9) {
-                                    value = value * 10 + digit;
-                                    if (++s < send) {
-                                      /* Now got 9 digits, so need to check
-                                         each time for overflow.  */
-                                      digit = *s - '0';
-                                      while (digit >= 0 && digit <= 9
-                                             && (value < max_div_10
-                                                 || (value == max_div_10
-                                                     && digit <= max_mod_10))) {
-                                        value = value * 10 + digit;
-                                        if (++s < send)
-                                          digit = *s - '0';
-                                        else
-                                          break;
-                                      }
-                                      if (digit >= 0 && digit <= 9
-                                          && (s < send)) {
-                                        /* value overflowed.
-                                           skip the remaining digits, don't
-                                           worry about setting *valuep.  */
-                                        do {
-                                          s++;
-                                        } while (s < send && isDIGIT(*s));
-                                        numtype |=
-                                          IS_NUMBER_GREATER_THAN_UV_MAX;
-                                        goto skip_value;
-                                      }
-                                    }
-                                  }
-                               }
-                              }
-                            }
-                          }
-                        }
-                      }
-                    }
-                  }
-                }
-              }
-            }
-          }
-       }
-      }
-    }
-    numtype |= IS_NUMBER_IN_UV;
-    if (valuep)
-      *valuep = value;
-
-  skip_value:
-    if (GROK_NUMERIC_RADIX(&s, send)) {
-      numtype |= IS_NUMBER_NOT_INT;
-      while (s < send && isDIGIT(*s))  /* optional digits after the radix */
-        s++;
-    }
-  }
-  else if (GROK_NUMERIC_RADIX(&s, send)) {
-    numtype |= IS_NUMBER_NOT_INT | IS_NUMBER_IN_UV; /* valuep assigned below */
-    /* no digits before the radix means we need digits after it */
-    if (s < send && isDIGIT(*s)) {
-      do {
-        s++;
-      } while (s < send && isDIGIT(*s));
-      if (valuep) {
-        /* integer approximation is valid - it's 0.  */
-        *valuep = 0;
-      }
-    }
-    else
-      return 0;
-  } else if (*s == 'I' || *s == 'i') {
-    s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
-    s++; if (s == send || (*s != 'F' && *s != 'f')) return 0;
-    s++; if (s < send && (*s == 'I' || *s == 'i')) {
-      s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
-      s++; if (s == send || (*s != 'I' && *s != 'i')) return 0;
-      s++; if (s == send || (*s != 'T' && *s != 't')) return 0;
-      s++; if (s == send || (*s != 'Y' && *s != 'y')) return 0;
-      s++;
-    }
-    sawinf = 1;
-  } else if (*s == 'N' || *s == 'n') {
-    /* XXX TODO: There are signaling NaNs and quiet NaNs. */
-    s++; if (s == send || (*s != 'A' && *s != 'a')) return 0;
-    s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
-    s++;
-    sawnan = 1;
-  } else
-    return 0;
-
-  if (sawinf) {
-    numtype &= IS_NUMBER_NEG; /* Keep track of sign  */
-    numtype |= IS_NUMBER_INFINITY | IS_NUMBER_NOT_INT;
-  } else if (sawnan) {
-    numtype &= IS_NUMBER_NEG; /* Keep track of sign  */
-    numtype |= IS_NUMBER_NAN | IS_NUMBER_NOT_INT;
-  } else if (s < send) {
-    /* we can have an optional exponent part */
-    if (*s == 'e' || *s == 'E') {
-      /* The only flag we keep is sign.  Blow away any "it's UV"  */
-      numtype &= IS_NUMBER_NEG;
-      numtype |= IS_NUMBER_NOT_INT;
-      s++;
-      if (s < send && (*s == '-' || *s == '+'))
-        s++;
-      if (s < send && isDIGIT(*s)) {
-        do {
-          s++;
-        } while (s < send && isDIGIT(*s));
-      }
-      else
-      return 0;
-    }
-  }
-  while (s < send && isSPACE(*s))
-    s++;
-  if (s >= send)
-    return numtype;
-  if (len == 10 && memEQ(pv, "0 but true", 10)) {
-    if (valuep)
-      *valuep = 0;
-    return IS_NUMBER_IN_UV;
-  }
-  return 0;
-}
-#endif
-#endif
-
-/*
- * The grok_* routines have been modified to use warn() instead of
- * Perl_warner(). Also, 'hexdigit' was the former name of PL_hexdigit,
- * which is why the stack variable has been renamed to 'xdigit'.
- */
-
-#ifndef grok_bin
-#if defined(NEED_grok_bin)
-static UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
-static
-#else
-extern UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
-#endif
-
-#ifdef grok_bin
-#  undef grok_bin
-#endif
-#define grok_bin(a,b,c,d) DPPP_(my_grok_bin)(aTHX_ a,b,c,d)
-#define Perl_grok_bin DPPP_(my_grok_bin)
-
-#if defined(NEED_grok_bin) || defined(NEED_grok_bin_GLOBAL)
-UV
-DPPP_(my_grok_bin)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
-{
-    const char *s = start;
-    STRLEN len = *len_p;
-    UV value = 0;
-    NV value_nv = 0;
-
-    const UV max_div_2 = UV_MAX / 2;
-    bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
-    bool overflowed = FALSE;
-
-    if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) {
-        /* strip off leading b or 0b.
-           for compatibility silently suffer "b" and "0b" as valid binary
-           numbers. */
-        if (len >= 1) {
-            if (s[0] == 'b') {
-                s++;
-                len--;
-            }
-            else if (len >= 2 && s[0] == '0' && s[1] == 'b') {
-                s+=2;
-                len-=2;
-            }
-        }
-    }
-
-    for (; len-- && *s; s++) {
-        char bit = *s;
-        if (bit == '0' || bit == '1') {
-            /* Write it in this wonky order with a goto to attempt to get the
-               compiler to make the common case integer-only loop pretty tight.
-               With gcc seems to be much straighter code than old scan_bin.  */
-          redo:
-            if (!overflowed) {
-                if (value <= max_div_2) {
-                    value = (value << 1) | (bit - '0');
-                    continue;
-                }
-                /* Bah. We're just overflowed.  */
-                warn("Integer overflow in binary number");
-                overflowed = TRUE;
-                value_nv = (NV) value;
-            }
-            value_nv *= 2.0;
-           /* If an NV has not enough bits in its mantissa to
-            * represent a UV this summing of small low-order numbers
-            * is a waste of time (because the NV cannot preserve
-            * the low-order bits anyway): we could just remember when
-            * did we overflow and in the end just multiply value_nv by the
-            * right amount. */
-            value_nv += (NV)(bit - '0');
-            continue;
-        }
-        if (bit == '_' && len && allow_underscores && (bit = s[1])
-            && (bit == '0' || bit == '1'))
-           {
-               --len;
-               ++s;
-                goto redo;
-           }
-        if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
-            warn("Illegal binary digit '%c' ignored", *s);
-        break;
-    }
-
-    if (   ( overflowed && value_nv > 4294967295.0)
-#if UVSIZE > 4
-       || (!overflowed && value > 0xffffffff  )
-#endif
-       ) {
-       warn("Binary number > 0b11111111111111111111111111111111 non-portable");
-    }
-    *len_p = s - start;
-    if (!overflowed) {
-        *flags = 0;
-        return value;
-    }
-    *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
-    if (result)
-        *result = value_nv;
-    return UV_MAX;
-}
-#endif
-#endif
-
-#ifndef grok_hex
-#if defined(NEED_grok_hex)
-static UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
-static
-#else
-extern UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
-#endif
-
-#ifdef grok_hex
-#  undef grok_hex
-#endif
-#define grok_hex(a,b,c,d) DPPP_(my_grok_hex)(aTHX_ a,b,c,d)
-#define Perl_grok_hex DPPP_(my_grok_hex)
-
-#if defined(NEED_grok_hex) || defined(NEED_grok_hex_GLOBAL)
-UV
-DPPP_(my_grok_hex)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
-{
-    const char *s = start;
-    STRLEN len = *len_p;
-    UV value = 0;
-    NV value_nv = 0;
-
-    const UV max_div_16 = UV_MAX / 16;
-    bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
-    bool overflowed = FALSE;
-    const char *xdigit;
-
-    if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) {
-        /* strip off leading x or 0x.
-           for compatibility silently suffer "x" and "0x" as valid hex numbers.
-        */
-        if (len >= 1) {
-            if (s[0] == 'x') {
-                s++;
-                len--;
-            }
-            else if (len >= 2 && s[0] == '0' && s[1] == 'x') {
-                s+=2;
-                len-=2;
-            }
-        }
-    }
-
-    for (; len-- && *s; s++) {
-       xdigit = strchr((char *) PL_hexdigit, *s);
-        if (xdigit) {
-            /* Write it in this wonky order with a goto to attempt to get the
-               compiler to make the common case integer-only loop pretty tight.
-               With gcc seems to be much straighter code than old scan_hex.  */
-          redo:
-            if (!overflowed) {
-                if (value <= max_div_16) {
-                    value = (value << 4) | ((xdigit - PL_hexdigit) & 15);
-                    continue;
-                }
-                warn("Integer overflow in hexadecimal number");
-                overflowed = TRUE;
-                value_nv = (NV) value;
-            }
-            value_nv *= 16.0;
-           /* If an NV has not enough bits in its mantissa to
-            * represent a UV this summing of small low-order numbers
-            * is a waste of time (because the NV cannot preserve
-            * the low-order bits anyway): we could just remember when
-            * did we overflow and in the end just multiply value_nv by the
-            * right amount of 16-tuples. */
-            value_nv += (NV)((xdigit - PL_hexdigit) & 15);
-            continue;
-        }
-        if (*s == '_' && len && allow_underscores && s[1]
-               && (xdigit = strchr((char *) PL_hexdigit, s[1])))
-           {
-               --len;
-               ++s;
-                goto redo;
-           }
-        if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
-            warn("Illegal hexadecimal digit '%c' ignored", *s);
-        break;
-    }
-
-    if (   ( overflowed && value_nv > 4294967295.0)
-#if UVSIZE > 4
-       || (!overflowed && value > 0xffffffff  )
-#endif
-       ) {
-       warn("Hexadecimal number > 0xffffffff non-portable");
-    }
-    *len_p = s - start;
-    if (!overflowed) {
-        *flags = 0;
-        return value;
-    }
-    *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
-    if (result)
-        *result = value_nv;
-    return UV_MAX;
-}
-#endif
-#endif
-
-#ifndef grok_oct
-#if defined(NEED_grok_oct)
-static UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
-static
-#else
-extern UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
-#endif
-
-#ifdef grok_oct
-#  undef grok_oct
-#endif
-#define grok_oct(a,b,c,d) DPPP_(my_grok_oct)(aTHX_ a,b,c,d)
-#define Perl_grok_oct DPPP_(my_grok_oct)
-
-#if defined(NEED_grok_oct) || defined(NEED_grok_oct_GLOBAL)
-UV
-DPPP_(my_grok_oct)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
-{
-    const char *s = start;
-    STRLEN len = *len_p;
-    UV value = 0;
-    NV value_nv = 0;
-
-    const UV max_div_8 = UV_MAX / 8;
-    bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
-    bool overflowed = FALSE;
-
-    for (; len-- && *s; s++) {
-         /* gcc 2.95 optimiser not smart enough to figure that this subtraction
-            out front allows slicker code.  */
-        int digit = *s - '0';
-        if (digit >= 0 && digit <= 7) {
-            /* Write it in this wonky order with a goto to attempt to get the
-               compiler to make the common case integer-only loop pretty tight.
-            */
-          redo:
-            if (!overflowed) {
-                if (value <= max_div_8) {
-                    value = (value << 3) | digit;
-                    continue;
-                }
-                /* Bah. We're just overflowed.  */
-                warn("Integer overflow in octal number");
-                overflowed = TRUE;
-                value_nv = (NV) value;
-            }
-            value_nv *= 8.0;
-           /* If an NV has not enough bits in its mantissa to
-            * represent a UV this summing of small low-order numbers
-            * is a waste of time (because the NV cannot preserve
-            * the low-order bits anyway): we could just remember when
-            * did we overflow and in the end just multiply value_nv by the
-            * right amount of 8-tuples. */
-            value_nv += (NV)digit;
-            continue;
-        }
-        if (digit == ('_' - '0') && len && allow_underscores
-            && (digit = s[1] - '0') && (digit >= 0 && digit <= 7))
-           {
-               --len;
-               ++s;
-                goto redo;
-           }
-        /* Allow \octal to work the DWIM way (that is, stop scanning
-         * as soon as non-octal characters are seen, complain only iff
-         * someone seems to want to use the digits eight and nine). */
-        if (digit == 8 || digit == 9) {
-            if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
-                warn("Illegal octal digit '%c' ignored", *s);
-        }
-        break;
-    }
-
-    if (   ( overflowed && value_nv > 4294967295.0)
-#if UVSIZE > 4
-       || (!overflowed && value > 0xffffffff  )
-#endif
-       ) {
-       warn("Octal number > 037777777777 non-portable");
-    }
-    *len_p = s - start;
-    if (!overflowed) {
-        *flags = 0;
-        return value;
-    }
-    *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
-    if (result)
-        *result = value_nv;
-    return UV_MAX;
-}
-#endif
-#endif
-
-#if !defined(my_snprintf)
-#if defined(NEED_my_snprintf)
-static int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...);
-static
-#else
-extern int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...);
-#endif
-
-#define my_snprintf DPPP_(my_my_snprintf)
-#define Perl_my_snprintf DPPP_(my_my_snprintf)
-
-#if defined(NEED_my_snprintf) || defined(NEED_my_snprintf_GLOBAL)
-
-int
-DPPP_(my_my_snprintf)(char *buffer, const Size_t len, const char *format, ...)
-{
-    dTHX;
-    int retval;
-    va_list ap;
-    va_start(ap, format);
-#ifdef HAS_VSNPRINTF
-    retval = vsnprintf(buffer, len, format, ap);
-#else
-    retval = vsprintf(buffer, format, ap);
-#endif
-    va_end(ap);
-    if (retval < 0 || (len > 0 && (Size_t)retval >= len))
-       Perl_croak(aTHX_ "panic: my_snprintf buffer overflow");
-    return retval;
-}
-
-#endif
-#endif
-
-#if !defined(my_sprintf)
-#if defined(NEED_my_sprintf)
-static int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...);
-static
-#else
-extern int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...);
-#endif
-
-#define my_sprintf DPPP_(my_my_sprintf)
-#define Perl_my_sprintf DPPP_(my_my_sprintf)
-
-#if defined(NEED_my_sprintf) || defined(NEED_my_sprintf_GLOBAL)
-
-int
-DPPP_(my_my_sprintf)(char *buffer, const char* pat, ...)
-{
-    va_list args;
-    va_start(args, pat);
-    vsprintf(buffer, pat, args);
-    va_end(args);
-    return strlen(buffer);
-}
-
-#endif
-#endif
-
-#ifdef NO_XSLOCKS
-#  ifdef dJMPENV
-#    define dXCPT             dJMPENV; int rEtV = 0
-#    define XCPT_TRY_START    JMPENV_PUSH(rEtV); if (rEtV == 0)
-#    define XCPT_TRY_END      JMPENV_POP;
-#    define XCPT_CATCH        if (rEtV != 0)
-#    define XCPT_RETHROW      JMPENV_JUMP(rEtV)
-#  else
-#    define dXCPT             Sigjmp_buf oldTOP; int rEtV = 0
-#    define XCPT_TRY_START    Copy(top_env, oldTOP, 1, Sigjmp_buf); rEtV = Sigsetjmp(top_env, 1); if (rEtV == 0)
-#    define XCPT_TRY_END      Copy(oldTOP, top_env, 1, Sigjmp_buf);
-#    define XCPT_CATCH        if (rEtV != 0)
-#    define XCPT_RETHROW      Siglongjmp(top_env, rEtV)
-#  endif
-#endif
-
-#if !defined(my_strlcat)
-#if defined(NEED_my_strlcat)
-static Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size);
-static
-#else
-extern Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size);
-#endif
-
-#define my_strlcat DPPP_(my_my_strlcat)
-#define Perl_my_strlcat DPPP_(my_my_strlcat)
-
-#if defined(NEED_my_strlcat) || defined(NEED_my_strlcat_GLOBAL)
-
-Size_t
-DPPP_(my_my_strlcat)(char *dst, const char *src, Size_t size)
-{
-    Size_t used, length, copy;
-
-    used = strlen(dst);
-    length = strlen(src);
-    if (size > 0 && used < size - 1) {
-        copy = (length >= size - used) ? size - used - 1 : length;
-        memcpy(dst + used, src, copy);
-        dst[used + copy] = '\0';
-    }
-    return used + length;
-}
-#endif
-#endif
-
-#if !defined(my_strlcpy)
-#if defined(NEED_my_strlcpy)
-static Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size);
-static
-#else
-extern Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size);
-#endif
-
-#define my_strlcpy DPPP_(my_my_strlcpy)
-#define Perl_my_strlcpy DPPP_(my_my_strlcpy)
-
-#if defined(NEED_my_strlcpy) || defined(NEED_my_strlcpy_GLOBAL)
-
-Size_t
-DPPP_(my_my_strlcpy)(char *dst, const char *src, Size_t size)
-{
-    Size_t length, copy;
-
-    length = strlen(src);
-    if (size > 0) {
-        copy = (length >= size) ? size - 1 : length;
-        memcpy(dst, src, copy);
-        dst[copy] = '\0';
-    }
-    return length;
-}
-
-#endif
-#endif
-#ifndef PERL_PV_ESCAPE_QUOTE
-#  define PERL_PV_ESCAPE_QUOTE           0x0001
-#endif
-
-#ifndef PERL_PV_PRETTY_QUOTE
-#  define PERL_PV_PRETTY_QUOTE           PERL_PV_ESCAPE_QUOTE
-#endif
-
-#ifndef PERL_PV_PRETTY_ELLIPSES
-#  define PERL_PV_PRETTY_ELLIPSES        0x0002
-#endif
-
-#ifndef PERL_PV_PRETTY_LTGT
-#  define PERL_PV_PRETTY_LTGT            0x0004
-#endif
-
-#ifndef PERL_PV_ESCAPE_FIRSTCHAR
-#  define PERL_PV_ESCAPE_FIRSTCHAR       0x0008
-#endif
-
-#ifndef PERL_PV_ESCAPE_UNI
-#  define PERL_PV_ESCAPE_UNI             0x0100
-#endif
-
-#ifndef PERL_PV_ESCAPE_UNI_DETECT
-#  define PERL_PV_ESCAPE_UNI_DETECT      0x0200
-#endif
-
-#ifndef PERL_PV_ESCAPE_ALL
-#  define PERL_PV_ESCAPE_ALL             0x1000
-#endif
-
-#ifndef PERL_PV_ESCAPE_NOBACKSLASH
-#  define PERL_PV_ESCAPE_NOBACKSLASH     0x2000
-#endif
-
-#ifndef PERL_PV_ESCAPE_NOCLEAR
-#  define PERL_PV_ESCAPE_NOCLEAR         0x4000
-#endif
-
-#ifndef PERL_PV_ESCAPE_RE
-#  define PERL_PV_ESCAPE_RE              0x8000
-#endif
-
-#ifndef PERL_PV_PRETTY_NOCLEAR
-#  define PERL_PV_PRETTY_NOCLEAR         PERL_PV_ESCAPE_NOCLEAR
-#endif
-#ifndef PERL_PV_PRETTY_DUMP
-#  define PERL_PV_PRETTY_DUMP            PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_QUOTE
-#endif
-
-#ifndef PERL_PV_PRETTY_REGPROP
-#  define PERL_PV_PRETTY_REGPROP         PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_LTGT|PERL_PV_ESCAPE_RE
-#endif
-
-/* Hint: pv_escape
- * Note that unicode functionality is only backported to
- * those perl versions that support it. For older perl
- * versions, the implementation will fall back to bytes.
- */
-
-#ifndef pv_escape
-#if defined(NEED_pv_escape)
-static char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags);
-static
-#else
-extern char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags);
-#endif
-
-#ifdef pv_escape
-#  undef pv_escape
-#endif
-#define pv_escape(a,b,c,d,e,f) DPPP_(my_pv_escape)(aTHX_ a,b,c,d,e,f)
-#define Perl_pv_escape DPPP_(my_pv_escape)
-
-#if defined(NEED_pv_escape) || defined(NEED_pv_escape_GLOBAL)
-
-char *
-DPPP_(my_pv_escape)(pTHX_ SV *dsv, char const * const str,
-  const STRLEN count, const STRLEN max,
-  STRLEN * const escaped, const U32 flags)
-{
-    const char esc = flags & PERL_PV_ESCAPE_RE ? '%' : '\\';
-    const char dq = flags & PERL_PV_ESCAPE_QUOTE ? '"' : esc;
-    char octbuf[32] = "%123456789ABCDF";
-    STRLEN wrote = 0;
-    STRLEN chsize = 0;
-    STRLEN readsize = 1;
-#if defined(is_utf8_string) && defined(utf8_to_uvchr)
-    bool isuni = flags & PERL_PV_ESCAPE_UNI ? 1 : 0;
-#endif
-    const char *pv  = str;
-    const char * const end = pv + count;
-    octbuf[0] = esc;
-
-    if (!(flags & PERL_PV_ESCAPE_NOCLEAR))
-       sv_setpvs(dsv, "");
-
-#if defined(is_utf8_string) && defined(utf8_to_uvchr)
-    if ((flags & PERL_PV_ESCAPE_UNI_DETECT) && is_utf8_string((U8*)pv, count))
-        isuni = 1;
-#endif
-
-    for (; pv < end && (!max || wrote < max) ; pv += readsize) {
-        const UV u =
-#if defined(is_utf8_string) && defined(utf8_to_uvchr)
-                    isuni ? utf8_to_uvchr((U8*)pv, &readsize) :
-#endif
-                            (U8)*pv;
-        const U8 c = (U8)u & 0xFF;
-
-        if (u > 255 || (flags & PERL_PV_ESCAPE_ALL)) {
-            if (flags & PERL_PV_ESCAPE_FIRSTCHAR)
-                chsize = my_snprintf(octbuf, sizeof octbuf,
-                                      "%"UVxf, u);
-            else
-                chsize = my_snprintf(octbuf, sizeof octbuf,
-                                      "%cx{%"UVxf"}", esc, u);
-        } else if (flags & PERL_PV_ESCAPE_NOBACKSLASH) {
-            chsize = 1;
-        } else {
-            if (c == dq || c == esc || !isPRINT(c)) {
-               chsize = 2;
-                switch (c) {
-               case '\\' : /* fallthrough */
-               case '%'  : if (c == esc)
-                               octbuf[1] = esc;
-                           else
-                               chsize = 1;
-                           break;
-               case '\v' : octbuf[1] = 'v'; break;
-               case '\t' : octbuf[1] = 't'; break;
-               case '\r' : octbuf[1] = 'r'; break;
-               case '\n' : octbuf[1] = 'n'; break;
-               case '\f' : octbuf[1] = 'f'; break;
-                case '"'  : if (dq == '"')
-                               octbuf[1] = '"';
-                           else
-                               chsize = 1;
-                           break;
-               default:    chsize = my_snprintf(octbuf, sizeof octbuf,
-                               pv < end && isDIGIT((U8)*(pv+readsize))
-                               ? "%c%03o" : "%c%o", esc, c);
-                }
-            } else {
-                chsize = 1;
-            }
-       }
-       if (max && wrote + chsize > max) {
-           break;
-        } else if (chsize > 1) {
-            sv_catpvn(dsv, octbuf, chsize);
-            wrote += chsize;
-       } else {
-           char tmp[2];
-           my_snprintf(tmp, sizeof tmp, "%c", c);
-            sv_catpvn(dsv, tmp, 1);
-           wrote++;
-       }
-        if (flags & PERL_PV_ESCAPE_FIRSTCHAR)
-            break;
-    }
-    if (escaped != NULL)
-        *escaped= pv - str;
-    return SvPVX(dsv);
-}
-
-#endif
-#endif
-
-#ifndef pv_pretty
-#if defined(NEED_pv_pretty)
-static char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags);
-static
-#else
-extern char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags);
-#endif
-
-#ifdef pv_pretty
-#  undef pv_pretty
-#endif
-#define pv_pretty(a,b,c,d,e,f,g) DPPP_(my_pv_pretty)(aTHX_ a,b,c,d,e,f,g)
-#define Perl_pv_pretty DPPP_(my_pv_pretty)
-
-#if defined(NEED_pv_pretty) || defined(NEED_pv_pretty_GLOBAL)
-
-char *
-DPPP_(my_pv_pretty)(pTHX_ SV *dsv, char const * const str, const STRLEN count,
-  const STRLEN max, char const * const start_color, char const * const end_color,
-  const U32 flags)
-{
-    const U8 dq = (flags & PERL_PV_PRETTY_QUOTE) ? '"' : '%';
-    STRLEN escaped;
-
-    if (!(flags & PERL_PV_PRETTY_NOCLEAR))
-       sv_setpvs(dsv, "");
-
-    if (dq == '"')
-        sv_catpvs(dsv, "\"");
-    else if (flags & PERL_PV_PRETTY_LTGT)
-        sv_catpvs(dsv, "<");
-
-    if (start_color != NULL)
-        sv_catpv(dsv, D_PPP_CONSTPV_ARG(start_color));
-
-    pv_escape(dsv, str, count, max, &escaped, flags | PERL_PV_ESCAPE_NOCLEAR);
-
-    if (end_color != NULL)
-        sv_catpv(dsv, D_PPP_CONSTPV_ARG(end_color));
-
-    if (dq == '"')
-       sv_catpvs(dsv, "\"");
-    else if (flags & PERL_PV_PRETTY_LTGT)
-        sv_catpvs(dsv, ">");
-
-    if ((flags & PERL_PV_PRETTY_ELLIPSES) && escaped < count)
-       sv_catpvs(dsv, "...");
-
-    return SvPVX(dsv);
-}
-
-#endif
-#endif
-
-#ifndef pv_display
-#if defined(NEED_pv_display)
-static char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim);
-static
-#else
-extern char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim);
-#endif
-
-#ifdef pv_display
-#  undef pv_display
-#endif
-#define pv_display(a,b,c,d,e) DPPP_(my_pv_display)(aTHX_ a,b,c,d,e)
-#define Perl_pv_display DPPP_(my_pv_display)
-
-#if defined(NEED_pv_display) || defined(NEED_pv_display_GLOBAL)
-
-char *
-DPPP_(my_pv_display)(pTHX_ SV *dsv, const char *pv, STRLEN cur, STRLEN len, STRLEN pvlim)
-{
-    pv_pretty(dsv, pv, cur, pvlim, NULL, NULL, PERL_PV_PRETTY_DUMP);
-    if (len > cur && pv[cur] == '\0')
-       sv_catpvs(dsv, "\\0");
-    return SvPVX(dsv);
-}
-
-#endif
-#endif
-
-#endif /* _P_P_PORTABILITY_H_ */
-
-/* End of File ppport.h */
diff --git a/data/PVE/pvecm b/data/PVE/pvecm
deleted file mode 100755 (executable)
index 87bc9f6..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use PVE::INotify;
-use PVE::Cluster;
-use PVE::CLI::pvecm;
-
-my $prepare = sub {
-    my $nodename = PVE::INotify::nodename();
-
-    PVE::Cluster::check_cfs_is_mounted();
-    PVE::Cluster::cfs_update();
-
-    # trigger check that we have resolvable name
-    my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
-};
-
-PVE::CLI::pvecm->run_cli_handler(prepare => $prepare, no_rpcenv => 1);
diff --git a/data/PVE/test.pl b/data/PVE/test.pl
deleted file mode 100755 (executable)
index 89fb062..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-
-use PVE::IPCC;
-
-if (defined(my $res = PVE::IPCC::ipcc_send_rec(2))) {
-    print "GOT: $res\n";
-} else {
-    die "ipcc_send_rec failed: $!\n";
-}
-exit 0;
-
-my $i = 0;
-    
-
-for($i = 0; $i < 10000; $i++) {
-    print "t1\n";
-    print "c1: " . PVE::IPCC::ipcc_send_rec(1, "adas\0defg") . "\n";
-    print "t1\n";
-}
-
diff --git a/data/PVE/test1.pl b/data/PVE/test1.pl
deleted file mode 100755 (executable)
index 717e7e0..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/perl -w -T
-
-use strict;
-
-use PVE::Cluster;
-use PVE::INotify;
-use PVE::AccessControl;
-use Data::Dumper;
-
-
-my $nodename = PVE::INotify::nodename();
-PVE::Cluster::log_msg(1, "ident2", "msg1 Ã¶Ã¤Ã¼");
-PVE::Cluster::log_msg(1, "root\@pam", "msg1 Ã¶Ã¤Ã¼");
-#print PVE::Cluster::get_system_log(undef, 0);
-exit 0;
-
-#print PVE::Cluster::get_system_log(undef, 0);
-
-#PVE::Cluster::cfs_update();
-
-#my $res = PVE::Cluster::get_vmlist();
-#print "TEST1: " . Dumper($res->{ids});
-
-#exit 0;
-
-while (1) {
-
-    
-    print "update start\n";
-    PVE::Cluster::cfs_update();
-    print "update end\n";
-
-    my $res = PVE::Cluster::rrd_dump();
-    print "RRDDATA:" . Dumper($res);
-
-    #my $res = PVE::Cluster::cfs_file_version('user.cfg');
-    #print "VER $res\n";
-
-    sleep(1);
-}
-exit 0;
-
-my $loopcount = 0;
-
-
-while (1) {
-
-    PVE::Cluster::update();
-
-    PVE::Cluster::broadcast_vminfo({ count => $loopcount});
-
-    my $res = PVE::Cluster::get_vminfo($nodename);
-    print "TEST1: " . Dumper($res);
-
-   if (defined($res = PVE::Cluster::get_config("cluster.conf"))) {
-       print "TEST2: " . Dumper($res);
-    } else {
-       warn "get_config failed: $!\n";
-    }
-
-    $loopcount++;
-
-    sleep(2);
-}
-
diff --git a/data/README b/data/README
deleted file mode 100644 (file)
index 4d884e0..0000000
+++ /dev/null
@@ -1,377 +0,0 @@
-Enable/Disable debugging
-========================
-
-# echo "1" >/etc/pve/.debug 
-# echo "0" >/etc/pve/.debug 
-
-Memory leak debugging (valgrind)
-================================
-
-export G_SLICE=always-malloc 
-export G_DEBUG=gc-friendly
-valgrind --leak-check=full ./pmxcfs -f
-
-# pmap <PID>
-# cat /proc/<PID>/maps
-
-Profiling (google-perftools)
-============================
-
-compile with: -lprofiler
-CPUPROFILE=./profile ./pmxcfs -f
-google-pprof --text ./pmxcfs profile 
-google-pprof --gv ./pmxcfs profile 
-
-Proposed file system layout
-============================
-
-The file system is mounted at:
-
-/etc/pve
-
-Files:
-
-cluster.conf
-storage.cfg
-user.cfg
-domains.cfg
-authkey.pub
-
-priv/shadow.cfg
-priv/authkey.key
-
-nodes/${NAME}/pve-ssl.pem
-nodes/${NAME}/priv/pve-ssl.key
-nodes/${NAME}/qemu-server/${VMID}.conf
-nodes/${NAME}/openvz/${VMID}.conf
-
-Symbolic links:
-
-local => nodes/${LOCALNAME}
-qemu-server => nodes/${LOCALNAME}/qemu-server/
-openvz => nodes/${LOCALNAME}/openvz/
-
-Special status files for debugging (JSON):
-.version    => file versions (to detect file modifications)
-.members    => Info about cluster members
-.vmlist     => List of all VMs
-.clusterlog => Cluster log (last 50 entries)
-.rrd        => RRD data (most recent entries)
-
-POSIX Compatibility
-===================
-
-The file system is based on fuse, so the behavior is POSIX like. But
-many feature are simply not implemented, because we do not need them:
-
-    - just normal files, no symbolic links, ...
-    - you can't rename non-empty directories (because this makes it easier 
-      to guarantee that VMIDs are unique).
-    - you can't change file permissions (permissions are based on path)
-    - O_EXCL creates were not atomic (like old NFS)
-    - O_TRUNC creates are not atomic (fuse restriction)
-    - ...
-
-File access rights
-==================
-
-All files/dirs are owned by user 'root' and have group
-'www-data'. Only root has write permissions, but group 'www-data' can
-read most files. Files below the following paths:
-
- priv/
- nodes/${NAME}/priv/
-
-are only accessible by root.
-
-SOURCE FILES
-============
-
-src/pmxcfs.c
-
-The final fuse binary which mounts the file system at '/etc/pve' is
-called 'pmxcfs'.
-
-
-src/cfs-plug.c
-src/cfs-plug.h
-
-That files implement some kind of fuse plugins - we can assemble our
-file system using several plugins (like bind mounts).
-
-
-src/cfs-plug-memdb.h
-src/cfs-plug-memdb.c
-src/dcdb.c
-src/dcdb.h
-
-This plugin implements the distributed, replicated file system. All
-file system operations are sent over the wire.
-
-
-src/cfs-plug-link.c
-
-Plugin for symbolic links.
-
-src/cfs-plug-func.c
-
-Plugin to dump data returned from a function. We use this to provide
-status information (for example the .version or .vmlist files)
-
-
-src/cfs-utils.c
-src/cfs-utils.h
-
-Some helper function.
-
-
-src/memdb.c
-src/memdb.h
-
-In memory file system, which writes data back to the disk.
-
-
-src/database.c 
-
-This implements the sqlite backend for memdb.c 
-
-src/server.c
-src/server.h
-
-A simple IPC server based on libqb. Provides fast access to
-configuration and status.
-
-src/status.c
-src/status.h
-
-A simple key/value store. Values are copied to all cluster members.
-
-src/dfsm.c
-src/dfsm.h
-
-Helper to simplify the implementation of a distributed finite state
-machine on top of corosync CPG.
-
-src/loop.c
-src/loop.h
-
-A simple event loop for corosync services.
-
-HOW TO COMPILE AND TEST
-=======================
-
-# ./autogen.sh
-# ./configure
-# make
-
-To test, you need a working corosync installation. First create
-the mount point with:
-
-# mkdir /etc/pve
-
-and create the directory to store the database:
-
-# mkdir /var/lib/pve-cluster/
-
-Then start the fuse file system with:
-
-# ./src/pmxcfs
-
-The distributed file system is accessible under /etc/pve
-
-There is a small test program to dump the database (and the index used
-to compare database contents).
-
-# ./src/testmemdb
-
-To build the Debian package use:
-
-# dpkg-buildpackage -rfakeroot -b -us -uc
-
-Distributed Configuration Database (DCDB)
-===========================================
-
-We want to implement a simple way to distribute small configuration
-files among the cluster on top of corosync CPG.
-
-The set of all configuration files defines the 'state'. That state is
-stored persistently on all members using a backend
-database. Configuration files are usually quite small, and we can even
-set a limit for the file size.
-
-* Backend Database
-
-Each node stores the state using a backend database. That database
-need to have transaction support, because we want to do atomic
-updates. It must also be possible to get a copy/snapshot of the
-current state.
-
-** File Based Backend (not implemented)
-
-Seems possible, but its hard to implement atomic update and snapshots.
-
-** Berkeley Database Backend (not implemented)
-
-The Berkeley DB provides full featured transaction support, including
-atomic commits and snapshot isolation. 
-
-** SQLite Database Backend (currently in use)
-
-This is simpler than BDB. All data is inside a single file. And there
-is a defined way to access that data (SQL). It is also very stable.
-
-We can use the following simple database table:
-
-  INODE PARENT NAME WRITER VERSION SIZE VALUE
-
-We use a global 'version' number (64bit) to uniquely identify the
-current version. This 'version' is incremented on any database
-modification. We also use it as 'inode' number when we create a new
-entry. The 'inode' is the primary key.
-
-** RAM/File Based Backend
-
-If the state is small enough we can hold all data in RAM. Then a
-'snapshot' is a simple copy of the state in RAM. Although all data is
-in RAM, a copy is written to the disk. The idea is that the state in
-RAM is the 'correct' one. If any file/database operations fails the
-saved state can become inconsistent, and the node must trigger a state
-resync operation if that happens.
-
-We can use the DB design from above to store data on disk.
-
-* Comparing States
-
-We need an effective way to compare states and test if they are
-equal. The easiest way is to assign a version number which increase on
-every change. States are equal if they have the same version. Also,
-the version provides a way to determine which state is newer. We can
-gain additional safety by 
-
-  - adding the ID of the last writer for each value
-  - computing a hash for each value
-
-And on partition merge we use that info to compare the version of each
-entry.
-
-* Quorum
-
-Quorum is necessary to modify state. Else we allow read-only access.
-
-* State Transfer to a Joining Process ([Ban], [Bir96, ch. 15.3.2])
-
-We adopt the general mechanism described in [Ban] to avoid making
-copies of the state. This can be achieved by initiating a state
-transfer immediately after a configuration change. We implemented this
-protocol in 'dfsm.c'. It is used by the DCDB implementation 'dcdb.c'.
-
-There are to types of messages:
-
-  - normal: only delivered when the state is synchronized. We queue
-    them until the state is in sync.
-
-  - state transfer: used to implement the state transfer
-
-The following example assumes that 'P' joins, 'Q' and 'R' share the
-same state.
-
-init:
-       P       Q       R
-        c-------c-------c new configuration
-       *       *       * change mode: DFSM_MODE_START_SYNC
-       *       *       * start queuing
-       *       *       * $state[X] = dfsm_get_state_fn()
-       |------->-------> send(DFSM_MESSAGE_STATE, $state[P]) 
-       |<------|------>| send(DFSM_MESSAGE_STATE, $state[Q]) 
-       <-------<-------| send(DFSM_MESSAGE_STATE, $state[R]) 
-       w-------w-------w wait until we have received all states
-       *       *       * dfsm_process_state_update($state[P,Q,R])
-       *       |       | change mode: DFSM_MODE_UPDATE
-       |       *       * change mode: DFSM_MODE_SYNCED
-       |       *       * stop queuing (deliver queue)
-       |       *       | selected Q as leader: send updates 
-       |<------*       | send(DFSM_MESSAGE_UPDATE, $updates) 
-       |<------*       | send(DFSM_MESSAGE_UPDATE_COMPLETE) 
-
-update:
-       P       Q       R
-       *<------|       | record updates: dfsm_process_update_fn() 
-       *<------|-------| queue normal messages 
-       w       |       | wait for DFSM_MESSAGE_UPDATE_COMPLETE
-       *       |       | commit new state: dfsm_commit_fn()
-       *       |       | change mode: DFSM_MODE_SYNCED
-       *       |       | stop queuing (deliver queue)
-
-
-While the general algorithm seems quite easy, there are some pitfalls
-when implementing it using corosync CPG (extended virtual synchrony):
-
-Messages sent in one configuration can be received in a later
-configuration. This is perfect for normal messages, but must not
-happen for state transfer message. We add an unique epoch to all state
-transfer messages, and simply discard messages from other
-configurations.
-
-Configuration change may happen before the protocol finish. This is
-particularly bad when we have already queued messages. Those queued
-messages needs to be considered part of the state (and thus we need
-to make sure that all nodes have exactly the same queue).
-
-A simple solution is to resend all queued messages. We just need to
-make sure that we still have a reasonable order (resend changes the
-order). A sender expects that sent messages are received in the same
-order. We include a 'msg_count' (local to each member) in all 'normal'
-messages, and so we can use that to sort the queue.
-
-A second problem arrives from the fact that we allow synced member to
-continue operation while other members doing state updates. We
-basically use 2 different queues:
-
-  queue 1: Contain messages from 'unsynced' members. This queue is
-  sorted and resent on configuration change. We commit those messages
-  when we get the DFSM_MESSAGE_UPDATE_COMPLETE message.
-
-  queue 2: Contain messages from 'synced' members. This queue is only
-  used by 'unsynced' members, because 'synced' members commits those
-  messages immediately. We can safely discard this queue at
-  configuration change.
-
-File Locking
-============
-
-We implement a simple lock-file based locking mechanism on top of the
-distributed file system. You can create/acquire a lock with:
-
-  $filename = "/etc/pve/priv/lock/<A-LOCK-NAME>";
-  while(!(mkdir $filename)) {
-      (utime 0, 0, $filename); # cfs unlock request
-      sleep(1);
-  }
-  /* got the lock */
-
-If above command succeed, you got the lock for 120 seconds (hard coded
-time limit). The 'mkdir' command is atomic and only succeed if the
-directory does not exist. The 'utime 0 0' triggers a cluster wide
-test, and removes $filename if it is older than 120 seconds. This test
-does not use the mtime stored inside the file system, because there can
-be a time drift between nodes. Instead each node stores the local time when
-it first see a lock file. This time is used to calculate the age of
-the lock.
-
-With version 3.0-17, it is possible to update an existing lock using
-
-  utime 0, time();
-
-This succeeds if run from the same node you created the lock, and updates
-the lock lifetime for another 120 seconds. 
-
-
-References
-==========
-
-[Bir96]        Kenneth P. Birman, Building Secure and Reliable Network Applications,
-       Manning Publications Co., 1996 
-
-[Ban]   Bela Ban, Flexible API for State Transfer in the JavaGroups Toolkit,
-       http://www.jgroups.org/papers/state.ps.gz
-
diff --git a/data/cts/create_large_files.pl b/data/cts/create_large_files.pl
deleted file mode 100755 (executable)
index 0dd595a..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use POSIX;
-use File::Path qw(make_path remove_tree);
-use IO::File;
-
-my (undef, $nodename) = POSIX::uname();
-
-sub safe_mkdir {
-    my $dir = shift;
-    (mkdir $dir) || die "safe_mkdir $dir failed - $!\n";
-}
-
-my $data = "0" x (1024*100);
-
-sub create_file {
-    my ($filename) = shift;
-
-    my $fh = new IO::File $filename, O_RDWR|O_CREAT|O_EXCL;
-    die "cant create file $filename - $!" if !defined $fh;
-
-    #my $data = "$filename\n" x 30;
-
-    (print $fh $data) || die "write $filename failed\n";
-    close ($fh);
-
-    #system("cat $filename");
-    #system("df -h /etc/pve");
-}
-
-my $testdir = "/etc/pve/manyfilestest/";
-
-remove_tree($testdir);
-
-safe_mkdir $testdir;
-
-for (my $i = 0; $i < 300; $i++) {
-
-    create_file("$testdir/test$i.dat");
-}
diff --git a/data/cts/create_many_files.pl b/data/cts/create_many_files.pl
deleted file mode 100755 (executable)
index 483e83f..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use POSIX;
-use File::Path qw(make_path remove_tree);
-use IO::File;
-
-my (undef, $nodename) = POSIX::uname();
-
-sub safe_mkdir {
-    my $dir = shift;
-    (mkdir $dir) || die "safe_mkdir $dir failed - $!\n";
-}
-
-sub create_file {
-    my ($filename) = shift;
-
-    my $fh = new IO::File $filename, O_RDWR|O_CREAT|O_EXCL;
-    die "cant create file $filename - $!" if !defined $fh;
-
-    #my $data = "$filename\n" x 30;
-    my $data = "0" x 2048;
-
-    (print $fh $data) || die "write $filename failed\n";
-    close ($fh);
-
-    #system("cat $filename");
-    #system("df -h /etc/pve");
-}
-
-my $testdir = "/etc/pve/manyfilestest/";
-
-remove_tree($testdir);
-
-safe_mkdir $testdir;
-
-for (my $i = 0; $i < 100; $i++) {
-
-    safe_mkdir "$testdir/$i";
-
-    for (my $j = 0; $j < 90; $j++) {
-       create_file("$testdir/$i/test$j.dat");
-    }
-}
diff --git a/data/cts/create_vm_test.pl b/data/cts/create_vm_test.pl
deleted file mode 100755 (executable)
index 6af97a7..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use POSIX;
-use File::Path qw(make_path remove_tree);
-use IO::File;
-
-my (undef, $nodename) = POSIX::uname();
-
-sub safe_mkdir {
-    my $dir = shift;
-    (mkdir $dir) || die "safe_mkdir $dir failed - $!\n";
-}
-
-sub safe_rmdir {
-    my $dir = shift;
-    (rmdir $dir) || die "safe_rmdir $dir failed - $!\n";
-}
-
-sub safe_unlink {
-    my $file = shift;
-    (unlink $file) || die "safe_unlink $file failed - $!\n";
-}
-
-sub create_vmfile {
-    my ($filename) = shift;
-
-    my $fh = new IO::File $filename, O_RDWR|O_CREAT|O_EXCL;
-    die "cant create file $filename - $!" if !defined $fh;
-
-    #my $data = "$filename\n" x 30;
-    my $data = "0" x 1024;
-
-    (print $fh $data) || die "write $filename failed\n";
-    close ($fh);
-
-    #system("cat $filename");
-    #system("df -h /etc/pve");
-}
-
-sub start_vmtest {
-    my ($subdir) = @_;
-
-    for (my $i = 1000; $i < 1100; $i++) {
-       my $filename = "$subdir/${i}.conf";
-       create_vmfile($filename);
-    }
-
-    for (my $i = 1000; $i < 1100; $i++) {
-       my $filename = "$subdir/${i}.conf";
-       safe_unlink($filename);
-    }
-}
-
-sub start_subtest {
-    my ($subdir) = @_;
-
-    safe_mkdir $subdir;
-
-    start_vmtest($subdir);
-
-    safe_rmdir $subdir;
-}
-
-sub start_test {
-    my ($subdir) = @_;
-
-    safe_mkdir $subdir;
-
-    start_subtest("$subdir/qemu-server");
-
-    safe_rmdir $subdir;
-}
-
-
-my $basedir = "/etc/pve/nodes/";
-
-my $testdir = "$basedir/${nodename}-test1";
-
-remove_tree($testdir);
-
-while(1) {
-    eval {
-       local $SIG{INT} = sub { die "interrupted" };
-       start_test("$testdir");
-    };
-    my $err = $@;
-
-    system("date; df -h /etc/pve");
-    die $err if $err;
-}
diff --git a/data/src/Makefile b/data/src/Makefile
deleted file mode 100644 (file)
index 743f949..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-
-DEPENDENCIES=libcpg libcmap libquorum libqb glib-2.0 fuse sqlite3 librrd
-
-CC = gcc
-CFLAGS += -std=gnu99
-CFLAGS += -Wall -Werror -Wno-unknown-pragmas -Wno-strict-aliasing
-CFLAGS += -Wpedantic
-CFLAGS += -g -O2 -Wl,-z,relro
-CFLAGS += -I.
-CFLAGS += $(shell pkg-config --cflags ${DEPENDENCIES})
-
-LDFLAGS += $(shell pkg-config --libs ${DEPENDENCIES})
-
-AR = ar
-ARFLAGS = crs
-
-.c.o:
-       $(CC) $(CFLAGS) -c -o $@ $< -MMD -MT $@ -MF $@.d
-
-all: pmxcfs create_pmxcfs_db logtest check_memdb
-
-libpmxcfs.a: logger.o loop.o server.o status.o confdb.o quorum.o dcdb.o dfsm.o
-libpmxcfs.a: cfs-plug.o cfs-plug-memdb.o cfs-plug-link.o cfs-plug-func.o
-libpmxcfs.a: cfs-utils.o memdb.o database.o
-libpmxcfs.a:
-       $(AR) $(ARFLAGS) $@ $^
-
-pmxcfs: pmxcfs.o libpmxcfs.a
-       $(CC) -o $@ $^ $(LDFLAGS)
-
-
-create_pmxcfs_db: create_pmxcfs_db.o libpmxcfs.a
-       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
-
-logtest: logtest.o libpmxcfs.a
-       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
-
-logtest2: logtest2.o libpmxcfs.a
-       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
-
-check_memdb: check_memdb.o libpmxcfs.a
-       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs check)
-
-pmxcfs.8:
-
--include /usr/share/pve-doc-generator/pve-doc-generator.mk
-
-.PHONY: install
-install: pmxcfs create_pmxcfs_db pmxcfs.8
-       install -D -m 0755 pmxcfs ${DESTDIR}/usr/bin/pmxcfs
-       install -D -m 0755 create_pmxcfs_db ${DESTDIR}/usr/bin/create_pmxcfs_db
-       install -D pmxcfs.8 ${DESTDIR}/usr/share/man/man8/pmxcfs.8
-
-.PHONY: check
-check: check_memdb
-       ./check_memdb
-
-.PHONY: clean
-clean:
-       rm -f *.o *.o.d *.a pmxcfs create_pmxcfs_db check_memdb logtest pmxcfs.8
-
--include *.o.d
diff --git a/data/src/cfs-ipc-ops.h b/data/src/cfs-ipc-ops.h
deleted file mode 100644 (file)
index 249308d..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-  Copyright (C) 2018 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Thomas Lamprecht <t.lamprecht@proxmox.com>
-
-*/
-
-#ifndef _PVE_CFS_IPC_OPS_H_
-#define _PVE_CFS_IPC_OPS_H_
-
-#define CFS_IPC_GET_FS_VERSION 1
-
-#define CFS_IPC_GET_CLUSTER_INFO 2
-
-#define CFS_IPC_GET_GUEST_LIST 3
-
-#define CFS_IPC_SET_STATUS 4
-
-#define CFS_IPC_GET_STATUS 5
-
-#define CFS_IPC_GET_CONFIG 6
-
-#define CFS_IPC_LOG_CLUSTER_MSG 7
-
-#define CFS_IPC_GET_CLUSTER_LOG 8
-
-#define CFS_IPC_GET_RRD_DUMP 10
-
-#define CFS_IPC_GET_GUEST_CONFIG_PROPERTY 11
-
-#define CFS_IPC_VERIFY_TOKEN 12
-
-#define CFS_IPC_GET_GUEST_CONFIG_PROPERTIES 13
-
-#endif
diff --git a/data/src/cfs-plug-func.c b/data/src/cfs-plug-func.c
deleted file mode 100644 (file)
index 96d8e8d..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
-  Copyright (C) 2011 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <glib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/file.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <dirent.h>
-
-#include "cfs-utils.h"
-#include "cfs-plug.h"
-
-static struct cfs_operations cfs_ops;
-
-static cfs_plug_t *cfs_plug_func_lookup_plug(cfs_plug_t *plug, char **path)
-{
-       g_return_val_if_fail(plug != NULL, NULL);
-       g_return_val_if_fail(plug->ops == &cfs_ops, NULL);
-
-       return (!*path || !(*path)[0]) ? plug : NULL;
-}
-
-static void cfs_plug_func_destroy(cfs_plug_t *plug)
-{
-       g_return_if_fail(plug != NULL);
-       g_return_if_fail(plug->ops == &cfs_ops);
-
-       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
-
-       cfs_debug("enter cfs_plug_func_destroy %s", plug->name);
-
-       if (fplug->data)
-               g_free(fplug->data);
-
-       g_free(plug->name);
-
-       g_free(plug);
-}
-
-static int 
-cfs_plug_func_getattr(
-       cfs_plug_t *plug, 
-       const char *path, 
-       struct stat *stbuf)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_func_getattr %s", path);
-
-       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
-
-       memset(stbuf, 0, sizeof(struct stat));
-
-       g_rw_lock_writer_lock(&fplug->data_rw_lock);
-       if (fplug->data)
-               g_free(fplug->data);
-
-       fplug->data = fplug->update_callback(plug);
-
-       stbuf->st_size = fplug->data ? strlen(fplug->data) : 0;
-
-       g_rw_lock_writer_unlock(&fplug->data_rw_lock);
-
-       stbuf->st_mode = fplug->mode;
-       stbuf->st_nlink = 1;
-
-       return 0;
-}
-
-static int 
-cfs_plug_func_read(
-       cfs_plug_t *plug, 
-       const char *path, 
-       char *buf, 
-       size_t size, 
-       off_t offset,
-       struct fuse_file_info *fi)
-{
-       (void) fi;
-
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
-
-       g_rw_lock_reader_lock(&fplug->data_rw_lock);
-       char *data = fplug->data;
-
-       cfs_debug("enter cfs_plug_func_read %s", data);
-
-       if (!data) {
-               g_rw_lock_reader_unlock(&fplug->data_rw_lock);
-               return 0;
-       }
-
-       int len = strlen(data);
-
-       if (offset < len) {
-               if (offset + size > len)
-                       size = len - offset;
-               memcpy(buf, data + offset, size);
-       } else {
-               size = 0;
-       }
-       g_rw_lock_reader_unlock(&fplug->data_rw_lock);
-
-       return size;
-}
-
-static int 
-cfs_plug_func_write(
-       cfs_plug_t *plug, 
-       const char *path, 
-       const char *buf, 
-       size_t size,
-       off_t offset, 
-       struct fuse_file_info *fi)
-{
-       (void) fi;
-
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_func_write");
-
-       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
-
-       if (offset != 0 || !fplug->write_callback)
-               return -EIO;
-
-       return fplug->write_callback(plug, buf, size);
-}
-
-static int 
-cfs_plug_func_truncate(
-       cfs_plug_t *plug, 
-       const char *path, 
-       off_t size)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
-
-       if (fplug->write_callback)
-               return 0;
-
-       return -EIO;
-}
-
-static int 
-cfs_plug_func_open(
-       cfs_plug_t *plug, 
-       const char *path, 
-       struct fuse_file_info *fi)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_func_open %s", path);
-
-       return 0;
-}
-
-static struct cfs_operations cfs_ops = {
-       .getattr = cfs_plug_func_getattr,
-       .read = cfs_plug_func_read,
-       .write = cfs_plug_func_write,
-       .truncate = cfs_plug_func_truncate,
-       .open = cfs_plug_func_open,
-};
-
-
-cfs_plug_func_t *
-cfs_plug_func_new(
-       const char *name, 
-       mode_t mode,
-       cfs_plug_func_update_data_fn_t update_callback,
-       cfs_plug_func_write_data_fn_t write_callback)
-{
-       g_return_val_if_fail(name != NULL, NULL);
-       g_return_val_if_fail(update_callback != NULL, NULL);
-
-       cfs_plug_func_t *fplug = g_new0(cfs_plug_func_t, 1);
-
-       fplug->plug.ops = &cfs_ops;
-
-       fplug->plug.lookup_plug = cfs_plug_func_lookup_plug;
-       fplug->plug.destroy_plug = cfs_plug_func_destroy;
-
-       fplug->plug.name = g_strdup(name);
-
-       fplug->update_callback = update_callback;
-       fplug->write_callback = write_callback;
-       if (!write_callback)
-               mode = mode & ~0222;
-
-       fplug->mode = S_IFREG | mode;
-
-       return fplug;
-}
-
diff --git a/data/src/cfs-plug-link.c b/data/src/cfs-plug-link.c
deleted file mode 100644 (file)
index 58d442d..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <glib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/file.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <dirent.h>
-
-#include "cfs-utils.h"
-#include "cfs-plug.h"
-#include "status.h"
-
-static struct cfs_operations cfs_ops;
-
-static cfs_plug_t *cfs_plug_link_lookup_plug(cfs_plug_t *plug, char **path)
-{
-       g_return_val_if_fail(plug != NULL, NULL);
-       g_return_val_if_fail(plug->ops == &cfs_ops, NULL);
-
-       return (!*path || !(*path)[0]) ? plug : NULL;
-}
-
-static int cfs_plug_link_getattr(cfs_plug_t *plug, const char *path, struct stat *stbuf)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_link_getattr %s", path);
-
-       memset(stbuf, 0, sizeof(struct stat));
-
-       if (cfs_is_quorate()) {
-               stbuf->st_mode = S_IFLNK | 0777;
-       } else {
-               stbuf->st_mode = S_IFLNK | 0555;
-       }
-
-       stbuf->st_nlink = 1;
-
-       return 0;
-}
-
-static int cfs_plug_link_readlink(cfs_plug_t *plug, const char *path, char *buf, size_t max)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_link_readlink %s", path);
-
-       int ret = -EACCES;
-
-       cfs_plug_link_t *lnk = (cfs_plug_link_t *)plug;
-
-       if (!lnk->symlink)
-               goto ret;
-
-       strncpy(buf, lnk->symlink, max);
-       if (max > 0)
-               buf[max - 1]= '\0';
-       ret = 0;
-
-ret:
-       return ret;
-}
-
-static void cfs_plug_link_destroy(cfs_plug_t *plug)
-{
-       g_return_if_fail(plug != NULL);
-       g_return_if_fail(plug->ops == &cfs_ops);
-
-       cfs_plug_link_t *lnk = (cfs_plug_link_t *)plug;
-
-       cfs_debug("enter cfs_plug_link_destroy %s", plug->name);
-
-       g_free(plug->name);
-
-       g_free(lnk->symlink);
-
-       g_free(plug);
-}
-
-static struct cfs_operations cfs_ops = {
-       .getattr = cfs_plug_link_getattr,
-       .readlink = cfs_plug_link_readlink,
-};
-
-
-cfs_plug_link_t *cfs_plug_link_new(const char *name, const char *symlink)
-{
-       g_return_val_if_fail(name != NULL, NULL);
-       g_return_val_if_fail(symlink != NULL, NULL);
-
-       cfs_plug_link_t *lnk = g_new0(cfs_plug_link_t, 1);
-
-       lnk->plug.ops = &cfs_ops;
-
-       lnk->plug.lookup_plug = cfs_plug_link_lookup_plug;
-       lnk->plug.destroy_plug = cfs_plug_link_destroy;
-
-       lnk->plug.name = g_strdup(name);
-
-       lnk->symlink = g_strdup(symlink);
-
-       return lnk;
-}
-
diff --git a/data/src/cfs-plug-memdb.c b/data/src/cfs-plug-memdb.c
deleted file mode 100644 (file)
index b7a5567..0000000
+++ /dev/null
@@ -1,527 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <glib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/file.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <dirent.h>
-#include <arpa/inet.h>
-
-#include "cfs-utils.h"
-#include "cfs-plug-memdb.h"
-#include "dcdb.h"
-#include "status.h"
-
-static struct cfs_operations cfs_ops;
-
-// FIXME: remove openvz stuff for 7.x
-static gboolean 
-name_is_openvz_script(
-       const char *name, 
-       guint32 *vmid_ret)
-{
-       if (!name)
-               return FALSE;
-
-       guint32 vmid = 0;
-       char *end = NULL;
-
-       if (name[0] == 'v' && name[1] == 'p' && name[2] == 's') {
-               end = (char *)name + 3;
-       } else {
-               if (name[0] < '1' || name[0] > '9')
-                       return FALSE;
-
-               vmid = strtoul(name, &end, 10);
-       }
-
-       if (!end || end[0] != '.')
-               return FALSE;
-
-       end++;
-
-       gboolean res = FALSE;
-
-       if (end[0] == 'm' && strcmp(end, "mount") == 0)
-               res = TRUE;
-
-       if (end[0] == 'u' && strcmp(end, "umount") == 0)
-               res = TRUE;
-
-       if (end[0] == 's' && 
-           (strcmp(end, "start") == 0 || strcmp(end, "stop") == 0))
-               res = TRUE;
-
-       if (end[0] == 'p' && 
-           (strcmp(end, "premount") == 0 || strcmp(end, "postumount") == 0))
-               res = TRUE;
-
-
-       if (res && vmid_ret)
-               *vmid_ret = vmid;
-
-       return res;
-}
-
-static void tree_entry_stat(memdb_tree_entry_t *te, struct stat *stbuf, gboolean quorate)
-{
-       g_return_if_fail(te != NULL);
-       g_return_if_fail(stbuf != NULL);
-
-       if (te->type == DT_DIR) {
-               stbuf->st_mode = S_IFDIR | (quorate ? 0777 : 0555);
-               stbuf->st_nlink = 2;
-       } else {
-               stbuf->st_mode = S_IFREG | (quorate ? 0666 : 0444);
-               stbuf->st_nlink = 1;
-               // FIXME: remove openvz stuff for 7.x
-               if (name_is_openvz_script(te->name, NULL)) {
-                       stbuf->st_mode |= S_IXUSR;
-               }
-       } 
-               
-       stbuf->st_size = te->size;
-       stbuf->st_blocks = 
-               (stbuf->st_size + MEMDB_BLOCKSIZE -1)/MEMDB_BLOCKSIZE;
-       stbuf->st_atime = te->mtime;
-       stbuf->st_mtime = te->mtime;
-       stbuf->st_ctime = te->mtime;
-}
-
-static int cfs_plug_memdb_getattr(cfs_plug_t *plug, const char *path, struct stat *stbuf)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
-
-       memset(stbuf, 0, sizeof(struct stat));
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       memdb_tree_entry_t *te = memdb_getattr(mdb->memdb, path);
-
-       if (te) {
-               tree_entry_stat(te, stbuf, cfs_is_quorate());
-               memdb_tree_entry_free(te);
-               return 0;
-       }
-
-       return  -ENOENT;
-}
-
-static int cfs_plug_memdb_readdir(
-       cfs_plug_t *plug, 
-       const char *path, 
-       void *buf, 
-       fuse_fill_dir_t filler, 
-       off_t offset, 
-       struct fuse_file_info *fi)
-{
-       (void) offset;
-       (void) fi;
-
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(filler != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       GList *dirlist = memdb_readdir(mdb->memdb, path);
-
-       if (dirlist) {
-
-               filler(buf, ".", NULL, 0);
-               filler(buf, "..", NULL, 0);
-
-               struct stat stbuf;
-               memset(&stbuf, 0, sizeof(struct stat));
-            
-               GList *l = dirlist;
-               while (l) {
-                       memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
-
-                       tree_entry_stat(te, &stbuf, 0);
-                       filler(buf, te->name, &stbuf, 0);
-                       l = g_list_next(l);
-               }
-
-               memdb_dirlist_free(dirlist);
-       }
-
-       return 0;
-}
-
-static int cfs_plug_memdb_open(cfs_plug_t *plug, const char *path, struct fuse_file_info *fi)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
-
-       memdb_tree_entry_t *te;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       if ((te = memdb_getattr(mdb->memdb, path))) {
-               memdb_tree_entry_free(te);
-       } else 
-               return -ENOENT;
-
-       return 0;
-}
-
-static int cfs_plug_memdb_read(
-       cfs_plug_t *plug, 
-       const char *path, 
-       char *buf, 
-       size_t size, 
-       off_t offset,
-       struct fuse_file_info *fi)
-{
-       (void) fi;
-
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-
-       int len;
-       gpointer data = NULL;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       len = memdb_read(mdb->memdb, path, &data);
-       if (len < 0)
-               return len;
-
-       if (offset < len) {
-               if (offset + size > len)
-                       size = len - offset;
-               memcpy(buf, (uint8_t *) data + offset, size);
-       } else {
-               size = 0;
-       }
-
-       if (data)
-               g_free(data);
-
-       return size;
-}
-
-static int cfs_plug_memdb_write(
-       cfs_plug_t *plug, 
-       const char *path, 
-       const char *buf, 
-       size_t size,
-       off_t offset, 
-       struct fuse_file_info *fi)
-{
-       (void) fi;
-
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-
-       int res;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       if (mdb->dfsm) {
-               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_WRITE, path, NULL, buf,
-                                            size, offset, 0);
-       } else {
-               uint32_t ctime = time(NULL);
-               res = memdb_write(mdb->memdb, path, 0, ctime, buf, size, offset, 0);
-       }
-
-       return res;
-}
-
-static int cfs_plug_memdb_truncate(cfs_plug_t *plug, const char *path, off_t size)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-
-       int res;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       if (mdb->dfsm) {
-               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_WRITE, path, NULL, NULL,
-                                            0, size, 1);
-       } else {
-               uint32_t ctime = time(NULL);
-               res = memdb_write(mdb->memdb, path, 0, ctime, NULL, 0, size, 1);
-       }
-
-       return res;
-}
-
-static int cfs_plug_memdb_create (
-       cfs_plug_t *plug, 
-       const char *path, 
-       mode_t mode, 
-       struct fuse_file_info *fi)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
-
-       int res;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       if (mdb->dfsm) {
-               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_CREATE, path, 
-                                            NULL, NULL, 0, 0, 0);
-       } else {
-               uint32_t ctime = time(NULL);
-
-               res = memdb_create(mdb->memdb, path, 0, ctime);
-       }
-
-       return res;
-}
-
-static int cfs_plug_memdb_mkdir(cfs_plug_t *plug, const char *path, mode_t mode)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-
-       int res;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       if (mdb->dfsm) {
-               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_MKDIR, path, 
-                                            NULL, NULL, 0, 0, 0);
-       } else {
-               uint32_t ctime = time(NULL);
-               res = memdb_mkdir(mdb->memdb, path, 0, ctime);
-       }
-
-       return res;
-}
-
-static int cfs_plug_memdb_rmdir(cfs_plug_t *plug, const char *path)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-
-       int res;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       if (mdb->dfsm) {
-               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_DELETE, path, 
-                                            NULL, NULL, 0, 0, 0);
-       } else {
-               uint32_t ctime = time(NULL);
-               res = memdb_delete(mdb->memdb, path, 0, ctime);
-       }
-
-       return res;
-}
-
-static int cfs_plug_memdb_rename(cfs_plug_t *plug, const char *from, const char *to)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(from != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(to != NULL, PARAM_CHECK_ERRNO);
-
-       int res;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       if (mdb->dfsm) {
-               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_RENAME, from, to, 
-                                            NULL, 0, 0, 0);
-       } else {
-               uint32_t ctime = time(NULL);
-               res = memdb_rename(mdb->memdb, from, to, 0, ctime);
-       }
-
-       return res;
-}
-
-static int cfs_plug_memdb_unlink(cfs_plug_t *plug, const char *path)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-
-       int res;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       if (mdb->dfsm) {
-               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_DELETE, path, 
-                                            NULL, NULL, 0, 0, 0);
-       } else {
-               uint32_t ctime = time(NULL);
-               res = memdb_delete(mdb->memdb, path, 0, ctime);
-       }
-
-       return res;
-}
-
-static int cfs_plug_memdb_utimens(
-       cfs_plug_t *plug, 
-       const char *path, 
-       const struct timespec tv[2])
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(tv != NULL, PARAM_CHECK_ERRNO);
-
-       int res;
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       res = -EIO;
-
-       memdb_tree_entry_t *te = memdb_getattr(mdb->memdb, path);
-       uint32_t mtime = tv[1].tv_sec;
-
-       gboolean unlock_req = FALSE;
-       guchar csum[32];
-
-       if (te && mtime == 0 && te->type == DT_DIR &&
-           path_is_lockdir(path)) {
-               unlock_req = TRUE;
-       }
-
-       if (mdb->dfsm) {
-               if (unlock_req && memdb_tree_entry_csum(te, csum))
-                       dcdb_send_unlock(mdb->dfsm, path, csum, TRUE);
-           
-               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_MTIME, path, 
-                                            NULL, NULL, 0, mtime, 0);
-       } else {
-               uint32_t ctime = time(NULL);
-               if (unlock_req && memdb_tree_entry_csum(te, csum) &&
-                   memdb_lock_expired(mdb->memdb, path, csum)) {
-                       res = memdb_delete(mdb->memdb, path, 0, ctime);
-               } else {
-                       res = memdb_mtime(mdb->memdb, path, 0, mtime);
-               }
-       }
-
-       memdb_tree_entry_free(te);
-
-       return res;
-}
-
-static int cfs_plug_memdb_statfs(cfs_plug_t *plug, const char *path, struct statvfs *stbuf)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
-
-       return memdb_statfs(mdb->memdb, stbuf);
-}
-
-static void cfs_plug_memdb_destroy(cfs_plug_t *plug)
-{
-       g_return_if_fail(plug != NULL);
-       g_return_if_fail(plug->ops == &cfs_ops);
-       
-       g_free(plug->name);
-
-       g_free(plug);
-}
-
-static cfs_plug_t *cfs_plug_memdb_lookup_plug(cfs_plug_t *plug, char **path)
-{
-       g_return_val_if_fail(plug != NULL, NULL);
-       g_return_val_if_fail(plug->ops == &cfs_ops, NULL);
-
-       return plug;
-}
-
-static struct cfs_operations cfs_ops = {
-       .getattr = cfs_plug_memdb_getattr,
-       .readdir = cfs_plug_memdb_readdir,
-       .open = cfs_plug_memdb_open,
-       .create = cfs_plug_memdb_create,
-       .read   = cfs_plug_memdb_read,
-       .write = cfs_plug_memdb_write,
-       .truncate = cfs_plug_memdb_truncate,
-       .unlink = cfs_plug_memdb_unlink,
-       .mkdir = cfs_plug_memdb_mkdir,
-       .rmdir = cfs_plug_memdb_rmdir,
-       .rename = cfs_plug_memdb_rename,
-       .utimens = cfs_plug_memdb_utimens,
-       .statfs = cfs_plug_memdb_statfs,
-#ifdef HAS_CFS_PLUG_MEMDB_LOCK
-       .lock = cfs_plug_memdb_lock,
-#endif
-};
-
-cfs_plug_memdb_t *cfs_plug_memdb_new(
-       const char *name, 
-       memdb_t *memdb,
-       dfsm_t *dfsm)
-{
-       g_return_val_if_fail(name != NULL, NULL);
-       g_return_val_if_fail(memdb != NULL, NULL);
-
-       cfs_plug_memdb_t *mdb = g_new0(cfs_plug_memdb_t, 1);
-
-       g_return_val_if_fail(mdb != NULL, NULL);
-
-       mdb->plug.ops = &cfs_ops;
-
-       mdb->plug.lookup_plug = cfs_plug_memdb_lookup_plug;
-
-       mdb->plug.destroy_plug = cfs_plug_memdb_destroy;
-
-       mdb->plug.name = g_strdup(name);
-
-       mdb->memdb = memdb;
-
-       mdb->dfsm = dfsm;
-
-       return mdb;
-}
diff --git a/data/src/cfs-plug-memdb.h b/data/src/cfs-plug-memdb.h
deleted file mode 100644 (file)
index 936272c..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_CFS_PLUG_MEMDB_H_
-#define _PVE_CFS_PLUG_MEMDB_H_
-
-#include <unistd.h>
-#include <fcntl.h>
-#include "cfs-plug.h"
-
-#include "dfsm.h"
-#include "memdb.h"
-
-
-typedef struct {
-       cfs_plug_t plug;
-       memdb_t *memdb;
-       dfsm_t *dfsm;
-} cfs_plug_memdb_t;
-
-cfs_plug_memdb_t *cfs_plug_memdb_new(
-       const char *name, 
-       memdb_t *memdb, 
-       dfsm_t *dfsm);
-
-#endif /* _PVE_CFS_PLUG_MEMDB_H_ */
diff --git a/data/src/cfs-plug.c b/data/src/cfs-plug.c
deleted file mode 100644 (file)
index 108ed6b..0000000
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <glib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/file.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <dirent.h>
-
-#include "cfs-utils.h"
-#include "cfs-plug.h"
-
-static struct cfs_operations cfs_ops;
-
-static cfs_plug_t *cfs_plug_base_lookup_plug(cfs_plug_t *plug, char **path)
-{
-       g_return_val_if_fail(plug != NULL, NULL);
-       g_return_val_if_fail(plug->ops == &cfs_ops, NULL);
-       g_return_val_if_fail(path != NULL, NULL);
-
-       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
-
-       g_return_val_if_fail(bplug->entries != NULL, NULL);
-
-       cfs_debug("cfs_plug_base_lookup_plug %s", *path);
-
-       if (!*path || !(*path)[0])
-               return plug;
-
-       char *name = strsep(path, "/");
-
-       cfs_debug("cfs_plug_base_lookup_plug name = %s new path = %s", name, *path);
-
-       cfs_plug_t *sub;
-
-       if (!(sub = (cfs_plug_t *)g_hash_table_lookup(bplug->entries, name))) {
-               /* revert strsep modification */
-               if (*path) (*path)[-1] = '/';
-               *path = name;
-               return plug;
-       }
-
-       if ((sub = sub->lookup_plug(sub, path)))
-               return sub;
-
-       *path = NULL;
-       return NULL;
-}
-
-static int cfs_plug_base_getattr(cfs_plug_t *plug, const char *path, struct stat *stbuf)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_getattr %s", path);
-
-       int ret = -EACCES;
-
-       memset(stbuf, 0, sizeof(struct stat));
-
-       if (*path) {
-               cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-               if (base && base->ops && base->ops->getattr)
-                       ret = base->ops->getattr(base, path, stbuf);
-               goto ret;
-       }
-
-       stbuf->st_mode = S_IFDIR | 0777;
-       stbuf->st_nlink = 2;
-       ret = 0;
-
-ret:
-       cfs_debug("leave cfs_plug_base_getattr %s", path);
-       return ret;
-}
-
-
-struct hash_filler {
-       void *buf;
-       GHashTable *entries;
-       fuse_fill_dir_t filler;
-};
-
-static int tmp_hash_filler (
-       void *buf, 
-       const char *name,
-       const struct stat *stbuf, 
-       off_t off) {
-
-       struct hash_filler *hf = (struct hash_filler *)buf;
-
-       if (hf->entries && g_hash_table_lookup(hf->entries, name))
-               return 0;
-               
-       if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] ==  0)))
-               return 0;
-
-       hf->filler(hf->buf, name, stbuf, off);
-
-       return 0;
-}
-
-static int cfs_plug_base_readdir(cfs_plug_t *plug, const char *path, void *buf, fuse_fill_dir_t filler,
-                                off_t offset, struct fuse_file_info *fi)
-{
-       (void) offset;
-       (void) fi;
-
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(filler != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
-
-       cfs_debug("enter cfs_plug_base_readdir %s", path);
-
-       int ret = -EACCES;
-
-       filler(buf, ".", NULL, 0);
-       filler(buf, "..", NULL, 0);
-
-       if (!path[0]) {
-               GHashTableIter iter;
-               gpointer key, value;
-
-               g_hash_table_iter_init (&iter, bplug->entries);
-
-               while (g_hash_table_iter_next (&iter, &key, &value)) {
-                       filler(buf, key, NULL, 0);
-               }
-       }
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->readdir) {
-               struct hash_filler hf = { 
-                       .buf = buf, 
-                       .filler = filler,
-                       .entries = NULL
-               };
-
-               if (!path[0])
-                       hf.entries = bplug->entries;
-
-               ret = base->ops->readdir(base, path, &hf, tmp_hash_filler, 0, fi);
-
-       } else {
-               ret = 0;
-       }       
-       
-       return ret;
-}
-
-static int cfs_plug_base_mkdir(cfs_plug_t *plug, const char *path, mode_t mode)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_mkdir %s", path);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (*path && base && base->ops && base->ops->mkdir)
-               ret = base->ops->mkdir(base, path, mode);
-
-       return ret;
-}
-
-static int cfs_plug_base_rmdir(cfs_plug_t *plug, const char *path)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_rmdir %s", path);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (*path && base && base->ops && base->ops->rmdir)
-               ret = base->ops->rmdir(base, path);
-
-       return ret;
-}
-
-static int cfs_plug_base_rename(cfs_plug_t *plug, const char *from, const char *to)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(from != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(to != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_rename from %s to %s", from, to);
-
-       int ret = -EACCES;
-       
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->rename)
-               ret = base->ops->rename(base, from, to);
-
-       return ret;
-}
-
-static int cfs_plug_base_open(cfs_plug_t *plug, const char *path, struct fuse_file_info *fi)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_open %s", path);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->open)
-               ret =  base->ops->open(base, path, fi);
-
-       return ret;
-}
-
-static int cfs_plug_base_read(cfs_plug_t *plug, const char *path, char *buf, 
-                             size_t size, off_t offset, struct fuse_file_info *fi)
-{
-       (void) fi;
-
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_read %s %zu %jd", path, size, offset);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->read)
-               ret = base->ops->read(base, path, buf, size, offset, fi);
-
-       return ret;
-}
-
-static int cfs_plug_base_write(cfs_plug_t *plug, const char *path, const char *buf, 
-                              size_t size, off_t offset, struct fuse_file_info *fi)
-{
-       (void) fi;
-
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_write %s %zu %jd", path, size, offset);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->write)
-               ret = base->ops->write(base, path, buf, size, offset, fi);
-
-       return ret;
-}
-
-static int cfs_plug_base_truncate(cfs_plug_t *plug, const char *path, off_t size)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_truncate %s %jd", path, size);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->truncate)
-               ret = base->ops->truncate(base, path, size);
-
-       return ret;
-}
-
-static int cfs_plug_base_create(cfs_plug_t *plug, const char *path, mode_t mode, 
-                               struct fuse_file_info *fi)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_create %s", path);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->create)
-               ret = base->ops->create(base, path, mode, fi);
-
-       return ret;
-}
-
-static int cfs_plug_base_unlink(cfs_plug_t *plug, const char *path)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_unlink %s", path);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->unlink)
-               ret = base->ops->unlink(base, path);
-
-       return ret;
-}
-
-static int cfs_plug_base_readlink(cfs_plug_t *plug, const char *path, char *buf, size_t max)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_readlink %s", path);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->readlink)
-               ret = base->ops->readlink(base, path, buf, max);
-
-       return ret;
-}
-
-static int cfs_plug_base_utimens(cfs_plug_t *plug, const char *path, const struct timespec tv[2])
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(tv != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_utimes %s", path);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->utimens)
-               ret = base->ops->utimens(base, path, tv);
-
-       return ret;
-}
-
-static int cfs_plug_base_statfs(cfs_plug_t *plug, const char *path, struct statvfs *stbuf)
-{
-       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
-       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_plug_base_statfs %s", path);
-
-       int ret = -EACCES;
-
-       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
-
-       if (base && base->ops && base->ops->statfs)
-               ret = base->ops->statfs(base, path, stbuf);
-
-       return ret;
-}
-
-static gboolean plug_remove_func(
-       gpointer key,
-       gpointer value,
-       gpointer user_data)
-{
-       cfs_plug_t *plug = (cfs_plug_t *)value;
-
-       if (plug && plug->destroy_plug)
-               plug->destroy_plug(plug);
-
-       return TRUE;
-}
-
-static void cfs_plug_base_destroy(cfs_plug_t *plug)
-{
-       g_return_if_fail(plug != NULL);
-       g_return_if_fail(plug->ops == &cfs_ops);
-
-       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
-
-       cfs_debug("enter cfs_plug_base_destroy %s", plug->name);
-
-       if (bplug->entries) {
-               g_hash_table_foreach_remove(bplug->entries, plug_remove_func, NULL);
-               g_hash_table_destroy(bplug->entries);
-       }
-
-       if (bplug->base && bplug->base->destroy_plug) {
-               bplug->base->destroy_plug(bplug->base);
-       }
-
-       g_free(plug->name);
-
-       g_free(plug);
-}
-
-static void cfs_plug_base_start_workers(cfs_plug_t *plug)
-{
-       g_return_if_fail(plug != NULL);
-       g_return_if_fail(plug->ops == &cfs_ops);
-
-       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
-       GHashTableIter iter;
-       gpointer key, value;
-
-       g_hash_table_iter_init (&iter, bplug->entries);
-
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-
-               cfs_plug_t *p = (cfs_plug_t *)value;
-               
-               if (p->start_workers)
-                       p->start_workers(p);
-
-       }
-
-       if (bplug->base && bplug->base->start_workers) {
-               bplug->base->start_workers(bplug->base);
-       }
-
-}
-
-static void cfs_plug_base_stop_workers(cfs_plug_t *plug)
-{
-       g_return_if_fail(plug != NULL);
-       g_return_if_fail(plug->ops == &cfs_ops);
-
-       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
-       GHashTableIter iter;
-       gpointer key, value;
-
-       g_hash_table_iter_init (&iter, bplug->entries);
-
-       if (bplug->base && bplug->base->stop_workers) {
-               bplug->base->stop_workers(bplug->base);
-       }
-
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-
-               cfs_plug_t *p = (cfs_plug_t *)value;
-               
-               if (p->stop_workers)
-                       p->stop_workers(p);
-
-       }
-}
-
-static struct cfs_operations cfs_ops = {
-       .getattr = cfs_plug_base_getattr,
-       .create = cfs_plug_base_create,
-       .open   = cfs_plug_base_open,
-       .read   = cfs_plug_base_read,
-       .write = cfs_plug_base_write,
-       .truncate = cfs_plug_base_truncate,
-       .unlink = cfs_plug_base_unlink,
-       .readdir = cfs_plug_base_readdir,
-       .mkdir = cfs_plug_base_mkdir,
-       .rmdir = cfs_plug_base_rmdir,
-       .rename = cfs_plug_base_rename,
-       .readlink = cfs_plug_base_readlink,
-       .utimens = cfs_plug_base_utimens,
-       .statfs = cfs_plug_base_statfs,
-};
-
-cfs_plug_base_t *cfs_plug_base_new(const char *name, cfs_plug_t *base)
-{
-       g_return_val_if_fail(name != NULL, NULL);
-       g_return_val_if_fail(base != NULL, NULL);
-
-       cfs_plug_base_t *plug = g_new0(cfs_plug_base_t, 1);
-
-       plug->plug.lookup_plug = cfs_plug_base_lookup_plug;
-       plug->plug.destroy_plug = cfs_plug_base_destroy;
-       plug->plug.start_workers = cfs_plug_base_start_workers;
-       plug->plug.stop_workers = cfs_plug_base_stop_workers;
-
-       plug->entries =  g_hash_table_new(g_str_hash, g_str_equal);
-
-       plug->plug.name = g_strdup(name);
-
-       plug->plug.ops = &cfs_ops;
-
-       plug->base = base;
-
-       return plug;
-}
-
-void cfs_plug_base_insert(cfs_plug_base_t *bplug, cfs_plug_t *sub)
-{
-       g_return_if_fail(bplug != NULL);
-       g_return_if_fail(sub != NULL);
-       g_return_if_fail(sub->name != NULL);
-
-       g_hash_table_replace(bplug->entries, sub->name, sub);
-}
diff --git a/data/src/cfs-plug.h b/data/src/cfs-plug.h
deleted file mode 100644 (file)
index 6fd2635..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_CFS_PLUG_H_
-#define _PVE_CFS_PLUG_H_
-
-#define FUSE_USE_VERSION 26
-
-#include <errno.h>
-#include <fuse.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#define PARAM_CHECK_ERRNO -EREMOTEIO
-
-typedef struct cfs_plug cfs_plug_t;
-
-struct cfs_operations {
-       int (*getattr) (cfs_plug_t *, const char *, struct stat *);
-       int (*readlink) (cfs_plug_t *, const char *, char *, size_t);
-       int (*mkdir) (cfs_plug_t *, const char *, mode_t);
-       int (*unlink) (cfs_plug_t *, const char *);
-       int (*rmdir) (cfs_plug_t *, const char *);
-       int (*rename) (cfs_plug_t *, const char *, const char *);
-       int (*truncate) (cfs_plug_t *, const char *, off_t);
-       int (*open) (cfs_plug_t *, const char *, struct fuse_file_info *);
-       int (*read) (cfs_plug_t *, const char *, char *, size_t, off_t,
-                    struct fuse_file_info *);
-       int (*write) (cfs_plug_t *, const char *, const char *, size_t, off_t,
-                     struct fuse_file_info *);
-       int (*readdir) (cfs_plug_t *, const char *, void *, fuse_fill_dir_t, off_t,
-                       struct fuse_file_info *);
-       int (*create) (cfs_plug_t *, const char *, mode_t, struct fuse_file_info *);
-       int (*utimens) (cfs_plug_t *, const char *, const struct timespec tv[2]);
-       int (*statfs) (cfs_plug_t *, const char *, struct statvfs *);
-};
-
-struct cfs_plug {
-       struct cfs_operations *ops;
-       cfs_plug_t *(*lookup_plug)(cfs_plug_t *plug, char **path);
-       void (*destroy_plug) (cfs_plug_t *plug);
-       void (*start_workers) (cfs_plug_t *plug);
-       void (*stop_workers) (cfs_plug_t *plug);
-
-       char *name;
-};
-
-typedef struct {
-       cfs_plug_t plug;
-       cfs_plug_t *base;
-       GHashTable *entries;
-} cfs_plug_base_t;
-
-typedef struct {
-       cfs_plug_t plug;
-       char *symlink;
-} cfs_plug_link_t;
-
-typedef char *(*cfs_plug_func_update_data_fn_t)(cfs_plug_t *plug);
-typedef int (*cfs_plug_func_write_data_fn_t)(
-       cfs_plug_t *plug, 
-       const char *buf,
-       size_t size);
-
-typedef struct {
-       cfs_plug_t plug;
-       char *data;
-       GRWLock data_rw_lock;
-       mode_t mode;
-       cfs_plug_func_update_data_fn_t update_callback;
-       cfs_plug_func_write_data_fn_t write_callback;
-} cfs_plug_func_t;
-
-cfs_plug_base_t *cfs_plug_base_new(const char *name, cfs_plug_t *base);
-void cfs_plug_base_insert(cfs_plug_base_t *base, cfs_plug_t *sub);
-
-cfs_plug_link_t *cfs_plug_link_new(const char *name, const char *symlink);
-cfs_plug_func_t *cfs_plug_func_new(
-       const char *name, 
-       mode_t mode,
-       cfs_plug_func_update_data_fn_t update_callback,
-       cfs_plug_func_write_data_fn_t write_callback);
-
-
-#endif /* _PVE_CFS_PLUG_H_ */
diff --git a/data/src/cfs-utils.c b/data/src/cfs-utils.c
deleted file mode 100644 (file)
index 5770833..0000000
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <utime.h>
-#include <sys/stat.h>
-#include <glib.h>
-#include <qb/qblog.h>
-
-#include "cfs-utils.h"
-
-static const char * hexchar = "0123456789abcdef";
-
-/* convert utf8 to json and syslog compatible ascii */
-void
-utf8_to_ascii(
-       char *buf, 
-       int bufsize, 
-       const char *msg, 
-       gboolean quotequote)
-{
-       g_return_if_fail(buf != NULL);
-
-       *buf = 0;
-
-       g_return_if_fail(bufsize > 10);
-
-       const char *p = msg;
-       char *d = buf;
-       char *end = buf + bufsize - 7;
-
-       if (!g_utf8_validate(msg, -1, NULL)) {
-               while (*p && d < end) {
-                       char c = *p++;
-                       if (c == 34 && quotequote) {
-                               *d++ = '\\';
-                               *d++ = '"';
-                       } else if (c >= 0 && c < 32) {
-                               *d++ = '#';
-                               *d++ = '0';
-                               *(d+1) = hexchar[c % 10]; c = c / 10;
-                               *d = hexchar[c % 10];
-                               d += 2;
-                       } else if (c >= 32 && c < 127) {
-                               *d++ = c;       
-                       } else {
-                               *d++ = '?';
-                       }
-               }
-               *d = 0;
-               return;
-       }
-
-       while (*p && d < end) {
-               gunichar u = g_utf8_get_char(p);
-               if (u == 34 && quotequote) {
-                       *d++ = '\\';
-                       *d++ = '"';
-               } else if (u < 32 || u == 127) {
-                       *d++ = '#';
-                       *(d+2) = hexchar[u % 10]; u = u / 10;
-                       *(d+1) = hexchar[u % 10]; u = u / 10;
-                       *d = hexchar[u % 10];
-                       d += 3;
-               } else if (u < 127) {
-                       *d++ = u;
-               } else if (u < 65536) {
-                       *d++ = '\\';
-                       *d++ = 'u';
-                       *(d+3) = hexchar[u&0xf]; u = u >> 4;
-                       *(d+2) = hexchar[u&0xf]; u = u >> 4;
-                       *(d+1) = hexchar[u&0xf]; u = u >> 4;
-                       *d = hexchar[u&0xf];
-                       d += 4;
-               } else {
-                       /* we simply ignore this */
-               }
-               p = g_utf8_next_char(p);
-       }
-       *d = 0;
-}
-
-void 
-cfs_log(
-       const gchar *log_domain,
-       GLogLevelFlags log_level,
-       const char *file,
-       int         line,
-       const char  *func,
-       const gchar    *format,
-       ...)
-{
-       gint level; 
-
-       switch (log_level & G_LOG_LEVEL_MASK) { 
-       case G_LOG_LEVEL_ERROR: 
-               level=LOG_ERR; 
-               break; 
-       case G_LOG_LEVEL_CRITICAL: 
-               level=LOG_CRIT; 
-               break; 
-       case G_LOG_LEVEL_WARNING: 
-               level=LOG_WARNING; 
-               break; 
-       case G_LOG_LEVEL_MESSAGE: 
-               level=LOG_NOTICE; 
-               break; 
-       case G_LOG_LEVEL_INFO: 
-               level=LOG_INFO; 
-               break; 
-       case G_LOG_LEVEL_DEBUG: 
-               level=LOG_DEBUG;   
-               if (!cfs.debug)
-                       return;
-               break; 
-       default:  
-               level=LOG_INFO; 
-       } 
-
-       va_list args;
-
-       va_start (args, format);
-       char *orgmsg = g_strdup_vprintf (format, args);
-       va_end (args);
-
-       char msg[8192];
-       utf8_to_ascii(msg, sizeof(msg), orgmsg, FALSE);
-
-       uint32_t tag = g_quark_from_string(log_domain);
-
-       qb_log_from_external_source(func, file, "%s", level, line, tag, msg);
-
-       g_free(orgmsg);
-}
-
-guint64 
-cluster_config_version(
-       const gpointer config_data, 
-       gsize config_length)
-{
-       GRegex *regex;
-       GMatchInfo *match_info;
-       guint64 version = 0;
-
-       regex = g_regex_new ("config_version\\s*:\\s*(\\d+)", 0, 0, NULL);
-       g_regex_match_full(regex, config_data, config_length, 0, 0, &match_info, NULL);
-       if (g_match_info_matches (match_info)) {
-               gchar *word = g_match_info_fetch (match_info, 1);
-               if (strlen(word)) {
-                       version = strtoull(word, NULL, 10);
-               }
-               g_free (word);
-       }
-       g_match_info_free (match_info);
-       g_regex_unref (regex);
-
-       return version;
-}
-
-ssize_t 
-safe_read(
-       int fd, 
-       char *buf, 
-       size_t count)
-{
-  ssize_t n;
-
-  do {
-    n = read(fd, buf, count);
-  } while (n < 0 && errno == EINTR);
-
-  return n;
-}
-
-gboolean 
-full_write(
-       int fd, 
-       const char *buf, 
-       size_t len)
-{
-       size_t total;
-
-       total = 0;
-
-       while (len > 0) {
-               ssize_t n;
-               do {
-                       n = write(fd, buf, len);
-               } while (n < 0 && errno == EINTR);
-               
-               if (n < 0)
-                       break;
-               
-               buf += n;
-               total += n;
-               len -= n;
-       }
-
-       return (len == 0);
-}
-
-gboolean 
-atomic_write_file(
-       const char *filename, 
-       gconstpointer data, 
-       size_t len, 
-       mode_t mode, 
-       gid_t gid)
-{
-       g_return_val_if_fail(filename != NULL, FALSE);
-       g_return_val_if_fail(len == 0 || data != NULL, FALSE);
-
-       gboolean res = TRUE;
-
-       char *tmp_name = g_strdup_printf ("%s.XXXXXX", filename);
-       int fd = mkstemp(tmp_name);
-       if (fd == -1) {
-               cfs_critical("Failed to create file '%s': %s", tmp_name, g_strerror(errno));
-               res = FALSE;
-               goto ret;
-       }
-
-       if (fchown(fd, 0, gid) == -1) {
-               cfs_critical("Failed to change group of file '%s': %s", tmp_name, g_strerror(errno));
-               close(fd);
-               goto fail;
-       }
-
-       if (fchmod(fd, mode) == -1) {
-               cfs_critical("Failed to change mode of file '%s': %s", tmp_name, g_strerror(errno));
-               close(fd);
-               goto fail;
-       }
-
-       if (len && !full_write(fd, data, len)) {
-               cfs_critical("Failed to write file '%s': %s", tmp_name, g_strerror(errno));
-               close(fd);
-               goto fail;
-       }
-
-       if (close(fd) == -1) {
-               cfs_critical("Failed to close file '%s': %s", tmp_name, g_strerror(errno));
-               goto fail;
-       }
-
-       if (rename(tmp_name, filename) == -1) {
-               cfs_critical("Failed to rename file from '%s' to '%s': %s", 
-                          tmp_name, filename, g_strerror(errno));
-               goto fail;
-       }
-ret:
-       g_free (tmp_name);
-
-       return res;
-
-fail:
-       unlink(tmp_name);
-
-       res = FALSE;
-
-       goto ret;
-}
-
-gboolean
-path_is_private(const char *path)
-{
-       while (*path == '/') path++;
-
-       if ((strncmp(path, "priv", 4) == 0) && (path[4] == 0 || path[4] == '/')) {
-               return TRUE;
-       } else {
-               if (strncmp(path, "nodes/", 6) == 0) {
-                       const char *tmp = path + 6;
-                       while(*tmp && *tmp != '/') tmp++;
-                       if (*tmp == '/' && 
-                           (strncmp(tmp, "/priv", 5) == 0) && 
-                           (tmp[5] == 0 || tmp[5] == '/')) {
-                               return TRUE;
-                       }
-               }
-       }
-       return FALSE;
-}
-
-gboolean
-path_is_lxc_conf(const char *path)
-{
-       while (*path == '/') path++;
-
-       if (strncmp(path, "nodes/", 6) == 0) {
-               const char *tmp = path + 6;
-               while(*tmp && *tmp != '/') tmp++;
-               if (*tmp == '/' && 
-                   (strncmp(tmp, "/lxc", 4) == 0) && 
-                   (tmp[4] == 0 || tmp[4] == '/')) {
-                       return TRUE;
-               }
-       }
-       
-       return FALSE;
-}
-
-
-gboolean
-path_is_lockdir(const char *path)
-{
-       while (*path == '/') path++;
-
-       return (strncmp(path, "priv/lock/", 10) == 0) && path[10];
-}
diff --git a/data/src/cfs-utils.h b/data/src/cfs-utils.h
deleted file mode 100644 (file)
index eb86379..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_CFS_UTILS_H_
-#define _PVE_CFS_UTILS_H_
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdint.h>
-#include <glib.h>
-#include <fcntl.h>
-
-#define HOST_CLUSTER_CONF_FN "/etc/corosync/corosync.conf"
-#define CFS_PID_FN "/var/run/pve-cluster.pid"
-#define VARLIBDIR "/var/lib/pve-cluster"
-#define RUNDIR "/run/pve-cluster"
-
-#define CFS_MAX(a, b)          (((a) > (b)) ? (a) : (b))
-#define CFS_MIN(a, b)          (((a) < (b)) ? (a) : (b))
-
-typedef struct {
-       char *nodename;
-       char *ip;
-       gid_t gid;
-       int debug;
-} cfs_t;
-
-extern cfs_t cfs;
-
-void
-utf8_to_ascii(
-       char *buf, 
-       int bufsize, 
-       const char *msg, 
-       gboolean quotequote);
-
-void 
-cfs_log(
-       const gchar *log_domain,
-       GLogLevelFlags log_level,
-       const char *file,
-       int         line,
-       const char  *func,
-       const gchar    *format,
-       ...) G_GNUC_PRINTF (6, 7);
-
-void ipc_log_fn(
-       const char *file,
-       int32_t line, 
-       int32_t severity, 
-       const char *msg);
-
-
-#define cfs_debug(...)  G_STMT_START { \
-       if (cfs.debug) \
-               cfs_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__); \
-       } G_STMT_END
-
-#define cfs_dom_debug(domain, ...)  G_STMT_START {     \
-       if (cfs.debug) \
-               cfs_log(domain, G_LOG_LEVEL_DEBUG, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__); \
-       } G_STMT_END
-
-#define cfs_critical(...)  cfs_log(G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__)
-#define cfs_dom_critical(domain, ...)  cfs_log(domain, G_LOG_LEVEL_CRITICAL, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__)
-#define cfs_message(...)  cfs_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__)
-#define cfs_dom_message(domain, ...)  cfs_log(domain, G_LOG_LEVEL_MESSAGE, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__)
-
-guint64 
-cluster_config_version(
-       const gpointer config_data, 
-       gsize config_length);
-
-ssize_t 
-safe_read(
-       int fd, 
-       char *buf, 
-       size_t count);
-
-gboolean 
-full_write(
-       int fd, 
-       const char *buf, 
-       size_t len);
-
-gboolean 
-atomic_write_file(
-       const char *filename, 
-       gconstpointer data, 
-       size_t len, 
-       mode_t mode, 
-       gid_t gid);
-
-gboolean
-path_is_private(const char *path);
-
-gboolean
-path_is_lxc_conf(const char *path);
-
-gboolean
-path_is_lockdir(const char *path);
-
-#endif /* _PVE_CFS_UTILS_H_ */
diff --git a/data/src/check_memdb.c b/data/src/check_memdb.c
deleted file mode 100644 (file)
index d7eb05f..0000000
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <glib.h>
-#include <errno.h>
-#include <string.h>
-#include <check.h>
-#include <errno.h>
-#include <unistd.h>
-
-#include "cfs-utils.h"
-#include "status.h"
-#include "memdb.h"
-
-#if CHECK_MAJOR_VERSION == 0 && CHECK_MINOR_VERSION <= 10
-#define _TEST_TYPE TFun
-#else
-#define _TEST_TYPE const TTest *
-#endif
-
-cfs_t cfs = {
-       .debug = 0,
-       .nodename = "testnode",
-};
-
-#define TESTDB "/tmp/test.db"
-
-static memdb_t *memdb;
-
-static void
-setup(void)
-{
-       unlink(TESTDB);
-       memdb = memdb_open(TESTDB);     
-       ck_assert (memdb != NULL);
-
-       struct statvfs stbuf;
-       ck_assert(memdb_statfs(memdb, &stbuf) == 0);
-
-       int count = stbuf.f_files - stbuf.f_ffree;
-       ck_assert(count == 1);
-}
-       
-void
-teardown(void)
-{
-       ck_assert (memdb != NULL);      
-
-       memdb_close(memdb);
-}
-
-START_TEST(test_indextest1)
-{
-       char namebuf[100];
-
-       time_t ctime = 1234;
-       int testsize = 1024*32;
-       gchar *testdata = g_malloc0(testsize);
-
-       for (int i = 0; i < 100; i++) {
-               sprintf(namebuf, "testfile%d", i);
-
-               ck_assert(memdb_create(memdb, namebuf, 0, ctime) == 0);
-               ck_assert(memdb_write(memdb, namebuf, 0, ctime, testdata, testsize, 0, 0) == testsize);
-       }
-
-       struct statvfs stbuf;
-       ck_assert(memdb_statfs(memdb, &stbuf) == 0);
-
-       int count = stbuf.f_files - stbuf.f_ffree;
-       ck_assert(count == 101);
-
-       memdb_index_t *idx = memdb_encode_index(memdb->index, memdb->root);
-       ck_assert(idx != NULL);
-       
-       ck_assert(idx->version == 201);
-       ck_assert(idx->last_inode == 200);
-       ck_assert(idx->writer == 0);
-       ck_assert(idx->size == 101);
-       ck_assert(idx->bytes == (101*40 + sizeof( memdb_index_t)));
-
-       GChecksum *sha256 = g_checksum_new(G_CHECKSUM_SHA256); 
-       ck_assert(sha256 != NULL);
-       g_checksum_update(sha256, (unsigned char *)idx, idx->bytes);
-       const char *csum = g_checksum_get_string(sha256);
-       ck_assert_msg(
-               strcmp(csum, "913fd95015af9d93f10dd51ba2a7bb11351bcfe040be21e95fcba834adc3ec10") == 0,
-               "wrong idx checksum %s",
-               csum
-       );
-       g_free(idx);
-       g_free(testdata);
-
-}
-END_TEST
-
-START_TEST (test_dirtest1)
-{
-       const char *dn = "/dir1";
-       const char *sdn = "/dir1/sdir1";
-       time_t ctime = 1234;
-
-       ck_assert(memdb_mkdir(memdb, sdn, 0, ctime) == -ENOENT);
-       ck_assert(memdb_delete(memdb, dn, 0, ctime) == -ENOENT);
-
-       ck_assert(memdb_mkdir(memdb, dn, 0, ctime) == 0);
-       ck_assert(memdb_mkdir(memdb, dn, 0, ctime) == -EEXIST);
-       ck_assert(memdb_mkdir(memdb, sdn, 0, ctime) == 0);
-       ck_assert(memdb_mkdir(memdb, sdn, 0, ctime) == -EEXIST);
-       ck_assert(memdb_delete(memdb, dn, 0, ctime) == -ENOTEMPTY);
-       ck_assert(memdb_delete(memdb, sdn, 0, ctime) == 0);
-       ck_assert(memdb_delete(memdb, dn, 0, ctime) == 0);
-}
-END_TEST
-
-START_TEST (test_filetest1)
-{
-       const char *dn = "/dir1";
-       const char *fn = "/dir1/f1";
-       time_t ctime = 1234;
-       gpointer data;
-
-       char buf[1024];
-       memset(buf, 0, sizeof(buf));
-
-       ck_assert(memdb_read(memdb, fn, &data) == -ENOENT);
-
-       ck_assert(memdb_mkdir(memdb, dn, 0, ctime) == 0);
-
-       ck_assert(memdb_read(memdb, fn, &data) == -ENOENT);
-
-       ck_assert(memdb_write(memdb, fn, 0, ctime, buf, sizeof(buf), 0, 0) == -ENOENT);
-
-       ck_assert(memdb_create(memdb, fn, 0, ctime) == 0);
-
-       ck_assert(memdb_write(memdb, fn, 0, ctime, buf, sizeof(buf), 0, 0) == sizeof(buf));
-
-       ck_assert(memdb_read(memdb, fn, &data) == sizeof(buf));
-
-       ck_assert(memcmp(buf, data, sizeof(buf)) == 0);
-
-       g_free(data);
-
-       ck_assert(memdb_write(memdb, fn, 0, ctime, "0123456789", 10, 0, 1) == 10);
-       
-       ck_assert(memdb_read(memdb, fn, &data) == 10);
-       g_free(data);
-
-       ck_assert(memdb_write(memdb, fn, 0, ctime, "X", 1, 3, 0) == 1);
-
-       ck_assert(memdb_write(memdb, fn, 0, ctime, "X", 1, 6, 0) == 1);
-
-       ck_assert(memdb_read(memdb, fn, &data) == 10);
-
-       ck_assert(strncmp(data, "012X45X789", 10) == 0);
-       g_free(data);
-
-       ck_assert(memdb_delete(memdb, fn, 0, ctime) == 0);
-
-       ck_assert(memdb_delete(memdb, fn, 0, ctime) == -ENOENT);
-
-       ck_assert(memdb_delete(memdb, dn, 0, ctime) == 0);
-}
-END_TEST
-
-/* Nornmaly, parent inode number is always less than contained inode,
- * but this is not allways the case. A simple move can destroy that 
- * ordering. This code test the placeholder algorithm in 
- * bdb_backend_load_index()
- */
-START_TEST (test_loaddb1)
-{
-       time_t ctime = 1234;
-
-       ck_assert(memdb_mkdir(memdb, "dir1", 0, ctime) == 0);
-
-       ck_assert(memdb_create(memdb, "dir1/file1", 0, ctime) == 0);
-
-       ck_assert(memdb_create(memdb, "dir1/file2", 0, ctime) == 0);
-
-       ck_assert(memdb_mkdir(memdb, "dir2", 0, ctime) == 0);
-
-       ck_assert(memdb_rename(memdb, "dir1/file1", "dir2/file1", 0, ctime) == 0);
-
-       ck_assert(memdb_rename(memdb, "dir1/file2", "dir2/file2", 0, ctime) == 0);
-
-       ck_assert(memdb_create(memdb, "dir2/file1", 0, ctime) == -EEXIST);
-
-       ck_assert(memdb_create(memdb, "dir2/file2", 0, ctime) == -EEXIST);
-
-       //memdb_dump(memdb);
-
-       memdb_close(memdb);
-
-       memdb = memdb_open(TESTDB);     
-       ck_assert (memdb != NULL);
-
-       ck_assert(memdb_create(memdb, "dir2/file1", 0, ctime) == -EEXIST);
-
-       ck_assert(memdb_create(memdb, "dir2/file2", 0, ctime) == -EEXIST);
-
-       //memdb_dump(memdb);
-
-}
-END_TEST
-
-START_TEST (test_loaddb2)
-{
-       time_t ctime = 1234;
-
-       ck_assert(memdb_mkdir(memdb, "dir1", 0, ctime) == 0);
-
-       ck_assert(memdb_mkdir(memdb, "dir1/sd1", 0, ctime) == 0);
-
-       ck_assert(memdb_create(memdb, "dir1/file1", 0, ctime) == 0);
-
-       ck_assert(memdb_create(memdb, "dir1/file2", 0, ctime) == 0);
-
-       ck_assert(memdb_mkdir(memdb, "dir2", 0, ctime) == 0);
-
-       ck_assert(memdb_rename(memdb, "dir1/sd1", "dir2/sd1", 0, ctime) == 0);
-
-       ck_assert(memdb_rename(memdb, "dir1/file1", "dir2/sd1/file1", 0, ctime) == 0);
-
-       ck_assert(memdb_rename(memdb, "dir1/file2", "dir2/sd1/file2", 0, ctime) == 0);
-
-       ck_assert(memdb_create(memdb, "dir2/file3", 0, ctime) == 0);
-
-       ck_assert(memdb_mkdir(memdb, "dir2/sd1", 0, ctime) == -EEXIST);
-
-       //memdb_dump(memdb);
-
-       memdb_close(memdb);
-
-       memdb = memdb_open(TESTDB);     
-       ck_assert (memdb != NULL);
-
-       ck_assert(memdb_mkdir(memdb, "dir2/sd1", 0, ctime) == -EEXIST);
-
-       //memdb_dump(memdb);
-
-}
-END_TEST
-
-static void
-add_test(
-       Suite *s,
-       _TEST_TYPE tf,
-       const char *name)
-{
-       TCase *tc = tcase_create (name);
-       tcase_add_checked_fixture (tc, setup, teardown);
-       tcase_add_test (tc, tf);
-       suite_add_tcase (s, tc);
-}
-
-static Suite *
-memdb_suite(void)
-{
-       Suite *s = suite_create ("memdb");
-
-       add_test(s, test_dirtest1, "dirtest1");
-
-       add_test(s, test_filetest1, "filetest1");
-       add_test(s, test_indextest1, "indextest1");
-       add_test(s, test_loaddb1, "loaddb1");
-       add_test(s, test_loaddb2, "loaddb2");
-       return s;
-}
-
-int
-main(void)
-{
-       int number_failed;
-
-       cfs_status_init();
-
-       Suite *s = memdb_suite();
-       SRunner *sr = srunner_create(s);
-       srunner_run_all(sr, CK_NORMAL);
-       number_failed = srunner_ntests_failed(sr);
-       srunner_free(sr);
-
-       return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
-}
-
diff --git a/data/src/confdb.c b/data/src/confdb.c
deleted file mode 100644 (file)
index 839f576..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-
-/* see "man cmap_overview" and "man cmap_keys" */
-
-#define G_LOG_DOMAIN "confdb"
-
-#define CLUSTER_KEY "cluster"
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <glib.h>
-
-#include <corosync/cmap.h>
-
-#include "cfs-utils.h"
-#include "loop.h"
-#include "status.h"
-
-typedef struct {
-       cmap_handle_t handle;
-       cmap_track_handle_t track_nodelist_handle;
-       cmap_track_handle_t track_version_handle;
-       gboolean changes;
-} cs_private_t;
-
-static cs_error_t
-cmap_read_clusternodes(
-       cmap_handle_t handle,
-       cfs_clinfo_t *clinfo)
-{
-       cs_error_t result;
-        cmap_iter_handle_t iter;
-
-       result = cmap_iter_init(handle, "nodelist.node.", &iter);
-       if (result != CS_OK) {
-               cfs_critical("cmap_iter_init failed %d", result);
-               return result;
-       }
-
-       cmap_value_types_t type;
-       char key_name[CMAP_KEYNAME_MAXLEN + 1];
-       size_t value_len;
-
-       int last_id = -1;
-       uint32_t nodeid = 0;
-       uint32_t votes = 0;
-       char *name = NULL;
-
-       while ((result = cmap_iter_next(handle, iter, key_name, &value_len, &type)) == CS_OK) {
-               int id;
-               char subkey[CMAP_KEYNAME_MAXLEN + 1];
-               if (sscanf(key_name, "nodelist.node.%d.%s", &id, subkey) != 2) continue;
-
-               if (id != last_id) {
-                       if (name && nodeid) {
-                               cfs_clnode_t *clnode = cfs_clnode_new(name, nodeid, votes);
-                               cfs_clinfo_add_node(clinfo, clnode);
-                       }
-                       last_id = id;
-                       free(name);
-                       name = NULL;
-                       nodeid = 0;
-                       votes = 0;
-               }
-
-               if (strcmp(subkey, "nodeid") == 0) {
-                       if ((result = cmap_get_uint32(handle, key_name, &nodeid)) != CS_OK) {
-                               cfs_critical("cmap_get %s failed %d", key_name, result);
-                       }
-               } else if (strcmp(subkey, "quorum_votes") == 0) {
-                       if ((result = cmap_get_uint32(handle, key_name, &votes)) != CS_OK) {
-                               cfs_critical("cmap_get %s failed %d", key_name, result);
-                       }
-               } else if (strcmp(subkey, "ring0_addr") == 0) {
-                       // prefering the 'name' subkey over 'ring0_addr', needed for RRP
-                       // and when using a IP address for ring0_addr
-                       if (name == NULL &&
-                           (result = cmap_get_string(handle, key_name, &name)) != CS_OK) {
-                               cfs_critical("cmap_get %s failed %d", key_name, result);
-                       }
-               } else if (strcmp(subkey, "name") == 0) {
-                       free(name);
-                       name = NULL;
-                       if ((result = cmap_get_string(handle, key_name, &name)) != CS_OK) {
-                               cfs_critical("cmap_get %s failed %d", key_name, result);
-                       }
-               }
-       }
-
-       if (name && nodeid) {
-               cfs_clnode_t *clnode = cfs_clnode_new(name, nodeid, votes);
-               cfs_clinfo_add_node(clinfo, clnode);
-       }
-       free(name);
-
-        result = cmap_iter_finalize(handle, iter);
-       if (result != CS_OK) {
-               cfs_critical("cmap_iter_finalize failed %d", result);
-               return result;
-       }
-
-       return result;
-}
-
-static cs_error_t
-cmap_read_config(cmap_handle_t handle)
-{
-       cs_error_t result;
-
-       uint64_t config_version = 0;
-
-       result = cmap_get_uint64(handle, "totem.config_version", &config_version);
-       if (result != CS_OK) {
-               cfs_critical("cmap_get totem.config_version failed %d", result);
-               // optional, do not throw error
-       }
-
-       char *clustername = NULL;
-       result = cmap_get_string(handle, "totem.cluster_name", &clustername);
-       if (result != CS_OK) {
-               cfs_critical("cmap_get totem.cluster_name failed %d", result);
-               return result;
-       }
-
-       cfs_clinfo_t *clinfo = cfs_clinfo_new(clustername, config_version);
-       g_free(clustername);
-
-       result = cmap_read_clusternodes(handle, clinfo);
-       if (result == CS_OK) {
-               cfs_status_set_clinfo(clinfo);
-       } else {
-               cfs_clinfo_destroy(clinfo);
-       }
-
-       return result;
-}
-
-static gboolean
-service_cmap_finalize(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_val_if_fail(service != NULL, FALSE);
-       g_return_val_if_fail(context != NULL, FALSE);
-
-       cs_private_t *private = (cs_private_t *)context;
-       cmap_handle_t handle = private->handle;
-       cs_error_t result;
-
-        if (private->track_nodelist_handle) {
-            result = cmap_track_delete(handle, private->track_nodelist_handle);
-            if (result != CS_OK) {
-               cfs_critical("cmap_track_delete nodelist failed: %d", result);
-            }
-            private->track_nodelist_handle = 0;
-        }
-       
-        if (private->track_version_handle) {
-            result = cmap_track_delete(handle, private->track_version_handle);
-            if (result != CS_OK) {
-               cfs_critical("cmap_track_delete version failed: %d", result);
-            }
-            private->track_version_handle = 0;
-        }
-
-       result = cmap_finalize(handle);
-       private->handle = 0;
-       if (result != CS_OK) {
-               cfs_critical("cmap_finalize failed: %d", result);
-               return FALSE;
-       }
-
-       return TRUE;
-}
-
-static void
-track_callback(
-    cmap_handle_t cmap_handle,
-    cmap_track_handle_t cmap_track_handle,
-    int32_t event,
-    const char *key_name,
-    struct cmap_notify_value new_value,
-    struct cmap_notify_value old_value,
-    void *context)
-{
-       g_return_if_fail(context != NULL);
-
-       cs_private_t *private = (cs_private_t *)context;
-
-       cfs_debug("track_callback %s %d\n", key_name, event);
-
-       private->changes = TRUE;
-}
-
-
-static int
-service_cmap_initialize(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_val_if_fail(service != NULL, FALSE);
-       g_return_val_if_fail(context != NULL, FALSE);
-
-       cs_private_t *private = (cs_private_t *)context;
-
-        // fixme: do not copy (use pointer)
-       cmap_handle_t handle = private->handle;
-       cs_error_t result;
-
-       if (!private->handle) {
-
-               result = cmap_initialize(&handle);
-               if (result != CS_OK) {
-                       cfs_critical("cmap_initialize failed: %d", result);
-                       private->handle = 0;
-                       return -1;
-               }
-
-               result = cmap_context_set(handle, private);
-               if (result != CS_OK) {
-                       cfs_critical("cmap_context_set failed: %d", result);
-                       cmap_finalize(handle);
-                       private->handle = 0;
-                       return -1;
-               }
-
-               private->handle = handle;
-       }
-
-       
-        result = cmap_track_add(handle, "nodelist.node.",
-                               CMAP_TRACK_PREFIX|CMAP_TRACK_ADD|CMAP_TRACK_DELETE|CMAP_TRACK_MODIFY,
-                                track_callback, context, &private->track_nodelist_handle);
-
-       if (result == CS_OK) {
-               result = cmap_track_add(handle, "totem.config_version",
-                                       CMAP_TRACK_ADD|CMAP_TRACK_DELETE|CMAP_TRACK_MODIFY,
-                                       track_callback, context, &private->track_version_handle);
-       }
-
-       if (result == CS_ERR_LIBRARY || result == CS_ERR_BAD_HANDLE) {
-               cfs_critical("cmap_track_changes failed: %d - closing handle", result);
-               cmap_finalize(handle);
-               private->handle = 0;
-               return -1;
-       } else if (result != CS_OK) {
-                cfs_critical("cmap_track_changes failed: %d - trying again", result);
-               return -1;
-       }
-
-       int cmap_fd = -1;
-       if ((result = cmap_fd_get(handle, &cmap_fd)) != CS_OK) {
-               cfs_critical("confdb_fd_get failed %d - trying again", result);
-               return -1;
-       }
-
-       cmap_read_config(handle);
-
-       return cmap_fd;
-}
-
-static gboolean
-service_cmap_dispatch(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_val_if_fail(service != NULL, FALSE);
-       g_return_val_if_fail(context != NULL, FALSE);
-
-       cs_private_t *private = (cs_private_t *)context;
-       cmap_handle_t handle =  private->handle;
-
-       cs_error_t result;
-
-       private->changes = FALSE;
-       int retries = 0;
-loop:
-       result = cmap_dispatch(handle, CS_DISPATCH_ALL);
-       if (result == CS_ERR_TRY_AGAIN) {
-               usleep(100000);
-               ++retries;
-               if ((retries % 100) == 0)
-                       cfs_message("cmap_dispatch retry %d", retries);
-               goto loop;
-       }
-
-
-       if (result == CS_OK || result == CS_ERR_TRY_AGAIN) {
-
-               if (private->changes) {
-                       result = cmap_read_config(handle);
-
-                       private->changes = FALSE;
-
-                       if (result == CS_OK)
-                               return TRUE;
-               }
-       } else {
-               cfs_critical("cmap_dispatch failed: %d", result);
-       }
-
-       cmap_finalize(handle);
-       private->handle = 0;
-       return FALSE;
-}
-
-static cfs_service_callbacks_t cfs_confdb_callbacks = {
-       .cfs_service_initialize_fn =  service_cmap_initialize,
-       .cfs_service_finalize_fn = service_cmap_finalize,
-       .cfs_service_dispatch_fn = service_cmap_dispatch,
-};
-
-cfs_service_t *
-service_confdb_new(void)
-{
-       cfs_service_t *service;
-
-       cs_private_t *private = g_new0(cs_private_t, 1);
-       if (!private)
-               return NULL;
-
-       service = cfs_service_new(&cfs_confdb_callbacks, G_LOG_DOMAIN, private);
-
-       return service;
-}
-
-void
-service_confdb_destroy(cfs_service_t *service)
-{
-       g_return_if_fail(service != NULL);
-
-       cs_private_t *private =
-               (cs_private_t *)cfs_service_get_context(service);
-
-       g_free(private);
-       g_free(service);
-}
diff --git a/data/src/confdb.h b/data/src/confdb.h
deleted file mode 100644 (file)
index 5c77cc4..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_CONFDB_H_
-#define _PVE_CONFDB_H_
-
-#include <glib.h>
-
-#include "loop.h"
-
-cfs_service_t *service_confdb_new(void);
-
-void service_confdb_destroy(
-       cfs_service_t *service);
-
-#endif /* _PVE_CONFDB_H_ */
diff --git a/data/src/create_pmxcfs_db.c b/data/src/create_pmxcfs_db.c
deleted file mode 100644 (file)
index b642477..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-  Note: we use this with the installer to create the initial db
-  without starting pmxcfs/fuse
-*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <glib.h>
-#include <errno.h>
-#include <string.h>
-#include <check.h>
-#include <errno.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <dirent.h>
-
-#include "cfs-utils.h"
-#include "status.h"
-#include "memdb.h"
-
-cfs_t cfs = {
-       .debug = 0,
-       .nodename = "dummy",
-};
-
-static memdb_t *memdb;
-
-static void
-usage_error(void) 
-{
-       fprintf(stderr, "Usage: create_pmxcfs_db /a/dir /a/filename.db\n");
-       exit(-1);
-}
-
-int 
-main(int argc, char *argv[]) 
-{
-       cfs_status_init();
-
-       if (argc != 3) {
-               usage_error();
-       }
-
-       const char *dir_name = argv[1];
-       const char *dbfile = argv[2];
-
-       DIR *dh = opendir(dir_name);
-       if (!dh) {
-               perror("unable to open dir");
-               exit(-1);
-       }
-       memdb = memdb_open(dbfile);     
-
-       struct dirent *de;
-       time_t ctime = time(NULL);
-
-       while((de = readdir(dh))) {
-               if (de->d_type != DT_REG) {
-                       continue;
-               }
-
-               char *cdata = NULL;
-               gsize clen = 0;
-               char *fn = g_strdup_printf("%s/%s", dir_name, de->d_name);
-               if (g_file_get_contents(fn, &cdata, &clen, NULL)) {
-                       //printf("FOUND %ld %s\n", clen, fn);
-                       if (memdb_create(memdb, de->d_name, 0, ctime) != 0) {
-                               fprintf(stderr, "memdb_create '%s' failed\n", de->d_name);
-                               exit(-1);
-                       }
-                       if (memdb_write(memdb, de->d_name, 0, ctime, cdata, clen, 0, 1) != clen) {
-                               fprintf(stderr, "memdb_write '%s' failed\n", de->d_name);
-                               exit(-1);
-                       }
-
-               }
-               g_free(fn);
-       }
-
-       memdb_close(memdb);
-
-       closedir(dh);
-}
diff --git a/data/src/database.c b/data/src/database.c
deleted file mode 100644 (file)
index d1c15a7..0000000
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#define G_LOG_DOMAIN "database"
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <glib.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <errno.h>
-
-#include <sqlite3.h>
-
-#include "cfs-utils.h"
-#include "status.h"
-#include "memdb.h"
-
-struct db_backend {
-       sqlite3 *db;
-       sqlite3_stmt *stmt_insert_entry;
-       sqlite3_stmt *stmt_update_entry;
-       sqlite3_stmt *stmt_replace_entry;
-       sqlite3_stmt *stmt_delete_entry;
-       sqlite3_stmt *stmt_begin;
-       sqlite3_stmt *stmt_commit;
-       sqlite3_stmt *stmt_rollback;
-       sqlite3_stmt *stmt_load_all;
-};
-
-#define VERSIONFILENAME "__version__"
-
-/* colume type "INTEGER PRIMARY KEY" is a special case, because sqlite
- * uses the internal ROWID. So only real interger are allowed, and
- * there is no need to add an additionl check
- */
-static const char *sql_create_db =
-       "CREATE TABLE IF NOT EXISTS tree ("
-       "  inode INTEGER PRIMARY KEY NOT NULL,"
-       "  parent INTEGER NOT NULL CHECK(typeof(parent)=='integer'),"
-       "  version INTEGER NOT NULL CHECK(typeof(version)=='integer'),"
-       "  writer INTEGER NOT NULL CHECK(typeof(writer)=='integer'),"
-       "  mtime INTEGER NOT NULL CHECK(typeof(mtime)=='integer'),"
-       "  type INTEGER NOT NULL CHECK(typeof(type)=='integer'),"
-       "  name TEXT NOT NULL,"
-       "  data BLOB);";
-
-static const char *sql_load_all =
-       "SELECT inode, parent, version, writer, mtime, type, name, data FROM tree;";
-
-static char *sql_insert_entry =
-       "INSERT INTO tree ("
-       "inode, parent, version, writer, mtime, type, name, data) "
-       "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);";
-
-static char *sql_update_entry =
-       "UPDATE tree SET parent = ?2, version = ?3, writer = ?4, mtime = ?5, "
-       "type = ?6, name = ?7, data = ?8 WHERE inode = ?1;";
-
-static char *sql_replace_entry =
-       "REPLACE INTO tree (inode, parent, version, writer, mtime, type, "
-       "name, data) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);";
-
-static char *sql_delete_entry =
-       "DELETE FROM tree WHERE inode = ?1;";
-
-static char *sql_begin = "BEGIN TRANSACTION;";
-static char *sql_commit = "COMMIT TRANSACTION;";
-static char *sql_rollback = "ROLLBACK TRANSACTION;";
-
-static sqlite3 *bdb_create(
-       const char *filename)
-{
-       int rc;
-       sqlite3 *db = NULL;
-
-       int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
-       rc = sqlite3_open_v2(filename, &db, flags, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("splite3_open_v2 failed: %d\n", rc);
-               sqlite3_close(db);
-               return NULL;
-       }
-
-       if (chmod(filename, 0600) == -1) {
-               cfs_critical("chmod failed: %s", strerror(errno));
-               return NULL;
-       }
-
-       /* use WAL mode - to allow concurrent reads */
-       rc = sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("unable to set WAL mode: %s\n", sqlite3_errmsg(db));
-               sqlite3_close(db);
-               return NULL;
-       }
-
-       /* NORMAL is good enough when using WAL */
-       rc = sqlite3_exec(db, "PRAGMA synchronous=NORMAL",  NULL, NULL, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("unable to set synchronous mode: %s\n", sqlite3_errmsg(db));
-               sqlite3_close(db);
-               return NULL;
-       }
-
-       sqlite3_busy_timeout(db, 10000); /* 10 seconds */
-
-       rc = sqlite3_exec(db, sql_create_db, NULL, NULL, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("init database failed: %s\n", sqlite3_errmsg(db));
-               sqlite3_close(db);
-               return NULL;
-       }
-
-       return db;
-}
-
-static int backend_write_inode(
-       sqlite3 *db,
-       sqlite3_stmt *stmt,
-       guint64 inode,
-       guint64 parent,
-       guint64 version,
-       guint32 writer,
-       guint32 mtime,
-       guint32 size,
-       char type,
-       char *name,
-       gpointer value)
-{
-       int rc;
-
-       cfs_debug("enter backend_write_inode %016" PRIX64 " '%s', size %"PRIu32"", inode, name, size);
-
-       if ((rc = sqlite3_bind_int64(stmt, 1, inode)) !=  SQLITE_OK) {
-               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
-               return rc;
-       }
-       if ((rc = sqlite3_bind_int64(stmt, 2, parent)) !=  SQLITE_OK) {
-               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
-               return rc;
-       }
-       if ((rc = sqlite3_bind_int64(stmt, 3, version)) !=  SQLITE_OK) {
-               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
-               return rc;
-       }
-       if ((rc = sqlite3_bind_int64(stmt, 4, writer)) !=  SQLITE_OK) {
-               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
-               return rc;
-       }
-       if ((rc = sqlite3_bind_int64(stmt, 5, mtime)) !=  SQLITE_OK) {
-               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
-               return rc;
-       }
-       if ((rc = sqlite3_bind_int64(stmt, 6, type)) !=  SQLITE_OK) {
-               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
-               return rc;
-       }
-       if ((rc = sqlite3_bind_text(stmt, 7, name, -1, SQLITE_STATIC)) !=  SQLITE_OK) {
-               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
-               return rc;
-       }
-       if ((rc = sqlite3_bind_blob(stmt, 8, value, size, SQLITE_STATIC)) !=  SQLITE_OK) {
-               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
-               return rc;
-       }
-
-       if ((rc = sqlite3_step(stmt)) != SQLITE_DONE) {
-               cfs_critical("sqlite3_step failed: %s\n", sqlite3_errmsg(db));
-               sqlite3_reset(stmt);
-               return rc;
-       }
-
-       sqlite3_reset(stmt);
-
-       return SQLITE_OK;
-}
-
-static int bdb_backend_delete_inode(
-       db_backend_t *bdb,
-       guint64 inode)
-{
-       int rc;
-
-       cfs_debug("enter dbd_backend_delete_inode");
-
-       sqlite3_stmt *stmt = bdb->stmt_delete_entry;
-
-       if ((rc = sqlite3_bind_int64(stmt, 1, inode)) !=  SQLITE_OK) {
-               cfs_critical("delete_inode/sqlite3_bind failed: %s\n", sqlite3_errmsg(bdb->db));
-               return rc;
-       }
-
-       if ((rc = sqlite3_step(stmt)) != SQLITE_DONE) {
-               cfs_critical("delete_inode failed: %s\n", sqlite3_errmsg(bdb->db));
-               sqlite3_reset(stmt);
-               return rc;
-       }
-
-       sqlite3_reset(stmt);
-
-       return SQLITE_OK;
-}
-
-int bdb_backend_write(
-       db_backend_t *bdb,
-       guint64 inode,
-       guint64 parent,
-       guint64 version,
-       guint32 writer,
-       guint32 mtime,
-       guint32 size,
-       char type,
-       char *name,
-       gpointer value,
-       guint64 delete_inode)
-{
-       g_return_val_if_fail(bdb != NULL, SQLITE_PERM);
-       g_return_val_if_fail(inode == 0 || (name != NULL && name[0]), SQLITE_PERM);
-       g_return_val_if_fail(type == DT_REG || type == DT_DIR, SQLITE_PERM);
-       int rc;
-
-       gboolean need_txn =  (inode != 0 || delete_inode != 0);
-
-       if (need_txn) {
-               rc = sqlite3_step(bdb->stmt_begin);
-               sqlite3_reset(bdb->stmt_begin);
-               if (rc != SQLITE_DONE) {
-                       cfs_critical("begin transaction failed: %s\n", sqlite3_errmsg(bdb->db));
-                       return rc;
-               }
-       }
-
-       if (delete_inode != 0) {
-               if ((rc = bdb_backend_delete_inode(bdb, delete_inode)) != SQLITE_OK)
-                       goto rollback;
-       }
-
-       if (inode != 0) {
-
-               sqlite3_stmt *stmt = (inode > version) ?
-                       bdb->stmt_insert_entry : bdb->stmt_replace_entry;
-
-               rc = backend_write_inode(bdb->db, stmt, inode, parent, version,
-                                        writer, mtime, size, type, name, value);
-               if (rc != SQLITE_OK)
-                       goto rollback;
-
-               if (sqlite3_changes(bdb->db) != 1) {
-                       cfs_critical("no such inode %016" PRIX64, inode);
-                       goto rollback;
-               }
-       }
-
-       rc = backend_write_inode(bdb->db, bdb->stmt_replace_entry, 0, 0, version,
-                                writer, mtime, 0, DT_REG, VERSIONFILENAME, NULL);
-
-       if (rc != SQLITE_OK)
-               goto rollback;
-
-
-       if (need_txn) {
-               rc = sqlite3_step(bdb->stmt_commit);
-               sqlite3_reset(bdb->stmt_commit);
-               if (rc != SQLITE_DONE) {
-                       cfs_critical("commit transaction failed: %s\n", sqlite3_errmsg(bdb->db));
-                       goto rollback;
-               }
-       }
-
-       return SQLITE_OK;
-
-rollback:
-
-       if (!need_txn)
-               return rc;
-
-       int rbrc = sqlite3_step(bdb->stmt_rollback);
-       sqlite3_reset(bdb->stmt_rollback);
-       if (rbrc != SQLITE_DONE) {
-               cfs_critical("rollback transaction failed: %s\n", sqlite3_errmsg(bdb->db));
-               return rc;
-       }
-
-       return rc;
-}
-
-static gboolean bdb_backend_load_index(
-       db_backend_t *bdb,
-       memdb_tree_entry_t *root,
-       GHashTable *index)
-{
-       g_return_val_if_fail(bdb != NULL, FALSE);
-       g_return_val_if_fail(root != NULL, FALSE);
-       g_return_val_if_fail(index != NULL, FALSE);
-       g_return_val_if_fail(root->version == 0, FALSE);
-       g_return_val_if_fail(g_hash_table_size(index) == 1, FALSE);
-
-       sqlite3_stmt *stmt = bdb->stmt_load_all;
-
-       int rc;
-       while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
-
-               memdb_tree_entry_t *te;
-
-               guint64 inode = sqlite3_column_int64(stmt, 0);
-               const char *name = (const char *)sqlite3_column_text(stmt, 6);
-               int namelen = sqlite3_column_bytes(stmt, 6);
-               if (name == NULL || namelen == 0) {
-                       cfs_critical("inode has no name (inode = %016" PRIX64 ")", inode);
-                       goto fail;
-               }
-               te = g_malloc0(sizeof(memdb_tree_entry_t) + namelen + 1);
-               strcpy(te->name, name);
-
-               te->inode = inode;
-               te->parent = sqlite3_column_int64(stmt, 1);
-               te->version = sqlite3_column_int64(stmt, 2);
-               te->writer = sqlite3_column_int64(stmt, 3) & 0x0ffffffff;
-               te->mtime = sqlite3_column_int64(stmt, 4) & 0x0ffffffff;
-               te->type = sqlite3_column_int64(stmt, 5) & 255;
-
-               gconstpointer value = sqlite3_column_blob(stmt, 7);
-
-               int size = sqlite3_column_bytes(stmt, 7);
-               te->size = size;
-
-               if (te->type == DT_REG) {
-                       if (size > 0)
-                               te->data.value = g_memdup2(value, size);
-               } else if (te->type == DT_DIR) {
-                       if (size) {
-                               cfs_critical("directory inode contains data (inode = %016" PRIX64 ")",
-                                          te->inode);
-                               g_free(te);
-                               goto fail;
-                       }
-                       te->data.entries = NULL;
-               } else {
-                       cfs_critical("inode has unknown type (inode = %016" PRIX64 ", type = %d)",
-                                  te->inode, te->type);
-                       g_free(te);
-                       goto fail;
-               }
-
-               cfs_debug("name %s (inode = %016" PRIX64 ", parent = %016" PRIX64 ")",
-                       te->name, te->inode, te->parent);
-
-               if (te->inode == 0) {
-                       if (!strcmp(te->name, VERSIONFILENAME)) {
-                               root->version = te->version;
-                               root->writer = te->writer;
-                               root->mtime = te->mtime;
-                               memdb_tree_entry_free(te);
-                       } else {
-                               cfs_critical("root inode has unexpected name '%s'", te->name);
-                               memdb_tree_entry_free(te);
-                               goto fail;
-                       }
-               } else {
-                       memdb_tree_entry_t *pte;
-
-                       if (!(pte = g_hash_table_lookup(index, &te->parent))) {
-                               /* allocate placeholder (type == 0)
-                                * this is simply replaced if we find a real inode later
-                                */
-                               pte = g_malloc0(sizeof(memdb_tree_entry_t));
-                               pte->inode = te->parent;
-                               pte->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
-                               g_hash_table_replace(index, &pte->inode, pte);
-
-                       } else if (!(pte->type == DT_DIR || pte->type == 0)) {
-                               cfs_critical("parent is not a directory "
-                                            "(inode = %016" PRIX64 ", parent = %016" PRIX64 ", name = '%s')",
-                                            te->inode, te->parent, te->name);
-                               memdb_tree_entry_free(te);
-                               goto fail;
-                       }
-
-                       if (te->type == DT_DIR) {
-                               memdb_tree_entry_t *tmpte;
-                               /* test if there is a placeholder entry */
-                               if ((tmpte = g_hash_table_lookup(index, &te->inode))) {
-                                       if (tmpte->type != 0) {
-                                               cfs_critical("found strange placeholder for "
-                                                            "(inode = %016" PRIX64 ", parent = %016" PRIX64 ", name = '%s', type = '%d')",
-                                                            te->inode, te->parent, te->name, tmpte->type);
-                                               memdb_tree_entry_free(te);
-                                               goto fail;
-                                       }
-                                       /* copy entries from placeholder */
-                                       te->data.entries = tmpte->data.entries;
-                                       tmpte->data.entries = NULL;
-                               } else {
-                                       te->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
-                               }
-                       }
-
-                       memdb_tree_entry_t *existing;
-                       if ((existing = g_hash_table_lookup(pte->data.entries, te->name))) {
-                               cfs_critical(
-                                   "found entry with duplicate name '%s' - "
-                                   "A:(inode = 0x%016"PRIX64", parent = 0x%016"PRIX64", v./mtime = 0x%"PRIX64"/0x%"PRIi32")"
-                                   " vs. "
-                                   "B:(inode = 0x%016"PRIX64", parent = 0x%016"PRIX64", v./mtime = 0x%"PRIX64"/0x%"PRIi32")",
-                                    te->name,
-                                    existing->inode, existing->parent, existing->version, existing->mtime,
-                                    te->inode, te->parent, te->version, te->mtime
-                               );
-                               goto fail;
-                       }
-
-                       g_hash_table_replace(pte->data.entries, te->name, te);
-                       g_hash_table_replace(index, &te->inode, te);
-               }
-       }
-       if (rc != SQLITE_DONE) {
-               cfs_critical("select returned error: %s", sqlite3_errmsg(bdb->db));
-               goto fail;
-       }
-
-       /* check if all inodes have parents (there must be no placeholders) */
-       GHashTableIter iter;
-       gpointer key, value;
-       g_hash_table_iter_init (&iter, index);
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-               memdb_tree_entry_t *te = (memdb_tree_entry_t *)value;
-               if (te->type == 0) {
-                       cfs_critical("missing directory inode (inode = %016" PRIX64 ")", te->inode);
-                       goto fail;
-               }
-       }
-
-       sqlite3_reset(stmt);
-
-       return TRUE;
-
-fail:
-       sqlite3_reset(stmt);
-
-       cfs_critical("DB load failed");
-
-       return FALSE;
-}
-
-gboolean bdb_backend_commit_update(
-       memdb_t *memdb,
-       memdb_index_t *master,
-       memdb_index_t *slave,
-       GList *inodes)
-{
-       g_return_val_if_fail(memdb != NULL, FALSE);
-       g_return_val_if_fail(memdb->bdb != NULL, FALSE);
-       g_return_val_if_fail(master != NULL, FALSE);
-       g_return_val_if_fail(slave != NULL, FALSE);
-
-       cfs_debug("enter bdb_backend_commit_update");
-
-       memdb_tree_entry_t *root = NULL;
-       GHashTable *index = NULL;
-
-       db_backend_t *bdb = (db_backend_t *)memdb->bdb;
-       gboolean result = FALSE;
-
-       int rc;
-
-       rc = sqlite3_step(bdb->stmt_begin);
-       sqlite3_reset(bdb->stmt_begin);
-       if (rc != SQLITE_DONE) {
-               cfs_critical("begin transaction failed: %s\n", sqlite3_errmsg(bdb->db));
-               return rc;
-       }
-
-       g_mutex_lock (&memdb->mutex);
-
-       /* first, delete anything not found in master index) */
-
-       int i = 0;
-       int j = 0;
-
-       for (i = 0; i < master->size; i++) {
-               guint64 inode =  master->entries[i].inode;
-               guint64 slave_inode;
-               while (j < slave->size && (slave_inode = slave->entries[j].inode) <= inode) {
-
-                       if (slave_inode < inode) {
-                               if (bdb_backend_delete_inode(bdb, slave_inode) != SQLITE_OK)
-                                       goto abort;
-
-                               cfs_debug("deleted inode %016" PRIX64, slave_inode);
-                       }
-                       j++;
-               }
-               if (j >= slave->size)
-                       break;
-       }
-
-       while (j < slave->size) {
-               guint64 slave_inode = slave->entries[j].inode;
-
-               if (bdb_backend_delete_inode(bdb, slave_inode) != SQLITE_OK)
-                       goto abort;
-
-               cfs_debug("deleted inode %016" PRIX64, slave_inode);
-
-               j++;
-       }
-
-       /* now add all updates */
-
-       GList *l = inodes;
-       while (l) {
-               memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
-
-               tree_entry_debug(te);
-
-               if (backend_write_inode(
-                           bdb->db, bdb->stmt_replace_entry, te->inode, te->parent, te->version,
-                           te->writer, te->mtime, te->size, te->type,
-                           te->inode ? te->name : VERSIONFILENAME, te->data.value) !=  SQLITE_OK) {
-                       goto abort;
-               }
-
-               l = g_list_next(l);
-       }
-
-       /* now try to reload */
-       root = memdb_tree_entry_new("");
-       root->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
-       root->type = DT_DIR;
-
-       index = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL,
-                                     (GDestroyNotify)memdb_tree_entry_free);
-
-       g_hash_table_replace(index, &root->inode, root);
-
-       if (!bdb_backend_load_index(bdb, root, index))
-               goto abort;
-
-       if (!memdb->root->version) {
-               cfs_critical("new index has version 0 - internal error");
-               goto abort;
-       }
-
-       memdb_index_t *new_idx = memdb_encode_index(index, root);
-       if (!new_idx) {
-               cfs_critical("cant encode new index - internal error");
-               goto abort;
-       }
-
-       int idx_equal = (new_idx->bytes == master->bytes &&
-                        (memcmp(master, new_idx, new_idx->bytes) == 0));
-
-       g_free (new_idx);
-
-       if (!idx_equal) {
-               cfs_critical("new index does not match master index - internal error");
-               goto abort;
-       }
-
-       rc = sqlite3_step(bdb->stmt_commit);
-       sqlite3_reset(bdb->stmt_commit);
-       if (rc != SQLITE_DONE) {
-               cfs_critical("commit transaction failed: %s\n", sqlite3_errmsg(bdb->db));
-               goto abort;
-       }
-
-       g_hash_table_destroy(memdb->index);
-       memdb->index = index;
-       memdb->root = root;
-       index = NULL;
-       root = NULL;
-
-       record_memdb_reload();
-
-       if (!memdb_recreate_vmlist(memdb)) {
-               cfs_critical("memdb_recreate_vmlist failed");
-               memdb->errors = 1;
-               result = FALSE;
-               goto ret;
-       }
-
-       memdb_update_locks(memdb);
-
-       result = TRUE;
-
-ret:
-       g_mutex_unlock (&memdb->mutex);
-
-       if (index)
-               g_hash_table_destroy(index);
-
-       cfs_debug("leave bdb_backend_commit_update (%d)", result);
-
-       return result;
-
-abort:
-
-       memdb->errors = 1;
-
-       rc = sqlite3_step(bdb->stmt_rollback);
-       sqlite3_reset(bdb->stmt_rollback);
-       if (rc != SQLITE_DONE)
-               cfs_critical("rollback transaction failed: %s\n", sqlite3_errmsg(bdb->db));
-
-       result = FALSE;
-
-       goto ret;
-}
-
-void bdb_backend_close(db_backend_t *bdb)
-{
-       g_return_if_fail(bdb != NULL);
-
-       sqlite3_finalize(bdb->stmt_insert_entry);
-       sqlite3_finalize(bdb->stmt_replace_entry);
-       sqlite3_finalize(bdb->stmt_update_entry);
-       sqlite3_finalize(bdb->stmt_delete_entry);
-       sqlite3_finalize(bdb->stmt_begin);
-       sqlite3_finalize(bdb->stmt_commit);
-       sqlite3_finalize(bdb->stmt_rollback);
-       sqlite3_finalize(bdb->stmt_load_all);
-
-       int rc;
-       if ((rc = sqlite3_close(bdb->db)) != SQLITE_OK) {
-               cfs_critical("sqlite3_close failed: %d\n", rc);
-       }
-
-       sqlite3_shutdown();
-
-       g_free(bdb);
-}
-
-db_backend_t *bdb_backend_open(
-       const char *filename,
-       memdb_tree_entry_t *root,
-       GHashTable *index)
-{
-       g_return_val_if_fail(filename != NULL, NULL);
-       g_return_val_if_fail(root != NULL, NULL);
-       g_return_val_if_fail(index != NULL, NULL);
-
-       db_backend_t *bdb = g_new0(db_backend_t, 1);
-       g_return_val_if_fail(bdb != NULL, NULL);
-
-       int rc;
-
-       sqlite3_initialize();
-
-       if (!(bdb->db = bdb_create(filename)))
-               goto fail;
-
-       // tell the query planner that the prepared statement will be retained for a long time and
-       // probably reused many times
-       const unsigned int flags = SQLITE_PREPARE_PERSISTENT;
-
-       rc = sqlite3_prepare_v3(bdb->db, sql_insert_entry, -1, flags, &bdb->stmt_insert_entry, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("sqlite3_prepare 'sql_insert_entry' failed: %s\n",
-                            sqlite3_errmsg(bdb->db));
-               goto fail;
-       }
-       rc = sqlite3_prepare_v3(bdb->db, sql_update_entry, -1, flags, &bdb->stmt_update_entry, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("sqlite3_prepare 'sql_update_entry' failed: %s\n",
-                            sqlite3_errmsg(bdb->db));
-               goto fail;
-       }
-       rc = sqlite3_prepare_v3(bdb->db, sql_replace_entry, -1, flags, &bdb->stmt_replace_entry, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("sqlite3_prepare 'sql_replace_entry' failed: %s\n",
-                            sqlite3_errmsg(bdb->db));
-               goto fail;
-       }
-       rc = sqlite3_prepare_v3(bdb->db, sql_delete_entry, -1, flags, &bdb->stmt_delete_entry, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("sqlite3_prepare 'sql_delete_entry' failed: %s\n",
-                            sqlite3_errmsg(bdb->db));
-               goto fail;
-       }
-       rc = sqlite3_prepare_v3(bdb->db, sql_begin, -1, flags, &bdb->stmt_begin, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("sqlite3_prepare 'sql_begin' failed: %s\n",
-                            sqlite3_errmsg(bdb->db));
-               goto fail;
-       }
-       rc = sqlite3_prepare_v3(bdb->db, sql_commit, -1, flags, &bdb->stmt_commit, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("sqlite3_prepare 'sql_commit' failed: %s\n",
-                            sqlite3_errmsg(bdb->db));
-               goto fail;
-       }
-       rc = sqlite3_prepare_v3(bdb->db, sql_rollback, -1, flags, &bdb->stmt_rollback, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("sqlite3_prepare 'sql_rollback' failed: %s\n",
-                            sqlite3_errmsg(bdb->db));
-               goto fail;
-       }
-       rc = sqlite3_prepare_v3(bdb->db, sql_load_all, -1, flags, &bdb->stmt_load_all, NULL);
-       if (rc != SQLITE_OK) {
-               cfs_critical("sqlite3_prepare 'sql_load_all' failed: %s\n",
-                            sqlite3_errmsg(bdb->db));
-               goto fail;
-       }
-
-       if (!bdb_backend_load_index(bdb, root, index))
-               goto fail;
-
-       if (!root->version) {
-               root->version++;
-
-               guint32 mtime = time(NULL);
-
-               if (bdb_backend_write(bdb, 0, 0, root->version, 0, mtime,
-                                     0, DT_REG, NULL, NULL, 0) != SQLITE_OK)
-                       goto fail;
-       }
-
-       return bdb;
-
-fail:
-       bdb_backend_close(bdb);
-
-       return NULL;
-}
diff --git a/data/src/dcdb.c b/data/src/dcdb.c
deleted file mode 100644 (file)
index 28e9ae6..0000000
+++ /dev/null
@@ -1,960 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#define G_LOG_DOMAIN "dcdb"
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <inttypes.h>
-#include <string.h>
-#include <unistd.h>
-#include <glib.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <arpa/inet.h>
-#include <sys/epoll.h>
-#include <dirent.h>
-#include <errno.h>
-
-#include "cfs-utils.h"
-#include "loop.h"
-#include "dcdb.h"
-#include "status.h"
-
-typedef struct {
-       memdb_index_t *master;
-       memdb_index_t *idx;
-       GList *updates;
-} dcdb_sync_info_t;
-
-void
-dcdb_send_unlock(
-       dfsm_t *dfsm,
-       const char *path,
-       const guchar csum[32],
-       gboolean request)
-{
-       g_return_if_fail(dfsm != NULL);
-       g_return_if_fail(path != NULL);
-       g_return_if_fail(csum != NULL);
-
-       struct iovec iov[2];
-
-       iov[0].iov_base = (char *)csum;
-       iov[0].iov_len = 32;
-
-       iov[1].iov_base = (char *)path;
-       iov[1].iov_len = strlen(path) + 1;
-
-       if (!cfs_is_quorate())
-               return;
-
-       dcdb_message_t msg_type = request ? 
-               DCDB_MESSAGE_CFS_UNLOCK_REQUEST : DCDB_MESSAGE_CFS_UNLOCK;
-       
-       dfsm_send_message_sync(dfsm, msg_type, iov, 2, NULL);
-}
-
-static gboolean 
-dcdb_parse_unlock_request(
-       const void *msg,
-       size_t msg_len,
-       const char **path,
-       const guchar **csum)
-
-{
-       g_return_val_if_fail(msg != NULL, FALSE);
-       g_return_val_if_fail(path != NULL, FALSE);
-       g_return_val_if_fail(csum != NULL, FALSE);
-
-       if (msg_len < 33) {
-               cfs_critical("received short unlock message (%zu < 33)", msg_len);
-               return FALSE;
-       }
-
-       char *msg_str = (char *) msg;
-
-       *csum = (guchar *) msg_str;
-       msg_str += 32; msg_len -= 32;
-
-       *path = msg_str;
-       if ((*path)[msg_len - 1] != 0) {
-               cfs_critical("received mailformed unlock message - 'path' not terminated");
-               *path = NULL;
-               return FALSE;
-       }
-
-       return TRUE;
-}
-
-int 
-dcdb_send_fuse_message(
-       dfsm_t *dfsm,
-       dcdb_message_t msg_type,
-       const char *path,
-       const char *to,
-       const char *buf,
-       guint32 size,
-       guint32 offset,
-       guint32 flags)
-{
-       struct iovec iov[8];
-
-       iov[0].iov_base = (char *)&size;
-       iov[0].iov_len = sizeof(size);
-
-       iov[1].iov_base = (char *)&offset;
-       iov[1].iov_len = sizeof(offset);
-
-       guint32 pathlen = path ? strlen(path) + 1 : 0;
-       iov[2].iov_base = (char *)&pathlen;
-       iov[2].iov_len = sizeof(pathlen);
-
-       guint32 tolen = to ? strlen(to) + 1 : 0;
-       iov[3].iov_base = (char *)&tolen;
-       iov[3].iov_len = sizeof(tolen);
-
-       iov[4].iov_base = (char *)&flags;
-       iov[4].iov_len = sizeof(flags);
-
-       iov[5].iov_base = (char *)path;
-       iov[5].iov_len = pathlen;
-
-       iov[6].iov_base = (char *)to;
-       iov[6].iov_len = tolen;
-
-       iov[7].iov_base = (char *)buf;
-       iov[7].iov_len = size;
-
-       dfsm_result_t rc;
-       memset(&rc, 0, sizeof(rc));
-       rc.result = -EBUSY;
-
-       if (!cfs_is_quorate())
-               return -EACCES;
-
-       if (dfsm_send_message_sync(dfsm, msg_type, iov, 8, &rc))
-               return rc.result;
-
-       return -EACCES;
-}
-
-static gboolean 
-dcdb_parse_fuse_message(
-       const void *msg,
-       size_t msg_len,
-       const char **path,
-       const char **to,
-       const char **buf,
-       guint32 *size,
-       guint32 *offset,
-       guint32 *flags)
-
-{
-       g_return_val_if_fail(msg != NULL, FALSE);
-       g_return_val_if_fail(path != NULL, FALSE);
-       g_return_val_if_fail(to != NULL, FALSE);
-       g_return_val_if_fail(buf != NULL, FALSE);
-       g_return_val_if_fail(size != NULL, FALSE);
-       g_return_val_if_fail(offset != NULL, FALSE);
-       g_return_val_if_fail(flags != NULL, FALSE);
-
-       if (msg_len < 20) {
-               cfs_critical("received short fuse message (%zu < 20)", msg_len);
-               return FALSE;
-       }
-
-       const uint8_t *msg_ptr = msg;
-
-       *size = *((guint32 *)msg_ptr);
-       msg_ptr += 4; msg_len -= 4;
-
-       *offset = *((guint32 *)msg_ptr);
-       msg_ptr += 4; msg_len -= 4;
-
-       guint32 pathlen = *((guint32 *)msg_ptr);
-       msg_ptr += 4; msg_len -= 4;
-
-       guint32 tolen = *((guint32 *)msg_ptr);
-       msg_ptr += 4; msg_len -= 4;
-
-       *flags = *((guint32 *)msg_ptr);
-       msg_ptr += 4; msg_len -= 4;
-
-       if (msg_len != ((*size) + pathlen + tolen)) {
-               cfs_critical("received mailformed fuse message");
-               return FALSE;
-       }
-
-       *path = (char *)msg_ptr;
-       msg_ptr += pathlen; msg_len -= pathlen;
-
-       if (pathlen) {
-               if ((*path)[pathlen - 1] != 0) {
-                       cfs_critical("received mailformed fuse message - 'path' not terminated");
-                       *path = NULL;
-                       return FALSE;
-               }
-       } else {
-               *path = NULL;
-       }
-
-       *to = (char *)msg_ptr;
-       msg_ptr += tolen; msg_len -= tolen;
-
-       if (tolen) {
-               if ((*to)[tolen - 1] != 0) {
-                       cfs_critical("received mailformed fuse message - 'to' not terminated");
-                       *to = NULL;
-                       return FALSE;
-               }
-       } else {
-               *to = NULL;
-       }
-
-       *buf = (*size) ? (const char*)msg_ptr : NULL;
-
-       return TRUE;
-}
-
-static gboolean 
-dcdb_send_update_inode(
-       dfsm_t *dfsm, 
-       memdb_tree_entry_t *te)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-       g_return_val_if_fail(te != NULL, FALSE);
-
-       int len;
-       struct iovec iov[20];
-
-       uint32_t namelen = strlen(te->name) + 1;
-
-       iov[0].iov_base = (char *)&te->parent;
-       iov[0].iov_len = sizeof(te->parent);
-       iov[1].iov_base = (char *)&te->inode;
-       iov[1].iov_len = sizeof(te->inode);
-       iov[2].iov_base = (char *)&te->version;
-       iov[2].iov_len = sizeof(te->version);
-       iov[3].iov_base = (char *)&te->writer;
-       iov[3].iov_len = sizeof(te->writer);
-       iov[4].iov_base = (char *)&te->mtime;
-       iov[4].iov_len = sizeof(te->mtime);
-       iov[5].iov_base = (char *)&te->size;
-       iov[5].iov_len = sizeof(te->size);
-       iov[6].iov_base = (char *)&namelen;
-       iov[6].iov_len = sizeof(namelen);
-       iov[7].iov_base = (char *)&te->type;
-       iov[7].iov_len = sizeof(te->type);
-       iov[8].iov_base = (char *)te->name;
-       iov[8].iov_len = namelen;
-
-       len = 9;
-       if (te->type == DT_REG && te->size) {
-               iov[9].iov_base = (char *)te->data.value;
-               iov[9].iov_len = te->size;
-               len++;
-       }
-
-       if (dfsm_send_update(dfsm, iov, len) != CS_OK)
-               return FALSE;
-
-       return TRUE;
-}
-
-memdb_tree_entry_t *
-dcdb_parse_update_inode(
-       const void *msg, 
-       size_t msg_len)
-{
-       if (msg_len < 40) {
-               cfs_critical("received short message (msg_len < 40)");
-               return NULL;
-       }
-
-       char *msg_ptr = (char *) msg;
-
-       guint64 parent = *((guint64 *)msg_ptr);
-       msg_ptr += 8; msg_len -= 8;
-       guint64 inode = *((guint64 *)msg_ptr);
-       msg_ptr += 8; msg_len -= 8;
-       guint64 version = *((guint64 *)msg_ptr);
-       msg_ptr += 8; msg_len -= 8;
-
-       guint32 writer = *((guint32 *)msg_ptr);
-       msg_ptr += 4; msg_len -= 4;
-       guint32 mtime = *((guint32 *)msg_ptr);
-       msg_ptr += 4; msg_len -= 4;
-       guint32 size = *((guint32 *)msg_ptr);
-       msg_ptr += 4; msg_len -= 4;
-       guint32 namelen = *((guint32 *)msg_ptr);
-       msg_ptr += 4; msg_len -= 4;
-
-       char type = *((char *)msg_ptr);
-       msg_ptr += 1; msg_len -= 1;
-
-       if (!(type == DT_REG || type == DT_DIR)) {
-               cfs_critical("received mailformed message (unknown inode type %d)", type);
-               return NULL;
-       }
-
-       if (msg_len != (size + namelen)) {
-               cfs_critical("received mailformed message (msg_len != (size + namelen))");
-               return NULL;
-       }
-
-       char *name = msg_ptr;
-       msg_ptr += namelen; msg_len -= namelen;
-
-       const void *data = msg_ptr;
-       
-       if (name[namelen - 1] != 0) {
-               cfs_critical("received mailformed message (name[namelen-1] != 0)");
-               return NULL;
-       }
-
-       memdb_tree_entry_t *te = memdb_tree_entry_new(name);
-       if (!te)
-               return NULL;
-
-       te->parent = parent;
-       te->version = version;
-       te->inode = inode;
-       te->writer = writer;
-       te->mtime = mtime;
-       te->size = size;
-       te->type = type;
-
-       if (te->type == DT_REG && te->size) {
-               te->data.value = g_memdup2(data, te->size);
-               if (!te->data.value) {
-                       memdb_tree_entry_free(te);
-                       return NULL;
-               }
-       }
-
-       return te;
-}
-
-void 
-dcdb_sync_corosync_conf(
-       memdb_t *memdb, 
-       gboolean notify_corosync)
-{
-       g_return_if_fail(memdb != NULL);
-
-       int len;
-       gpointer data = NULL;
-
-       len = memdb_read(memdb, "corosync.conf", &data);
-       if (len <= 0)
-               return;
-
-       guint64 new_version = cluster_config_version(data, len);
-       if (!new_version) {
-               cfs_critical("unable to parse cluster config_version");
-               return;
-       }
-
-       char *old_data = NULL;
-       gsize old_length = 0;
-       guint64 old_version = 0;
-
-       GError *err = NULL;
-       if (!g_file_get_contents(HOST_CLUSTER_CONF_FN, &old_data, &old_length, &err)) {
-               if (!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
-                       cfs_critical("unable to read cluster config file '%s' - %s", 
-                                    HOST_CLUSTER_CONF_FN, err->message);
-               }
-               g_error_free (err);
-       } else {
-               if (old_length)
-                       old_version = cluster_config_version(old_data, old_length);
-       }
-
-       /* test if something changed - return if no changes */
-       if (data && old_data && (old_length == len) && 
-           !memcmp(data, old_data, len))
-               goto ret;
-
-       if (new_version < old_version) {
-               cfs_critical("local corosync.conf is newer");
-               goto ret;
-       }
-
-       if (!atomic_write_file(HOST_CLUSTER_CONF_FN, data, len, 0644, 0))
-               goto ret;
-
-       cfs_message("wrote new corosync config '%s' (version = %" G_GUINT64_FORMAT ")",
-                   HOST_CLUSTER_CONF_FN, new_version);
-       
-       if (notify_corosync && old_version) {
-               /*
-                * sleep for 1s to hopefully allow new config to propagate
-                * FIXME: actually query the status somehow?
-                */
-               sleep(1);
-
-               /* tell corosync that there is a new config file */
-               cfs_debug ("run corosync-cfgtool -R");
-               int status = system("corosync-cfgtool -R >/dev/null 2>&1");
-               if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
-                       cfs_critical("corosync-cfgtool -R failed with exit code %d\n", WEXITSTATUS(status));
-               }
-               cfs_debug ("end corosync-cfgtool -R");
-       }
-
-ret:
-
-       if (data)
-               g_free(data);
-       
-       if (old_data)
-               g_free(old_data);
-}
-
-static gpointer
-dcdb_get_state(        
-       dfsm_t *dfsm, 
-       gpointer data,
-       unsigned int *res_len)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-       g_return_val_if_fail(data != NULL, FALSE);
-
-       memdb_t *memdb = (memdb_t *)data;
-
-       g_return_val_if_fail(memdb->root != NULL, FALSE);
-
-       cfs_debug("enter %s %016" PRIX64 " %08X", __func__, (uint64_t) memdb->root->version, memdb->root->mtime);
-
-       g_mutex_lock (&memdb->mutex);
-       memdb_index_t *idx = memdb_encode_index(memdb->index, memdb->root);
-       g_mutex_unlock (&memdb->mutex);
-
-       if (idx) {
-               *res_len = idx->bytes;
-       }
-
-       return idx;
-}
-
-static int
-dcdb_select_leader(
-       int node_count,
-       memdb_index_t *idx[])
-{
-       g_return_val_if_fail(idx != NULL, -1);
-
-       cfs_debug("enter %s", __func__);
-
-       int leader = -1;
-
-       /* try select most actual data - compare 'version' an 'time of last write'
-        * NOTE: syncinfo members are sorted 
-        */
-       for (int i = 0; i < node_count; i++) {
-               if (leader < 0) {
-                       leader = i;
-               } else {
-                       memdb_index_t *leaderidx = idx[leader];
-                               
-                       if (idx[i]->version == leaderidx->version &&
-                           idx[i]->mtime > leaderidx->mtime) {
-                               leader = i;
-                       } else if (idx[i]->version > leaderidx->version) {
-                               leader = i;
-                       }
-               }
-       }
-
-       cfs_debug ("leave %s (%d)", __func__, leader);
-
-       return leader;
-}
-
-static gboolean 
-dcdb_create_and_send_updates(
-       dfsm_t *dfsm,
-       memdb_t *memdb, 
-       memdb_index_t *master,
-       int node_count,
-       memdb_index_t *idx[])
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-       g_return_val_if_fail(memdb != NULL, FALSE);
-       g_return_val_if_fail(master != NULL, FALSE);
-
-       cfs_debug("enter %s", __func__);
-
-       gboolean res = FALSE;
-
-       GHashTable *updates = g_hash_table_new(g_int64_hash, g_int64_equal);
-       if (!updates)
-               goto ret;
-
-       g_mutex_lock (&memdb->mutex);
-
-       for (int n = 0; n < node_count; n++) {
-               memdb_index_t *slave = idx[n];
-
-               if (slave == master)
-                       continue;
-
-               int j = 0;
-
-               for (int i = 0; i < master->size; i++) {
-                       guint64 inode =  master->entries[i].inode;
-                       while (j < slave->size && slave->entries[j].inode < inode)
-                               j++;
-
-                       if (memcmp(&slave->entries[j], &master->entries[i], 
-                                   sizeof(memdb_index_extry_t)) == 0) {
-                               continue;
-                       }
-
-                       if (g_hash_table_lookup(updates, &inode))
-                               continue;
-                       
-                       cfs_debug("found different inode %d %016" PRIX64, i, (uint64_t) inode);
-                       
-                       memdb_tree_entry_t *te, *cpy;
-
-                       if (!(te = g_hash_table_lookup(memdb->index, &inode))) {
-                               cfs_critical("can get inode data for inode %016" PRIX64, (uint64_t) inode);
-                               goto ret;
-                       }
-                       
-                       cpy = memdb_tree_entry_copy(te, 1);
-                       g_hash_table_replace(updates, &cpy->inode, cpy);
-               }
-       }
-
-       g_mutex_unlock (&memdb->mutex);
-
-       /* send updates */
-
-       GHashTableIter iter;
-       gpointer key, value;
-       int count = 0;
-
-       cfs_message("start sending inode updates");
-
-       g_hash_table_iter_init (&iter, updates);
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-               memdb_tree_entry_t *te = (memdb_tree_entry_t *)value;
-               count++;
-
-               if (!dcdb_send_update_inode(dfsm, te)) {
-                       /* tolerate error here */
-                       cfs_critical("sending update inode failed %016" PRIX64, (uint64_t) te->inode);
-               } else {
-                       cfs_debug("sent update inode %016" PRIX64, (uint64_t) te->inode);
-               }
-                       
-               memdb_tree_entry_free(te);
-       }
-
-       cfs_message("sent all (%d) updates", count);
-
-       if (dfsm_send_update_complete(dfsm) != CS_OK) {
-               cfs_critical("failed to send UPDATE_COMPLETE message");
-               goto ret;
-       }
-
-       res = TRUE;
-
- ret:
-       if (updates)
-               g_hash_table_destroy(updates);
-
-       cfs_debug("leave %s (%d)", __func__, res);
-
-       return res;
-}
-
-static int
-dcdb_process_state_update(
-       dfsm_t *dfsm, 
-       gpointer data,
-       dfsm_sync_info_t *syncinfo)
-{
-       g_return_val_if_fail(dfsm != NULL, -1);
-       g_return_val_if_fail(data != NULL, -1);
-       g_return_val_if_fail(syncinfo != NULL, -1);
-
-       memdb_t *memdb = (memdb_t *)data;
-
-       cfs_debug("enter %s", __func__);
-
-       dcdb_sync_info_t *localsi = g_new0(dcdb_sync_info_t, 1);
-       if (!localsi)
-               return -1;
-
-       syncinfo->data = localsi;
-
-       memdb_index_t *idx[syncinfo->node_count];
-
-       for (int i = 0; i < syncinfo->node_count; i++) {
-               dfsm_node_info_t *ni = &syncinfo->nodes[i];
-
-               if (ni->state_len < sizeof(memdb_index_t)) {
-                       cfs_critical("received short memdb index (len < sizeof(memdb_index_t))");
-                       return -1;
-               }
-
-               idx[i] = (memdb_index_t *)ni->state;
-
-               if (ni->state_len != idx[i]->bytes) {
-                       cfs_critical("received mailformed memdb index (len != idx->bytes)");
-                       return -1;
-               }
-       }
-       
-       /* select leader - set mode */
-       int leader = dcdb_select_leader(syncinfo->node_count, idx);
-       if (leader < 0) {
-               cfs_critical("unable to select leader failed");
-               return -1;
-       }
-
-       cfs_message("leader is %d/%d", syncinfo->nodes[leader].nodeid, syncinfo->nodes[leader].pid);
-
-       memdb_index_t *leaderidx = idx[leader];
-       localsi->master = leaderidx;
-
-       GString *synced_member_ids = g_string_new(NULL);
-       g_string_append_printf(synced_member_ids, "%d/%d", syncinfo->nodes[leader].nodeid, syncinfo->nodes[leader].pid);
-
-       for (int i = 0; i < syncinfo->node_count; i++) {
-               dfsm_node_info_t *ni = &syncinfo->nodes[i];
-               if (i == leader) {
-                       ni->synced = 1;
-               } else {
-                       if (leaderidx->bytes == idx[i]->bytes &&
-                           memcmp(leaderidx, idx[i], leaderidx->bytes) == 0) {
-                               ni->synced = 1;
-                               g_string_append_printf(synced_member_ids, ", %d/%d", ni->nodeid, ni->pid);
-                       }
-               }
-               if (dfsm_nodeid_is_local(dfsm, ni->nodeid, ni->pid)) 
-                       localsi->idx = idx[i];
-       }
-       cfs_message("synced members: %s", synced_member_ids->str);
-       g_string_free(synced_member_ids, 1);
-
-       /* send update */
-       if (dfsm_nodeid_is_local(dfsm, syncinfo->nodes[leader].nodeid, syncinfo->nodes[leader].pid)) {
-               if (!dcdb_create_and_send_updates(dfsm, memdb, leaderidx, syncinfo->node_count, idx))
-                       return -1;
-       }
-
-       return 0;
-}
-
-static int 
-dcdb_process_update(
-       dfsm_t *dfsm, 
-       gpointer data,
-       dfsm_sync_info_t *syncinfo,
-       uint32_t nodeid,
-       uint32_t pid,
-       const void *msg,
-       size_t msg_len)
-{
-       g_return_val_if_fail(dfsm != NULL, -1);
-       g_return_val_if_fail(data != NULL, -1);
-       g_return_val_if_fail(msg != NULL, -1);
-       g_return_val_if_fail(syncinfo != NULL, -1);
-       g_return_val_if_fail(syncinfo->data != NULL, -1);
-       
-       cfs_debug("enter %s", __func__);
-
-       memdb_tree_entry_t *te;
-
-       if (!(te = dcdb_parse_update_inode(msg, msg_len)))
-               return -1;
-
-       cfs_debug("received inode update %016" PRIX64 " from node %d",
-                 (uint64_t) te->inode, nodeid);
-
-       dcdb_sync_info_t *localsi = (dcdb_sync_info_t *)syncinfo->data;
-
-       localsi->updates = g_list_append(localsi->updates, te);
-
-       return 0;
-}
-
-static int
-dcdb_commit(
-       dfsm_t *dfsm, 
-       gpointer data,
-       dfsm_sync_info_t *syncinfo)
-{
-       g_return_val_if_fail(dfsm != NULL, -1);
-       g_return_val_if_fail(data != NULL, -1);
-       g_return_val_if_fail(syncinfo != NULL, -1);
-       g_return_val_if_fail(syncinfo->data != NULL, -1);
-       
-       memdb_t *memdb = (memdb_t *)data;
-
-       cfs_debug("enter %s", __func__);
-
-       dcdb_sync_info_t *localsi = (dcdb_sync_info_t *)syncinfo->data;
-
-       guint count = g_list_length(localsi->updates); 
-
-       cfs_message("update complete - trying to commit (got %u inode updates)", count);
-
-       if (!bdb_backend_commit_update(memdb, localsi->master, localsi->idx, localsi->updates)) 
-               return -1;
-
-       dcdb_sync_corosync_conf(memdb, FALSE);
-
-       return 0;
-}
-
-static int 
-dcdb_cleanup(
-       dfsm_t *dfsm, 
-       gpointer data,
-       dfsm_sync_info_t *syncinfo)
-{
-       g_return_val_if_fail(dfsm != NULL, -1);
-       g_return_val_if_fail(data != NULL, -1);
-       g_return_val_if_fail(syncinfo != NULL, -1);
-       g_return_val_if_fail(syncinfo->data != NULL, -1);
-
-       cfs_debug("enter %s", __func__);
-
-       dcdb_sync_info_t *localsi = (dcdb_sync_info_t *)syncinfo->data;
-
-       GList *iter = localsi->updates;
-       while (iter) {
-               memdb_tree_entry_t *te = (memdb_tree_entry_t *)iter->data;
-               memdb_tree_entry_free(te);
-               iter = g_list_next(iter);
-       }
-       g_list_free(localsi->updates);
-
-       g_free(localsi);
-
-       return 0;
-}
-
-gboolean 
-dcdb_checksum(
-       dfsm_t *dfsm, 
-       gpointer data,
-       unsigned char *csum,
-       size_t csum_len)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-       g_return_val_if_fail(csum != NULL, FALSE);
-
-       memdb_t *memdb = (memdb_t *)data;
-
-       g_return_val_if_fail(memdb != NULL, FALSE);
-
-       cfs_debug("enter %s %016" PRIX64 " %08X", __func__, memdb->root->version, memdb->root->mtime);
-
-       g_mutex_lock (&memdb->mutex);
-       gboolean res = memdb_compute_checksum(memdb->index, memdb->root, csum, csum_len);
-       g_mutex_unlock (&memdb->mutex);
-
-       cfs_debug("leave %s %016" PRIX64 " (%d)", __func__, *( (uint64_t *) csum), res);
-
-       return res;
-}
-
-static int
-dcdb_deliver(
-       dfsm_t *dfsm,
-       gpointer data,
-       int *res_ptr,
-       uint32_t nodeid,
-       uint32_t pid,
-       uint16_t msg_type,
-       uint32_t msg_time,
-       const void *msg,
-       size_t msg_len)
-{
-       g_return_val_if_fail(dfsm != NULL, -1);
-       g_return_val_if_fail(msg != NULL, -1);
-
-       memdb_t *memdb = (memdb_t *)data;
-
-       g_return_val_if_fail(memdb != NULL, -1);
-       g_return_val_if_fail(res_ptr != NULL, -1);
-
-       int res = 1;
-
-       int msg_result = -ENOTSUP;
-
-       if (!DCDB_VALID_MESSAGE_TYPE(msg_type)) 
-               goto unknown;
-
-       cfs_debug("process message %u (length = %zd)", msg_type, msg_len);
-       
-       if (!cfs_is_quorate()) {
-               cfs_critical("received write while not quorate - trigger resync");
-               msg_result = -EACCES;
-               goto leave;
-       }
-
-       const char *path, *to, *buf; 
-       guint32 size, offset, flags;
-       const guchar *csum;
-
-       if (msg_type == DCDB_MESSAGE_CFS_UNLOCK_REQUEST ||
-           msg_type == DCDB_MESSAGE_CFS_UNLOCK) {
-               msg_result = 0; /* ignored anyways */ 
-               
-               if (!dcdb_parse_unlock_request(msg, msg_len, &path, &csum))
-                       goto leave;
-
-               guchar cur_csum[32];
-               memdb_tree_entry_t *te = memdb_getattr(memdb, path);
-
-               if (te &&  te->type == DT_DIR &&
-                   path_is_lockdir(path) && memdb_tree_entry_csum(te, cur_csum) &&
-                   (memcmp(csum, cur_csum, 32) == 0)) {
-
-                       if (msg_type == DCDB_MESSAGE_CFS_UNLOCK) {
-
-                               cfs_debug("got valid unlock message");
-
-                               msg_result = memdb_delete(memdb, path, nodeid, msg_time);
-
-                       } else if (dfsm_lowest_nodeid(dfsm)) {
-
-                               cfs_debug("got valid unlock request message");
-                       
-                               if (memdb_lock_expired(memdb, path, csum)) {
-                                       cfs_debug("sending unlock message");
-                                       dcdb_send_unlock(dfsm, path, csum, FALSE);
-                               }
-                       }
-               }
-               memdb_tree_entry_free(te);
-
-       } else if (msg_type == DCDB_MESSAGE_CFS_WRITE) {
-
-               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
-                                            &size, &offset, &flags))
-                       goto leave;
-
-               msg_result = memdb_write(memdb, path, nodeid, msg_time,
-                                        buf, size, offset, flags);
-
-               if ((msg_result >= 0) && !strcmp(path, "corosync.conf"))
-                       dcdb_sync_corosync_conf(memdb, dfsm_nodeid_is_local(dfsm, nodeid, pid));
-
-       } else if (msg_type == DCDB_MESSAGE_CFS_CREATE) {
-
-               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
-                                            &size, &offset, &flags))
-                       goto leave;
-               
-               msg_result = memdb_create(memdb, path, nodeid, msg_time);
-
-               if ((msg_result >= 0) && !strcmp(path, "corosync.conf"))
-                       dcdb_sync_corosync_conf(memdb, dfsm_nodeid_is_local(dfsm, nodeid, pid));
-               
-       } else if (msg_type == DCDB_MESSAGE_CFS_MKDIR) {
-
-               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
-                                            &size, &offset, &flags))
-                       goto leave;
-               
-               msg_result = memdb_mkdir(memdb, path, nodeid, msg_time);
-               
-       } else if (msg_type == DCDB_MESSAGE_CFS_DELETE) {
-
-               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
-                                            &size, &offset, &flags))
-                       goto leave;
-
-               msg_result = memdb_delete(memdb, path, nodeid, msg_time);
-                                               
-       } else if (msg_type == DCDB_MESSAGE_CFS_RENAME) {
-
-               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
-                                            &size, &offset, &flags))
-                       goto leave;
-
-               msg_result = memdb_rename(memdb, path, to, nodeid, msg_time);
-               
-               if ((msg_result >= 0) && !strcmp(to, "corosync.conf"))
-                       dcdb_sync_corosync_conf(memdb, dfsm_nodeid_is_local(dfsm, nodeid, pid));
-                       
-       } else if (msg_type == DCDB_MESSAGE_CFS_MTIME) {
-
-               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
-                                            &size, &offset, &flags))
-                       goto leave;
-               
-               /* Note: mtime is sent via offset field */
-               msg_result = memdb_mtime(memdb, path, nodeid, offset);
-               
-       } else {
-               goto unknown;
-       }
-
-       *res_ptr = msg_result;
-ret:
-       if (memdb->errors) {
-               dfsm_set_errormode(dfsm);
-               res = -1;
-       }
-
-       cfs_debug("leave %s (%d)", __func__, res);
-
-       return res;
-
-unknown:
-       cfs_critical("received unknown message type (msg_type == %u)", msg_type);
-leave:
-       res = -1;
-       goto ret;
-
-}
-
-static dfsm_callbacks_t dcdb_dfsm_callbacks = {
-       .dfsm_deliver_fn = dcdb_deliver,
-       .dfsm_get_state_fn = dcdb_get_state,
-       .dfsm_process_state_update_fn = dcdb_process_state_update,
-       .dfsm_process_update_fn = dcdb_process_update,
-       .dfsm_commit_fn = dcdb_commit,
-       .dfsm_cleanup_fn = dcdb_cleanup,
-       .dfsm_checksum_fn = dcdb_checksum,
-};
-
-dfsm_t *dcdb_new(memdb_t *memdb)
-{
-       g_return_val_if_fail(memdb != NULL, NULL);
-       return dfsm_new(memdb, DCDB_CPG_GROUP_NAME, G_LOG_DOMAIN, 
-                       DCDB_PROTOCOL_VERSION, &dcdb_dfsm_callbacks);
-}
diff --git a/data/src/dcdb.h b/data/src/dcdb.h
deleted file mode 100644 (file)
index 6de240a..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_DCDB_H_
-#define _PVE_DCDB_H_
-
-#include <glib.h>
-
-#include "dfsm.h"
-#include "memdb.h"
-
-#define DCDB_CPG_GROUP_NAME "pve_dcdb_v1"
-/* please increase protocol version if you want to stop older nodes */
-#define DCDB_PROTOCOL_VERSION 1
-#define DCDB_VERIFY_TIME (60*60)
-
-typedef enum {
-       DCDB_MESSAGE_CFS_WRITE = 1,
-       DCDB_MESSAGE_CFS_MKDIR = 2,
-       DCDB_MESSAGE_CFS_DELETE = 3,
-       DCDB_MESSAGE_CFS_RENAME = 4,
-       DCDB_MESSAGE_CFS_CREATE = 5,
-       DCDB_MESSAGE_CFS_MTIME = 6,
-       DCDB_MESSAGE_CFS_UNLOCK_REQUEST = 7,
-       DCDB_MESSAGE_CFS_UNLOCK = 8,
-} dcdb_message_t;
-
-#define DCDB_VALID_MESSAGE_TYPE(mt) (mt >= DCDB_MESSAGE_CFS_WRITE && mt <= DCDB_MESSAGE_CFS_UNLOCK)
-
-dfsm_t *dcdb_new(memdb_t *memdb);
-
-void dcdb_sync_corosync_conf(
-       memdb_t *memdb, 
-       gboolean notify_corosync);
-
-int dcdb_send_fuse_message(
-       dfsm_t *dfsm, 
-       dcdb_message_t msg_type,
-       const char *path, 
-       const char *to, 
-       const char *buf,
-       guint32 size, 
-       guint32 offset, 
-       guint32 flags);
-
-void
-dcdb_send_unlock(
-       dfsm_t *dfsm,
-       const char *path,
-       const guchar csum[32],
-       gboolean request);
-
-#endif /* _PVE_DCDB_H_ */
diff --git a/data/src/dfsm.c b/data/src/dfsm.c
deleted file mode 100644 (file)
index 84f7df7..0000000
+++ /dev/null
@@ -1,1697 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-
-/* NOTE: we try to keep the CPG handle as long as possible, because
- * calling cpg_initialize/cpg_finalize multiple times from the 
- * same process confuses corosync.
- * Note: CS_ERR_LIBRARY is returned when corosync died
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <sys/types.h>
-#include <inttypes.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include <corosync/corotypes.h>
-#include <corosync/cpg.h>
-#include <glib.h>
-
-#include "cfs-utils.h"
-#include "dfsm.h"
-
-static cpg_callbacks_t cpg_callbacks;
-
-typedef enum {
-       DFSM_MODE_START = 0,
-       DFSM_MODE_START_SYNC = 1,
-       DFSM_MODE_SYNCED = 2,
-       DFSM_MODE_UPDATE = 3,
-
-       /* values >= 128 indicates abnormal/error conditions */
-       DFSM_ERROR_MODE_START = 128,
-       DFSM_MODE_LEAVE = 253,
-       DFSM_MODE_VERSION_ERROR = 254,
-       DFSM_MODE_ERROR = 255,
-} dfsm_mode_t;
-
-typedef enum {
-       DFSM_MESSAGE_NORMAL = 0,
-       DFSM_MESSAGE_SYNC_START = 1,
-       DFSM_MESSAGE_STATE = 2,
-       DFSM_MESSAGE_UPDATE = 3,
-       DFSM_MESSAGE_UPDATE_COMPLETE = 4,
-       DFSM_MESSAGE_VERIFY_REQUEST = 5,
-       DFSM_MESSAGE_VERIFY = 6,
-} dfsm_message_t;
-
-#define DFSM_VALID_STATE_MESSAGE(mt) (mt >= DFSM_MESSAGE_SYNC_START && mt <= DFSM_MESSAGE_VERIFY)
-
-typedef struct {
-       uint16_t type;
-       uint16_t subtype;
-       uint32_t protocol_version;
-       uint32_t time;
-       uint32_t reserved;
-} dfsm_message_header_t;
-
-typedef struct {
-       uint32_t epoch; // per process (not globally unique) 
-       uint32_t time;
-       uint32_t nodeid;
-       uint32_t pid;
-} dfsm_sync_epoch_t;
-
-typedef struct {
-       dfsm_message_header_t base;
-       dfsm_sync_epoch_t epoch;
-} dfsm_message_state_header_t;
-
-typedef struct {
-       dfsm_message_header_t base;
-       uint64_t count;
-} dfsm_message_normal_header_t;
-
-typedef struct {
-       uint32_t nodeid;
-       uint32_t pid;
-       uint64_t msg_count;
-       void *msg;
-       int msg_len; // fixme: unsigned?
-} dfsm_queued_message_t;
-
-struct dfsm {
-       const char *log_domain;
-       cpg_callbacks_t *cpg_callbacks;
-       dfsm_callbacks_t *dfsm_callbacks;
-       cpg_handle_t cpg_handle;
-       GMutex cpg_mutex;
-       struct cpg_name cpg_group_name;
-       uint32_t nodeid;
-       uint32_t pid;
-       int we_are_member;
-
-       guint32 protocol_version;
-       gpointer data;
-
-       gboolean joined;
-
-       /* mode is protected with mode_mutex */
-       GMutex mode_mutex;
-       dfsm_mode_t mode;
-
-       GHashTable *members; /* contains dfsm_node_info_t pointers  */
-       dfsm_sync_info_t *sync_info;
-       uint32_t local_epoch_counter;
-       dfsm_sync_epoch_t sync_epoch;
-       uint32_t lowest_nodeid; 
-       GSequence *msg_queue; 
-       GList *sync_queue;
-
-       /* synchrounous message transmission, protected with sync_mutex */
-       GMutex sync_mutex;
-       GCond sync_cond;
-       GHashTable *results;
-       uint64_t msgcount;
-       uint64_t msgcount_rcvd;
-
-       /* state verification */
-       guchar csum[32];
-       dfsm_sync_epoch_t csum_epoch;
-       uint64_t csum_id;
-       uint64_t csum_counter;
-};
-
-static gboolean dfsm_deliver_queue(dfsm_t *dfsm);
-static gboolean dfsm_deliver_sync_queue(dfsm_t *dfsm);
-
-gboolean 
-dfsm_nodeid_is_local(
-       dfsm_t *dfsm, 
-       uint32_t nodeid, 
-       uint32_t pid)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-
-       return (nodeid == dfsm->nodeid && pid == dfsm->pid); 
-}
-
-
-static void 
-dfsm_send_sync_message_abort(dfsm_t *dfsm)
-{
-       g_return_if_fail(dfsm != NULL);
-
-       g_mutex_lock (&dfsm->sync_mutex);
-       dfsm->msgcount_rcvd = dfsm->msgcount;
-       g_cond_broadcast (&dfsm->sync_cond);
-       g_mutex_unlock (&dfsm->sync_mutex);
-}
-
-static void 
-dfsm_record_local_result(
-       dfsm_t *dfsm,
-       uint64_t msg_count,
-       int msg_result,
-       gboolean processed)
-{
-       g_return_if_fail(dfsm != NULL);
-       g_return_if_fail(dfsm->results != NULL);
-
-       g_mutex_lock (&dfsm->sync_mutex);
-       dfsm_result_t *rp = (dfsm_result_t *)g_hash_table_lookup(dfsm->results, &msg_count);
-       if (rp) {
-               rp->result = msg_result;
-               rp->processed = processed;
-       }
-       dfsm->msgcount_rcvd = msg_count;
-       g_cond_broadcast (&dfsm->sync_cond);
-       g_mutex_unlock (&dfsm->sync_mutex);
-}
-
-static cs_error_t 
-dfsm_send_message_full(
-       dfsm_t *dfsm,
-       struct iovec *iov, 
-       unsigned int len,
-       int retry)
-{
-       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
-       g_return_val_if_fail(!len || iov != NULL, CS_ERR_INVALID_PARAM);
-
-       struct timespec tvreq = { .tv_sec = 0, .tv_nsec = 100000000 };
-       cs_error_t result;
-       int retries = 0;
-loop:
-       g_mutex_lock (&dfsm->cpg_mutex);
-       result = cpg_mcast_joined(dfsm->cpg_handle, CPG_TYPE_AGREED, iov, len);
-       g_mutex_unlock (&dfsm->cpg_mutex);
-       if (retry && result == CS_ERR_TRY_AGAIN) {
-               nanosleep(&tvreq, NULL);
-               ++retries;
-               if ((retries % 10) == 0)
-                       cfs_dom_message(dfsm->log_domain, "cpg_send_message retry %d", retries);
-               if (retries < 100)
-                       goto loop;
-       }
-
-       if (retries)
-               cfs_dom_message(dfsm->log_domain, "cpg_send_message retried %d times", retries);
-
-       if (result != CS_OK &&
-           (!retry || result != CS_ERR_TRY_AGAIN))
-               cfs_dom_critical(dfsm->log_domain, "cpg_send_message failed: %d", result);
-
-       return result;
-}
-
-static cs_error_t 
-dfsm_send_state_message_full(
-       dfsm_t *dfsm,
-       uint16_t type,
-       struct iovec *iov, 
-       unsigned int len) 
-{
-       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
-       g_return_val_if_fail(DFSM_VALID_STATE_MESSAGE(type), CS_ERR_INVALID_PARAM);
-       g_return_val_if_fail(!len || iov != NULL, CS_ERR_INVALID_PARAM);
-
-       dfsm_message_state_header_t header;
-       header.base.type = type;
-       header.base.subtype = 0;
-       header.base.protocol_version = dfsm->protocol_version;
-       header.base.time = time(NULL);
-       header.base.reserved = 0;
-
-       header.epoch = dfsm->sync_epoch;
-
-       struct iovec real_iov[len + 1];
-
-       real_iov[0].iov_base = (char *)&header;
-       real_iov[0].iov_len = sizeof(header);
-
-       for (int i = 0; i < len; i++)
-               real_iov[i + 1] = iov[i];
-
-       return dfsm_send_message_full(dfsm, real_iov, len + 1, 1);
-}
-
-cs_error_t 
-dfsm_send_update(
-       dfsm_t *dfsm,
-       struct iovec *iov, 
-       unsigned int len)
-{
-       return dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_UPDATE, iov, len);
-}
-
-cs_error_t 
-dfsm_send_update_complete(dfsm_t *dfsm)
-{
-       return dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_UPDATE_COMPLETE, NULL, 0);
-}
-
-
-cs_error_t 
-dfsm_send_message(
-       dfsm_t *dfsm,
-       uint16_t msgtype,
-       struct iovec *iov, 
-       int len)
-{
-       return dfsm_send_message_sync(dfsm, msgtype, iov, len, NULL);
-}
-
-cs_error_t 
-dfsm_send_message_sync(
-       dfsm_t *dfsm,
-       uint16_t msgtype,
-       struct iovec *iov, 
-       int len,
-       dfsm_result_t *rp)
-{
-       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
-       g_return_val_if_fail(!len || iov != NULL, CS_ERR_INVALID_PARAM);
-
-       g_mutex_lock (&dfsm->sync_mutex);
-       /* note: hold lock until message is sent - to guarantee ordering */
-       uint64_t msgcount = ++dfsm->msgcount;
-       if (rp) {
-               rp->msgcount = msgcount;
-               rp->processed = 0;
-               g_hash_table_replace(dfsm->results, &rp->msgcount, rp);
-       }
-
-       dfsm_message_normal_header_t header;
-       header.base.type = DFSM_MESSAGE_NORMAL;
-       header.base.subtype = msgtype;
-       header.base.protocol_version = dfsm->protocol_version;
-       header.base.time = time(NULL);
-       header.base.reserved = 0;
-       header.count = msgcount;
-
-       struct iovec real_iov[len + 1];
-
-       real_iov[0].iov_base = (char *)&header;
-       real_iov[0].iov_len = sizeof(header);
-
-       for (int i = 0; i < len; i++)
-               real_iov[i + 1] = iov[i];
-
-       cs_error_t result = dfsm_send_message_full(dfsm, real_iov, len + 1, 1);
-
-       g_mutex_unlock (&dfsm->sync_mutex);
-
-       if (result != CS_OK) {
-               cfs_dom_critical(dfsm->log_domain, "cpg_send_message failed: %d", result);
-
-               if (rp) {
-                       g_mutex_lock (&dfsm->sync_mutex);
-                       g_hash_table_remove(dfsm->results, &rp->msgcount);
-                       g_mutex_unlock (&dfsm->sync_mutex);
-               }
-               return result;
-       }
-
-       if (rp) {
-               g_mutex_lock (&dfsm->sync_mutex);
-
-               while (dfsm->msgcount_rcvd < msgcount)
-                       g_cond_wait (&dfsm->sync_cond, &dfsm->sync_mutex);
-
-      
-               g_hash_table_remove(dfsm->results, &rp->msgcount);
-               
-               g_mutex_unlock (&dfsm->sync_mutex);
-
-               return rp->processed ? CS_OK : CS_ERR_FAILED_OPERATION;
-       }
-
-       return CS_OK;
-}
-
-static gboolean 
-dfsm_send_checksum(dfsm_t *dfsm)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-
-       int len = 2;
-       struct iovec iov[len];
-
-       iov[0].iov_base = (char *)&dfsm->csum_id;
-       iov[0].iov_len = sizeof(dfsm->csum_id);
-       iov[1].iov_base = dfsm->csum;
-       iov[1].iov_len = sizeof(dfsm->csum);
-       
-       gboolean res = (dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_VERIFY, iov, len) == CS_OK);
-
-       return res;
-}
-
-static void 
-dfsm_free_queue_entry(gpointer data)
-{
-       dfsm_queued_message_t *qm = (dfsm_queued_message_t *)data;
-       g_free (qm->msg);
-       g_free (qm);
-}
-
-static void 
-dfsm_free_message_queue(dfsm_t *dfsm) 
-{
-       g_return_if_fail(dfsm != NULL);
-       g_return_if_fail(dfsm->msg_queue != NULL);
-
-       GSequenceIter *iter = g_sequence_get_begin_iter(dfsm->msg_queue);
-       GSequenceIter *end = g_sequence_get_end_iter(dfsm->msg_queue);
-       while (iter != end) {
-               GSequenceIter *cur = iter; 
-               iter = g_sequence_iter_next(iter);
-               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)
-                       g_sequence_get(cur);
-               dfsm_free_queue_entry(qm);
-               g_sequence_remove(cur);
-       }
-}
-
-static void 
-dfsm_free_sync_queue(dfsm_t *dfsm) 
-{
-       g_return_if_fail(dfsm != NULL);
-
-       GList *iter = dfsm->sync_queue;
-       while (iter) {
-               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)iter->data;
-               iter = g_list_next(iter);
-               dfsm_free_queue_entry(qm);
-       }
-
-       g_list_free(dfsm->sync_queue);
-       dfsm->sync_queue = NULL;
-}
-
-static gint 
-message_queue_sort_fn(
-       gconstpointer a,
-       gconstpointer b,
-       gpointer user_data)
-{
-       return ((dfsm_queued_message_t *)a)->msg_count - 
-               ((dfsm_queued_message_t *)b)->msg_count;
-}
-
-static dfsm_node_info_t *
-dfsm_node_info_lookup(
-       dfsm_t *dfsm,
-       uint32_t nodeid, 
-       uint32_t pid)
-{
-       g_return_val_if_fail(dfsm != NULL, NULL);
-       g_return_val_if_fail(dfsm->members != NULL, NULL);
-
-       dfsm_node_info_t info = { .nodeid = nodeid, .pid = pid };
-
-       return (dfsm_node_info_t *)g_hash_table_lookup(dfsm->members, &info);
-}
-
-static dfsm_queued_message_t *
-dfsm_queue_add_message(
-       dfsm_t *dfsm,
-       uint32_t nodeid,
-       uint32_t pid,
-       uint64_t msg_count,
-       const void *msg,
-       size_t msg_len)
-{
-       g_return_val_if_fail(dfsm != NULL, NULL);
-       g_return_val_if_fail(msg != NULL, NULL);
-       g_return_val_if_fail(msg_len != 0, NULL);
-
-       dfsm_node_info_t *ni = dfsm_node_info_lookup(dfsm, nodeid, pid);
-       if (!ni) {
-               cfs_dom_critical(dfsm->log_domain, "dfsm_node_info_lookup failed");
-               return NULL;
-       }
-
-       dfsm_queued_message_t *qm = g_new0(dfsm_queued_message_t, 1);
-       g_return_val_if_fail(qm != NULL, NULL);
-               
-       qm->nodeid = nodeid;
-       qm->pid = pid;
-       qm->msg = g_memdup2 (msg, msg_len);
-       qm->msg_len = msg_len;
-       qm->msg_count =  msg_count;
-
-       if (dfsm->mode == DFSM_MODE_UPDATE && ni->synced) {
-               dfsm->sync_queue = g_list_append(dfsm->sync_queue, qm);
-       } else {
-               /* NOTE: we only need to sort the queue because we resend all
-                * queued messages sometimes.   
-                */
-               g_sequence_insert_sorted(dfsm->msg_queue, qm, message_queue_sort_fn, NULL);
-       }
-
-       return qm;
-}
-
-static guint 
-dfsm_sync_info_hash(gconstpointer key)
-{
-       dfsm_node_info_t *info = (dfsm_node_info_t *)key;
-
-       return g_int_hash(&info->nodeid) + g_int_hash(&info->pid);
-}
-
-static gboolean 
-dfsm_sync_info_equal(
-       gconstpointer v1, 
-       gconstpointer v2)
-{
-       dfsm_node_info_t *info1 = (dfsm_node_info_t *)v1;
-       dfsm_node_info_t *info2 = (dfsm_node_info_t *)v2;
-
-       if (info1->nodeid == info2->nodeid &&
-           info1->pid == info2->pid)
-               return TRUE;
-
-       return FALSE;
-}
-
-static int 
-dfsm_sync_info_compare(
-       gconstpointer v1, 
-       gconstpointer v2)
-{
-       dfsm_node_info_t *info1 = (dfsm_node_info_t *)v1;
-       dfsm_node_info_t *info2 = (dfsm_node_info_t *)v2;
-
-       if (info1->nodeid != info2->nodeid)
-               return info1->nodeid - info2->nodeid;
-
-       return info1->pid - info2->pid;
-}
-
-static void 
-dfsm_set_mode(
-       dfsm_t *dfsm, 
-       dfsm_mode_t new_mode)
-{
-       g_return_if_fail(dfsm != NULL);
-
-       cfs_debug("dfsm_set_mode - set mode to %d", new_mode);
-
-       int changed = 0;
-       g_mutex_lock (&dfsm->mode_mutex);
-       if (dfsm->mode != new_mode) {
-               if (new_mode < DFSM_ERROR_MODE_START ||
-                   (dfsm->mode < DFSM_ERROR_MODE_START || new_mode >= dfsm->mode)) {
-                       dfsm->mode = new_mode;
-                       changed = 1;
-               }
-       }
-       g_mutex_unlock (&dfsm->mode_mutex);
-
-       if (!changed)
-               return;
-
-       if (new_mode == DFSM_MODE_START) {
-               cfs_dom_message(dfsm->log_domain, "start cluster connection");
-       } else if (new_mode == DFSM_MODE_START_SYNC) {
-               cfs_dom_message(dfsm->log_domain, "starting data syncronisation");
-       } else if (new_mode == DFSM_MODE_SYNCED) {
-               cfs_dom_message(dfsm->log_domain, "all data is up to date");
-               if (dfsm->dfsm_callbacks->dfsm_synced_fn)
-                       dfsm->dfsm_callbacks->dfsm_synced_fn(dfsm);
-       } else if (new_mode == DFSM_MODE_UPDATE) {
-               cfs_dom_message(dfsm->log_domain, "waiting for updates from leader");   
-       } else if (new_mode == DFSM_MODE_LEAVE) {
-               cfs_dom_critical(dfsm->log_domain, "leaving CPG group");        
-       } else if (new_mode == DFSM_MODE_ERROR) {
-               cfs_dom_critical(dfsm->log_domain, "serious internal error - stop cluster connection"); 
-       } else if (new_mode == DFSM_MODE_VERSION_ERROR) {
-               cfs_dom_critical(dfsm->log_domain, "detected newer protocol - please update this node"); 
-       }
-}
-
-static dfsm_mode_t 
-dfsm_get_mode(dfsm_t *dfsm)
-{
-       g_return_val_if_fail(dfsm != NULL, DFSM_MODE_ERROR);
-
-       g_mutex_lock (&dfsm->mode_mutex);
-       dfsm_mode_t mode = dfsm->mode;
-       g_mutex_unlock (&dfsm->mode_mutex);
-
-       return mode;
-}
-
-gboolean 
-dfsm_restartable(dfsm_t *dfsm)
-{
-       dfsm_mode_t mode = dfsm_get_mode(dfsm);
-
-       return !(mode == DFSM_MODE_ERROR || 
-                mode == DFSM_MODE_VERSION_ERROR);
-}
-
-void
-dfsm_set_errormode(dfsm_t *dfsm)
-{
-       dfsm_set_mode(dfsm, DFSM_MODE_ERROR);
-}
-
-static void 
-dfsm_release_sync_resources(
-       dfsm_t *dfsm,
-       const struct cpg_address *member_list, 
-       size_t member_list_entries)
-{
-       g_return_if_fail(dfsm != NULL);
-       g_return_if_fail(dfsm->members != NULL);
-       g_return_if_fail(!member_list_entries || member_list != NULL);
-
-       cfs_debug("enter dfsm_release_sync_resources");
-
-       if (dfsm->sync_info) {
-
-               if (dfsm->sync_info->data && dfsm->dfsm_callbacks->dfsm_cleanup_fn) {
-                       dfsm->dfsm_callbacks->dfsm_cleanup_fn(dfsm, dfsm->data, dfsm->sync_info);
-                       dfsm->sync_info->data = NULL;
-               }
-               
-               for (int i = 0; i < dfsm->sync_info->node_count; i++) {
-                       if (dfsm->sync_info->nodes[i].state) {
-                               g_free(dfsm->sync_info->nodes[i].state);
-                               dfsm->sync_info->nodes[i].state = NULL;
-                               dfsm->sync_info->nodes[i].state_len = 0;
-                       }                       
-               }
-       }
-
-       if (member_list) {
-
-               g_hash_table_remove_all(dfsm->members);
-
-               if (dfsm->sync_info)
-                       g_free(dfsm->sync_info);
-
-               int size = sizeof(dfsm_sync_info_t) + 
-                       member_list_entries*sizeof(dfsm_sync_info_t);
-               dfsm_sync_info_t *sync_info = dfsm->sync_info = g_malloc0(size); 
-               sync_info->node_count = member_list_entries;
-
-               for (int i = 0; i < member_list_entries; i++) {
-                       sync_info->nodes[i].nodeid = member_list[i].nodeid;
-                       sync_info->nodes[i].pid = member_list[i].pid;
-               }
-
-               qsort(sync_info->nodes, member_list_entries, sizeof(dfsm_node_info_t),
-                     dfsm_sync_info_compare);
-
-               for (int i = 0; i < member_list_entries; i++) {
-                       dfsm_node_info_t *info = &sync_info->nodes[i];
-                       g_hash_table_insert(dfsm->members, info, info);
-                       if (info->nodeid == dfsm->nodeid && info->pid == dfsm->pid)
-                               sync_info->local = info;
-               }
-       } 
-}
-
-static void 
-dfsm_cpg_deliver_callback(
-       cpg_handle_t handle,
-       const struct cpg_name *group_name,
-       uint32_t nodeid,
-       uint32_t pid,
-       void *msg,
-       size_t msg_len)
-{
-       cs_error_t result;
-
-       dfsm_t *dfsm = NULL;
-       result = cpg_context_get(handle, (gpointer *)&dfsm);
-       if (result != CS_OK || !dfsm || dfsm->cpg_callbacks != &cpg_callbacks) {
-               cfs_critical("cpg_context_get error: %d (%p)", result, (void *) dfsm);
-               return; /* we have no valid dfsm pointer, so we can just ignore this */
-       }
-       dfsm_mode_t mode = dfsm_get_mode(dfsm);
-
-       cfs_dom_debug(dfsm->log_domain, "dfsm mode is %d", mode);
-
-       if (mode >= DFSM_ERROR_MODE_START) {
-               cfs_dom_debug(dfsm->log_domain, "error mode - ignoring message");
-               goto leave;
-       }
-
-       if (!dfsm->sync_info) {
-               cfs_dom_critical(dfsm->log_domain, "no dfsm_sync_info - internal error");
-               goto leave;
-       }
-
-       if (msg_len < sizeof(dfsm_message_header_t)) {
-               cfs_dom_critical(dfsm->log_domain, "received short message (%zd bytes)", msg_len);
-               goto leave;
-       }
-
-       dfsm_message_header_t *base_header = (dfsm_message_header_t *)msg;
-       
-       if (base_header->protocol_version > dfsm->protocol_version) {
-               cfs_dom_critical(dfsm->log_domain, "received message with protocol version %d", 
-                                base_header->protocol_version);
-               dfsm_set_mode(dfsm, DFSM_MODE_VERSION_ERROR);
-               return;
-       } else if (base_header->protocol_version < dfsm->protocol_version) {
-               cfs_dom_message(dfsm->log_domain, "ignore message with wrong protocol version %d", 
-                               base_header->protocol_version);
-               return;
-       }
-
-       if (base_header->type == DFSM_MESSAGE_NORMAL) {
-
-               dfsm_message_normal_header_t *header = (dfsm_message_normal_header_t *)msg;
-
-               if (msg_len < sizeof(dfsm_message_normal_header_t)) {
-                       cfs_dom_critical(dfsm->log_domain, "received short message (type = %d, subtype = %d, %zd bytes)",
-                                        base_header->type, base_header->subtype, msg_len);
-                       goto leave;
-               }
-
-               if (mode != DFSM_MODE_SYNCED) {
-                       cfs_dom_debug(dfsm->log_domain, "queue message %" PRIu64 " (subtype = %d, length = %zd)",
-                                     header->count, base_header->subtype, msg_len); 
-
-                       if (!dfsm_queue_add_message(dfsm, nodeid, pid, header->count, msg, msg_len))
-                               goto leave;
-               } else {
-
-                       int msg_res = -1;
-                       int res = dfsm->dfsm_callbacks->dfsm_deliver_fn(
-                               dfsm, dfsm->data, &msg_res, nodeid, pid, base_header->subtype, 
-                               base_header->time, (uint8_t *)msg + sizeof(dfsm_message_normal_header_t),
-                               msg_len - sizeof(dfsm_message_normal_header_t));
-
-                       if (nodeid == dfsm->nodeid && pid == dfsm->pid)
-                               dfsm_record_local_result(dfsm, header->count, msg_res, res);
-
-                       if (res < 0)
-                               goto leave;
-               }
-
-               return;
-       } 
-
-       /* state related messages
-        * we needs right epoch - else we simply discard the message 
-        */
-
-       dfsm_message_state_header_t *header = (dfsm_message_state_header_t *)msg;
-
-       if (msg_len < sizeof(dfsm_message_state_header_t)) {
-               cfs_dom_critical(dfsm->log_domain, "received short state message (type = %d, subtype = %d, %zd bytes)",
-                                base_header->type, base_header->subtype, msg_len);
-               goto leave;
-       }
-
-       if (base_header->type != DFSM_MESSAGE_SYNC_START && 
-           (memcmp(&header->epoch, &dfsm->sync_epoch, sizeof(dfsm_sync_epoch_t)) != 0)) {
-               cfs_dom_debug(dfsm->log_domain, "ignore message (msg_type == %d) with "
-                             "wrong epoch (epoch %d/%d/%08X)", base_header->type, 
-                             header->epoch.nodeid, header->epoch.pid, header->epoch.epoch);
-               return;
-       }
-
-       msg = (uint8_t *) msg + sizeof(dfsm_message_state_header_t);
-       msg_len -= sizeof(dfsm_message_state_header_t);
-
-       if (mode == DFSM_MODE_SYNCED) {
-               if (base_header->type == DFSM_MESSAGE_UPDATE_COMPLETE) {
-
-                       for (int i = 0; i < dfsm->sync_info->node_count; i++)
-                               dfsm->sync_info->nodes[i].synced = 1;
-
-                       if (!dfsm_deliver_queue(dfsm))
-                               goto leave;
-
-                       return;
-
-               } else if (base_header->type == DFSM_MESSAGE_VERIFY_REQUEST) {
-
-                       if (msg_len != sizeof(dfsm->csum_counter)) {
-                               cfs_dom_critical(dfsm->log_domain, "cpg received verify request with wrong length (%zd bytes) form node %d/%d", msg_len, nodeid, pid);
-                               goto leave;
-                       }
-
-                       uint64_t csum_id = *((uint64_t *)msg);
-                       msg = (uint8_t *) msg + 8; msg_len -= 8;
-
-                       cfs_dom_debug(dfsm->log_domain, "got verify request from node %d %016" PRIX64, nodeid, csum_id);
-
-                       if (dfsm->dfsm_callbacks->dfsm_checksum_fn) {
-                               if (!dfsm->dfsm_callbacks->dfsm_checksum_fn(
-                                           dfsm, dfsm->data, dfsm->csum, sizeof(dfsm->csum))) {
-                                       cfs_dom_critical(dfsm->log_domain, "unable to compute data checksum");
-                                       goto leave;
-                               }
-
-                               dfsm->csum_epoch = header->epoch;
-                               dfsm->csum_id = csum_id;
-
-                               if (nodeid == dfsm->nodeid && pid == dfsm->pid) {
-                                       if (!dfsm_send_checksum(dfsm)) 
-                                               goto leave;
-                               }
-                       }
-
-                       return;
-
-               } else if (base_header->type == DFSM_MESSAGE_VERIFY) {
-               
-                       cfs_dom_debug(dfsm->log_domain, "received verify message");
-                       
-                       if (dfsm->dfsm_callbacks->dfsm_checksum_fn) {
-
-                               if (msg_len != (sizeof(dfsm->csum_id) + sizeof(dfsm->csum))) {
-                                       cfs_dom_critical(dfsm->log_domain, "cpg received verify message with wrong length (%zd bytes)", msg_len);
-                                       goto leave;
-                               }
-
-                               uint64_t csum_id = *((uint64_t *)msg);
-                               msg = (uint8_t *) msg + 8; msg_len -= 8;
-                               
-                               if (dfsm->csum_id == csum_id &&
-                                   (memcmp(&dfsm->csum_epoch, &header->epoch, sizeof(dfsm_sync_epoch_t)) == 0)) {
-                                       if (memcmp(msg, dfsm->csum, sizeof(dfsm->csum)) != 0) {
-                                               cfs_dom_critical(dfsm->log_domain, "wrong checksum %016" PRIX64 " != %016" PRIX64 " - restarting",
-                                                                *(uint64_t *)msg, *(uint64_t *)dfsm->csum);
-                                               goto leave;
-                                       } else {
-                                               cfs_dom_message(dfsm->log_domain, "data verification successful");
-                                       }
-                               } else {
-                                       cfs_dom_message(dfsm->log_domain, "skip verification - no checksum saved");
-                               }
-                       }
-
-                       return;
-
-               } else {
-                       /* ignore (we already got all required updates, or we are leader) */
-                       cfs_dom_debug(dfsm->log_domain, "ignore state sync message %d", 
-                                     base_header->type);
-                       return;
-               }
-               
-       } else if (mode == DFSM_MODE_START_SYNC) {
-
-               if (base_header->type == DFSM_MESSAGE_SYNC_START) {
-
-                       if (nodeid != dfsm->lowest_nodeid) {
-                               cfs_dom_critical(dfsm->log_domain, "ignore sync request from wrong member %d/%d",
-                                                nodeid, pid);
-                       }
-
-                       cfs_dom_message(dfsm->log_domain, "received sync request (epoch %d/%d/%08X)",
-                                       header->epoch.nodeid, header->epoch.pid, header->epoch.epoch);
-
-                       dfsm->sync_epoch = header->epoch;
-
-                       dfsm_release_sync_resources(dfsm, NULL, 0);
-
-                       unsigned int state_len = 0;
-                       gpointer state = NULL;
-                       
-                       state = dfsm->dfsm_callbacks->dfsm_get_state_fn(dfsm, dfsm->data, &state_len);
-
-                       if (!(state && state_len)) {
-                               cfs_dom_critical(dfsm->log_domain, "dfsm_get_state_fn failed");
-                               goto leave;
-                       }
-
-                       struct iovec iov[1];
-                       iov[0].iov_base = state;
-                       iov[0].iov_len = state_len;
-
-                       result = dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_STATE, iov, 1);
-
-                       if (state)
-                               g_free(state);
-
-                       if (result != CS_OK)
-                               goto leave;
-
-                       return;
-
-               } else if (base_header->type == DFSM_MESSAGE_STATE) {
-
-                       dfsm_node_info_t *ni;
-                       
-                       if (!(ni = dfsm_node_info_lookup(dfsm, nodeid, pid))) {
-                               cfs_dom_critical(dfsm->log_domain, "received state for non-member %d/%d", nodeid, pid);
-                               goto leave;
-                       }
-
-                       if (ni->state) {
-                               cfs_dom_critical(dfsm->log_domain, "received duplicate state for member %d/%d", nodeid, pid);
-                               goto leave;
-                       }
-
-                       ni->state = g_memdup2(msg, msg_len);
-                       ni->state_len = msg_len;
-
-                       int received_all = 1;
-                       for (int i = 0; i < dfsm->sync_info->node_count; i++) {
-                               if (!dfsm->sync_info->nodes[i].state) {
-                                       received_all = 0;
-                                       break;
-                               }
-                       }
-
-                       if (received_all) {
-                               cfs_dom_message(dfsm->log_domain, "received all states");
-
-                               int res = dfsm->dfsm_callbacks->dfsm_process_state_update_fn(dfsm, dfsm->data, dfsm->sync_info);
-                               if (res < 0)
-                                       goto leave;
-
-                               if (dfsm->sync_info->local->synced)  {
-                                       dfsm_set_mode(dfsm, DFSM_MODE_SYNCED);
-                                       dfsm_release_sync_resources(dfsm, NULL, 0);
-
-                                       if (!dfsm_deliver_queue(dfsm))
-                                               goto leave;
-                                       
-                               } else {
-                                       dfsm_set_mode(dfsm, DFSM_MODE_UPDATE);
-
-                                       if (!dfsm_deliver_queue(dfsm))
-                                               goto leave;
-                               }
-
-                       }
-
-                       return;
-               }
-
-       } else if (mode == DFSM_MODE_UPDATE) {
-
-               if (base_header->type == DFSM_MESSAGE_UPDATE) {
-                               
-                       int res = dfsm->dfsm_callbacks->dfsm_process_update_fn(
-                               dfsm, dfsm->data, dfsm->sync_info, nodeid, pid, msg, msg_len);
-
-                       if (res < 0)
-                               goto leave;
-
-                       return;
-
-               } else if (base_header->type == DFSM_MESSAGE_UPDATE_COMPLETE) {
-
-
-                       int res = dfsm->dfsm_callbacks->dfsm_commit_fn(dfsm, dfsm->data, dfsm->sync_info);
-
-                       if (res < 0)
-                               goto leave;
-
-                       for (int i = 0; i < dfsm->sync_info->node_count; i++)
-                               dfsm->sync_info->nodes[i].synced = 1;
-
-                       dfsm_set_mode(dfsm, DFSM_MODE_SYNCED);
-
-                       if (!dfsm_deliver_sync_queue(dfsm))
-                               goto leave;
-
-                       if (!dfsm_deliver_queue(dfsm))
-                               goto leave;
-
-                       dfsm_release_sync_resources(dfsm, NULL, 0);
-
-                       return;
-               }
-
-       } else {
-               cfs_dom_critical(dfsm->log_domain, "internal error - unknown mode %d", mode);
-               goto leave;
-       }
-
-       if (base_header->type == DFSM_MESSAGE_VERIFY_REQUEST ||
-           base_header->type == DFSM_MESSAGE_VERIFY) {
-
-               cfs_dom_debug(dfsm->log_domain, "ignore verify message %d while not synced", base_header->type);
-    
-       } else {
-               cfs_dom_critical(dfsm->log_domain, "received unknown state message type (type = %d, %zd bytes)",
-                                base_header->type, msg_len);
-               goto leave;
-       }
-
-leave:
-       dfsm_set_mode(dfsm, DFSM_MODE_LEAVE);
-       dfsm_release_sync_resources(dfsm, NULL, 0);
-       return;
-}
-
-static gboolean 
-dfsm_resend_queue(dfsm_t *dfsm)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-       g_return_val_if_fail(dfsm->msg_queue != NULL, FALSE);
-
-       GSequenceIter *iter = g_sequence_get_begin_iter(dfsm->msg_queue);
-       GSequenceIter *end = g_sequence_get_end_iter(dfsm->msg_queue);
-       gboolean res = TRUE;
-
-       while (iter != end) {
-               GSequenceIter *cur = iter; 
-               iter = g_sequence_iter_next(iter);
-
-               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)
-                       g_sequence_get(cur);
-
-               if (qm->nodeid == dfsm->nodeid && qm->pid == dfsm->pid) {
-                       cs_error_t result;
-                       struct iovec iov[1];
-                       iov[0].iov_base = qm->msg;
-                       iov[0].iov_len = qm->msg_len;
-
-                       if ((result = dfsm_send_message_full(dfsm, iov, 1, 1)) != CS_OK) {
-                               res = FALSE;                    
-                               break;
-                       }
-               }
-       }
-
-       dfsm_free_message_queue(dfsm);
-
-       return res;
-}
-
-static gboolean
-dfsm_deliver_sync_queue(dfsm_t *dfsm) 
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-
-       if (!dfsm->sync_queue)
-               return TRUE;
-
-       gboolean res = TRUE;
-
-       // fixme: cfs_debug
-       cfs_dom_message(dfsm->log_domain, "%s: queue length %d", __func__, 
-                       g_list_length(dfsm->sync_queue));
-
-       GList *iter = dfsm->sync_queue;
-       while (iter) {
-               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)iter->data;
-               iter = g_list_next(iter);
-
-               if (res && dfsm->mode == DFSM_MODE_SYNCED) {            
-                       dfsm_cpg_deliver_callback(dfsm->cpg_handle, &dfsm->cpg_group_name,
-                                                 qm->nodeid, qm->pid, qm->msg, qm->msg_len);
-               } else {
-                       res = FALSE;
-               }
-
-               dfsm_free_queue_entry(qm);
-       }
-       g_list_free(dfsm->sync_queue);
-       dfsm->sync_queue = NULL;
-
-       return res;
-}
-
-static gboolean 
-dfsm_deliver_queue(dfsm_t *dfsm)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-       g_return_val_if_fail(dfsm->msg_queue != NULL, FALSE);
-
-       int qlen = g_sequence_get_length(dfsm->msg_queue);
-       if (!qlen)
-               return TRUE;
-
-       GSequenceIter *iter = g_sequence_get_begin_iter(dfsm->msg_queue);
-       GSequenceIter *end = g_sequence_get_end_iter(dfsm->msg_queue);
-       gboolean res = TRUE;
-
-       // fixme: cfs_debug
-       cfs_dom_message(dfsm->log_domain, "%s: queue length %d", __func__, qlen);
-
-       while (iter != end) {
-               GSequenceIter *cur = iter; 
-               iter = g_sequence_iter_next(iter);
-
-               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)
-                       g_sequence_get(cur);
-       
-               dfsm_node_info_t *ni = dfsm_node_info_lookup(dfsm, qm->nodeid, qm->pid);
-               if (!ni) {
-                       cfs_dom_message(dfsm->log_domain, "remove message from non-member %d/%d", 
-                                       qm->nodeid, qm->pid);
-                       dfsm_free_queue_entry(qm);
-                       g_sequence_remove(cur);
-                       continue;
-               }
-
-               if (dfsm->mode == DFSM_MODE_SYNCED) {
-                       if (ni->synced) {
-                               dfsm_cpg_deliver_callback(dfsm->cpg_handle, &dfsm->cpg_group_name,
-                                                         qm->nodeid, qm->pid, qm->msg, qm->msg_len);
-                               dfsm_free_queue_entry(qm);
-                               g_sequence_remove(cur);
-                       }
-               } else if (dfsm->mode == DFSM_MODE_UPDATE) {
-                       if (ni->synced) {
-                               dfsm->sync_queue = g_list_append(dfsm->sync_queue, qm);
-                               g_sequence_remove(cur);
-                       }
-               } else {
-                       res = FALSE;
-                       break;
-               }
-       }
-
-       return res;
-}
-
-static void 
-dfsm_cpg_confchg_callback(
-       cpg_handle_t handle,
-       const struct cpg_name *group_name,
-       const struct cpg_address *member_list, 
-       size_t member_list_entries,
-       const struct cpg_address *left_list, 
-       size_t left_list_entries,
-       const struct cpg_address *joined_list, 
-       size_t joined_list_entries)
-{
-       cs_error_t result;
-
-       dfsm_t *dfsm = NULL;
-       result = cpg_context_get(handle, (gpointer *)&dfsm);
-       if (result != CS_OK || !dfsm || dfsm->cpg_callbacks != &cpg_callbacks) {
-               cfs_critical("cpg_context_get error: %d (%p)", result, (void *) dfsm);
-               return; /* we have no valid dfsm pointer, so we can just ignore this */
-       }
-
-       dfsm->we_are_member = 0;
-
-       /* create new epoch */
-       dfsm->local_epoch_counter++;
-       dfsm->sync_epoch.epoch = dfsm->local_epoch_counter;
-       dfsm->sync_epoch.nodeid = dfsm->nodeid;
-       dfsm->sync_epoch.pid = dfsm->pid;
-       dfsm->sync_epoch.time = time(NULL);
-
-       /* invalidate saved checksum */
-       dfsm->csum_id = dfsm->csum_counter;
-       memset(&dfsm->csum_epoch, 0, sizeof(dfsm->csum_epoch));
-
-       dfsm_free_sync_queue(dfsm);
-
-       dfsm_mode_t mode = dfsm_get_mode(dfsm);
-
-       cfs_dom_debug(dfsm->log_domain, "dfsm mode is %d", mode);
-
-       if (mode >= DFSM_ERROR_MODE_START) {
-               cfs_dom_debug(dfsm->log_domain, "already left group - ignore message");
-               return;
-       }
-
-       int lowest_nodeid = 0;
-       GString *member_ids = g_string_new(NULL);
-       for (int i = 0; i < member_list_entries; i++) {
-
-               g_string_append_printf(member_ids, i ? ", %d/%d" : "%d/%d",
-                                      member_list[i].nodeid, member_list[i].pid);
-
-               if (lowest_nodeid == 0 || lowest_nodeid > member_list[i].nodeid)
-                       lowest_nodeid =  member_list[i].nodeid;
-
-               if (member_list[i].nodeid == dfsm->nodeid &&
-                   member_list[i].pid == dfsm->pid)
-                       dfsm->we_are_member = 1;
-       }
-
-
-       if ((dfsm->we_are_member || mode != DFSM_MODE_START))
-               cfs_dom_message(dfsm->log_domain, "members: %s",  member_ids->str);
-
-       g_string_free(member_ids, 1);
-
-       dfsm->lowest_nodeid = lowest_nodeid;
-
-       /* NOTE: one node can be in left and joined list at the same time,
-          so it is better to query member list. Also JOIN/LEAVE list are
-          different on different nodes!
-       */
-
-       dfsm_release_sync_resources(dfsm, member_list, member_list_entries);
-
-       if (!dfsm->we_are_member) {
-               if (mode == DFSM_MODE_START) {
-                       cfs_dom_debug(dfsm->log_domain, "ignore leave message");
-                       return;
-               }
-               cfs_dom_message(dfsm->log_domain, "we (%d/%d) left the process group", 
-                               dfsm->nodeid, dfsm->pid);
-               goto leave;
-       }
-
-       if (member_list_entries > 1) {
-
-               int qlen = g_sequence_get_length(dfsm->msg_queue);
-               if (joined_list_entries && qlen) {
-                       /* we need to make sure that all members have the same queue. */
-                       cfs_dom_message(dfsm->log_domain, "queue not emtpy - resening %d messages", qlen);
-                       if (!dfsm_resend_queue(dfsm)) {
-                               cfs_dom_critical(dfsm->log_domain, "dfsm_resend_queue failed");
-                               goto leave;
-                       }
-               }
-
-               dfsm_set_mode(dfsm, DFSM_MODE_START_SYNC);
-               if (lowest_nodeid == dfsm->nodeid) {
-                       if (dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_SYNC_START, NULL, 0) != CS_OK) {
-                               cfs_dom_critical(dfsm->log_domain, "failed to send SYNC_START message");
-                               goto leave;
-                       }
-               }
-       } else {
-               dfsm_set_mode(dfsm, DFSM_MODE_SYNCED);
-               dfsm->sync_info->local->synced = 1;
-               if (!dfsm_deliver_queue(dfsm))
-                       goto leave;
-       }
-
-       if (dfsm->dfsm_callbacks->dfsm_confchg_fn) 
-               dfsm->dfsm_callbacks->dfsm_confchg_fn(dfsm, dfsm->data, member_list, member_list_entries);
-
-       return;
-leave:
-       dfsm_set_mode(dfsm, DFSM_MODE_LEAVE);
-       return;
-}
-
-static cpg_callbacks_t cpg_callbacks = {
-       .cpg_deliver_fn = dfsm_cpg_deliver_callback,
-       .cpg_confchg_fn = dfsm_cpg_confchg_callback,
-};
-
-dfsm_t *
-dfsm_new(
-       gpointer data, 
-       const char *group_name, 
-       const char *log_domain,
-       guint32 protocol_version, 
-       dfsm_callbacks_t *callbacks)
-{
-       g_return_val_if_fail(sizeof(dfsm_message_header_t) == 16, NULL);
-       g_return_val_if_fail(sizeof(dfsm_message_state_header_t) == 32, NULL);
-       g_return_val_if_fail(sizeof(dfsm_message_normal_header_t) == 24, NULL); 
-
-       g_return_val_if_fail(callbacks != NULL, NULL);
-       g_return_val_if_fail(callbacks->dfsm_deliver_fn != NULL, NULL);
-
-       g_return_val_if_fail(callbacks->dfsm_get_state_fn != NULL, NULL);
-       g_return_val_if_fail(callbacks->dfsm_process_state_update_fn != NULL, NULL);
-       g_return_val_if_fail(callbacks->dfsm_process_update_fn != NULL, NULL);
-       g_return_val_if_fail(callbacks->dfsm_commit_fn != NULL, NULL);
-  
-       dfsm_t *dfsm;
-
-       if ((dfsm = g_new0(dfsm_t, 1)) == NULL)
-               return NULL;
-
-       g_mutex_init(&dfsm->sync_mutex);
-       
-       g_cond_init(&dfsm->sync_cond);
-
-       if (!(dfsm->results = g_hash_table_new(g_int64_hash, g_int64_equal)))
-               goto err;
-
-       if (!(dfsm->msg_queue = g_sequence_new(NULL))) 
-               goto err;
-
-       g_mutex_init(&dfsm->cpg_mutex);
-
-       dfsm->log_domain = log_domain;
-       dfsm->data = data;
-       dfsm->mode = DFSM_MODE_START;
-       dfsm->protocol_version = protocol_version;
-       strcpy (dfsm->cpg_group_name.value, group_name);
-       dfsm->cpg_group_name.length = strlen (group_name) + 1;
-
-       dfsm->cpg_callbacks = &cpg_callbacks;
-       dfsm->dfsm_callbacks = callbacks;
-
-       dfsm->members = g_hash_table_new(dfsm_sync_info_hash, dfsm_sync_info_equal);
-       if (!dfsm->members)
-               goto err;
-
-       g_mutex_init(&dfsm->mode_mutex);
-
-       return dfsm;
-
-err:
-       dfsm_destroy(dfsm);
-       return NULL;
-}
-
-gboolean
-dfsm_is_initialized(dfsm_t *dfsm)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-
-       return (dfsm->cpg_handle != 0) ? TRUE : FALSE;
-}
-
-gboolean 
-dfsm_lowest_nodeid(dfsm_t *dfsm)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-
-       if (dfsm->lowest_nodeid && (dfsm->lowest_nodeid == dfsm->nodeid))
-               return TRUE;
-
-       return FALSE;
-}
-
-cs_error_t 
-dfsm_verify_request(dfsm_t *dfsm)
-{
-       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
-
-       /* only do when we have lowest nodeid */
-       if (!dfsm->lowest_nodeid || (dfsm->lowest_nodeid != dfsm->nodeid))
-               return CS_OK;
-
-       dfsm_mode_t mode = dfsm_get_mode(dfsm);
-       if (mode != DFSM_MODE_SYNCED)
-               return CS_OK;           
-
-       int len = 1;
-       struct iovec iov[len];
-
-       if (dfsm->csum_counter != dfsm->csum_id) {
-               g_message("delay verify request %016" PRIX64, dfsm->csum_counter + 1);
-               return CS_OK;
-       };
-
-       dfsm->csum_counter++;
-       iov[0].iov_base = (char *)&dfsm->csum_counter;
-       iov[0].iov_len = sizeof(dfsm->csum_counter);
-       
-       cfs_debug("send verify request %016" PRIX64, dfsm->csum_counter);
-
-       cs_error_t result;
-       result = dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_VERIFY_REQUEST, iov, len);
-
-       if (result != CS_OK)
-               cfs_dom_critical(dfsm->log_domain, "failed to send VERIFY_REQUEST message");
-
-       return result;
-}
-
-
-cs_error_t
-dfsm_dispatch(
-       dfsm_t *dfsm, 
-       cs_dispatch_flags_t dispatch_types) 
-{
-       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
-       g_return_val_if_fail(dfsm->cpg_handle != 0, CS_ERR_INVALID_PARAM);
-
-       cs_error_t result;
-
-       struct timespec tvreq = { .tv_sec = 0, .tv_nsec = 100000000 };
-       int retries = 0;
-loop:
-       result = cpg_dispatch(dfsm->cpg_handle, dispatch_types);
-       if (result == CS_ERR_TRY_AGAIN) {
-               nanosleep(&tvreq, NULL);
-               ++retries;
-               if ((retries % 10) == 0)
-                       cfs_dom_message(dfsm->log_domain, "cpg_dispatch retry %d", retries);
-               goto loop;
-       }
-
-       if (!(result == CS_OK || result == CS_ERR_TRY_AGAIN)) {
-               cfs_dom_critical(dfsm->log_domain, "cpg_dispatch failed: %d", result);
-       }
-
-       return result;
-}
-
-
-cs_error_t
-dfsm_initialize(dfsm_t *dfsm, int *fd) 
-{
-       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
-       g_return_val_if_fail(fd != NULL, CS_ERR_INVALID_PARAM);
-
-       /* remove old messages */
-       dfsm_free_message_queue(dfsm);
-       dfsm_send_sync_message_abort(dfsm);
-
-       dfsm->joined = FALSE;
-       dfsm->we_are_member = 0;
-
-       dfsm_set_mode(dfsm, DFSM_MODE_START);
-
-       cs_error_t result;
-
-       if (dfsm->cpg_handle == 0) {
-               if ((result = cpg_initialize(&dfsm->cpg_handle, dfsm->cpg_callbacks)) != CS_OK) {
-                       cfs_dom_critical(dfsm->log_domain, "cpg_initialize failed: %d", result);
-                       goto err_no_finalize;
-               }
-
-               if ((result = cpg_local_get(dfsm->cpg_handle, &dfsm->nodeid)) != CS_OK) {
-                       cfs_dom_critical(dfsm->log_domain, "cpg_local_get failed: %d", result);
-                       goto err_finalize;
-               }
-
-               dfsm->pid = getpid();
-
-               result = cpg_context_set(dfsm->cpg_handle, dfsm);
-               if (result != CS_OK) {
-                       cfs_dom_critical(dfsm->log_domain, "cpg_context_set failed: %d", result);
-                       goto err_finalize;
-               }
-       }
-
-       result = cpg_fd_get(dfsm->cpg_handle, fd);
-       if (result != CS_OK) {
-               cfs_dom_critical(dfsm->log_domain, "cpg_fd_get failed: %d", result);
-               goto err_finalize;
-       }
-       
-       return CS_OK;
-
- err_finalize:
-       cpg_finalize(dfsm->cpg_handle);
- err_no_finalize:
-       dfsm->cpg_handle = 0;
-       return result;
-}
-
-cs_error_t
-dfsm_join(dfsm_t *dfsm) 
-{
-       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
-       g_return_val_if_fail(dfsm->cpg_handle != 0, CS_ERR_LIBRARY);
-       g_return_val_if_fail(dfsm->joined == 0, CS_ERR_EXIST);
-
-       cs_error_t result;
-
-       struct timespec tvreq = { .tv_sec = 0, .tv_nsec = 100000000 };
-       int retries = 0;
-loop:
-       g_mutex_lock (&dfsm->cpg_mutex);
-       result = cpg_join(dfsm->cpg_handle, &dfsm->cpg_group_name); 
-       g_mutex_unlock (&dfsm->cpg_mutex);
-       if (result == CS_ERR_TRY_AGAIN) {
-               nanosleep(&tvreq, NULL);
-               ++retries;
-               if ((retries % 10) == 0)
-                       cfs_dom_message(dfsm->log_domain, "cpg_join retry %d", retries);
-               goto loop;
-       }
-
-       if (result != CS_OK) {
-               cfs_dom_critical(dfsm->log_domain, "cpg_join failed: %d", result);
-               return result;
-       }
-
-       dfsm->joined = TRUE;
-       return TRUE;
-}
-
-cs_error_t
-dfsm_leave (dfsm_t *dfsm)
-{
-       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
-       g_return_val_if_fail(dfsm->joined, CS_ERR_NOT_EXIST);
-
-       cs_error_t result;
-
-       struct timespec tvreq = { .tv_sec = 0, .tv_nsec = 100000000 };
-       int retries = 0;
-loop:
-       g_mutex_lock (&dfsm->cpg_mutex);
-       result = cpg_leave(dfsm->cpg_handle, &dfsm->cpg_group_name);
-       g_mutex_unlock (&dfsm->cpg_mutex);
-       if (result == CS_ERR_TRY_AGAIN) {
-               nanosleep(&tvreq, NULL);
-               ++retries;
-               if ((retries % 10) == 0)
-                       cfs_dom_message(dfsm->log_domain, "cpg_leave retry %d", retries);
-               goto loop;
-       }
-
-       if (result != CS_OK) {
-               cfs_dom_critical(dfsm->log_domain, "cpg_leave failed: %d", result);
-               return result;
-       }
-
-       dfsm->joined = FALSE;
-
-       return TRUE;            
-}
-
-gboolean 
-dfsm_finalize(dfsm_t *dfsm)
-{
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-
-       dfsm_send_sync_message_abort(dfsm);
-
-       if (dfsm->joined)
-               dfsm_leave(dfsm);
-
-       if (dfsm->cpg_handle) {
-               cpg_finalize(dfsm->cpg_handle);
-               dfsm->cpg_handle = 0;
-               dfsm->joined = FALSE;
-               dfsm->we_are_member = 0;
-       }
-
-       return TRUE;
-}
-
-void 
-dfsm_destroy(dfsm_t *dfsm)
-{
-       g_return_if_fail(dfsm != NULL);
-
-       dfsm_finalize(dfsm);
-
-       if (dfsm->sync_info && dfsm->sync_info->data && dfsm->dfsm_callbacks->dfsm_cleanup_fn)
-               dfsm->dfsm_callbacks->dfsm_cleanup_fn(dfsm, dfsm->data, dfsm->sync_info);
-
-       dfsm_free_sync_queue(dfsm);
-
-       g_mutex_clear (&dfsm->mode_mutex);
-
-       g_mutex_clear (&dfsm->sync_mutex);
-
-       g_cond_clear (&dfsm->sync_cond);
-
-       g_mutex_clear (&dfsm->cpg_mutex);
-       if (dfsm->results)
-               g_hash_table_destroy(dfsm->results);
-
-       if (dfsm->msg_queue) {
-               dfsm_free_message_queue(dfsm);
-               g_sequence_free(dfsm->msg_queue);
-       }
-
-       if (dfsm->sync_info)
-               g_free(dfsm->sync_info);
-
-       if (dfsm->cpg_handle)
-               cpg_finalize(dfsm->cpg_handle);
-
-       if (dfsm->members)
-               g_hash_table_destroy(dfsm->members);
-
-       g_free(dfsm);
-}
-
-typedef struct {
-       dfsm_t *dfsm;
-} service_dfsm_private_t;
-
-static gboolean 
-service_dfsm_finalize(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_val_if_fail(service != NULL, FALSE);
-       g_return_val_if_fail(context != NULL, FALSE);
-
-       service_dfsm_private_t *private = (service_dfsm_private_t *)context;
-       dfsm_t *dfsm = private->dfsm;
-
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-
-       return dfsm_finalize(dfsm);
-}
-
-static int 
-service_dfsm_initialize(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_val_if_fail(service != NULL, -1);
-       g_return_val_if_fail(context != NULL, -1);
-
-       service_dfsm_private_t *private = (service_dfsm_private_t *)context;
-       dfsm_t *dfsm = private->dfsm;
-
-       g_return_val_if_fail(dfsm != NULL, -1);
-
-       /* serious internal error - don't try to recover */
-       if (!dfsm_restartable(dfsm))
-               return -1;
-       
-       int fd = -1;
-
-       cs_error_t result;
-       if ((result = dfsm_initialize(dfsm, &fd)) != CS_OK)
-               return -1;
-       
-       result = dfsm_join(dfsm);
-       if (result != CS_OK) {
-               /* we can't dispatch if not joined, so we need to finalize */
-               dfsm_finalize(dfsm);
-               return -1;
-       }
-
-       return fd;
-}
-
-static gboolean 
-service_dfsm_dispatch(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_val_if_fail(service != NULL, FALSE);
-       g_return_val_if_fail(context != NULL, FALSE);
-
-       service_dfsm_private_t *private = (service_dfsm_private_t *)context;
-       dfsm_t *dfsm = private->dfsm;
-
-       g_return_val_if_fail(dfsm != NULL, FALSE);
-       g_return_val_if_fail(dfsm->cpg_handle != 0, FALSE);
-
-       cs_error_t result;
-
-       result = dfsm_dispatch(dfsm, CS_DISPATCH_ONE);
-       if (result == CS_ERR_LIBRARY || result == CS_ERR_BAD_HANDLE)
-               goto finalize;
-       if (result != CS_OK)
-               goto fail;
-
-       dfsm_mode_t mode = dfsm_get_mode(dfsm);
-       if (mode >= DFSM_ERROR_MODE_START) {
-               if (dfsm->joined) {
-                       result = dfsm_leave(dfsm);
-                       if (result == CS_ERR_LIBRARY || result == CS_ERR_BAD_HANDLE)
-                               goto finalize;
-                       if (result != CS_OK)
-                               goto finalize;
-               } else {
-                       if (!dfsm->we_are_member)
-                               return FALSE;
-               }
-       }
-
-       return TRUE;
-
-finalize:
-       dfsm_finalize(dfsm);
-fail:
-       cfs_service_set_restartable(service, dfsm_restartable(dfsm));
-       return FALSE;
-}
-
-static void 
-service_dfsm_timer(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_if_fail(service != NULL);
-       g_return_if_fail(context != NULL);
-
-       service_dfsm_private_t *private = (service_dfsm_private_t *)context;
-       dfsm_t *dfsm = private->dfsm;
-
-       g_return_if_fail(dfsm != NULL);
-
-       dfsm_verify_request(dfsm);
-}
-
-static cfs_service_callbacks_t cfs_dfsm_callbacks = {
-       .cfs_service_initialize_fn =  service_dfsm_initialize,
-       .cfs_service_finalize_fn = service_dfsm_finalize,
-       .cfs_service_dispatch_fn = service_dfsm_dispatch,
-       .cfs_service_timer_fn = service_dfsm_timer,
-};
-
-cfs_service_t *
-service_dfsm_new(dfsm_t *dfsm)
-{
-       cfs_service_t *service;
-
-       g_return_val_if_fail(dfsm != NULL, NULL);
-
-       service_dfsm_private_t *private = g_new0(service_dfsm_private_t, 1);
-       if (!private)
-               return NULL;
-
-       private->dfsm = dfsm;
-
-       service = cfs_service_new(&cfs_dfsm_callbacks, dfsm->log_domain, private); 
-
-       return service;
-}
-
-void 
-service_dfsm_destroy(cfs_service_t *service) 
-{
-       g_return_if_fail(service != NULL);
-
-       service_dfsm_private_t *private = 
-               (service_dfsm_private_t *)cfs_service_get_context(service);
-
-       g_free(private);
-       g_free(service);
-}
-
-
-
-
diff --git a/data/src/dfsm.h b/data/src/dfsm.h
deleted file mode 100644 (file)
index 858127f..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_DFSM_H_
-#define _PVE_DFSM_H_
-
-#include <corosync/corotypes.h>
-#include <corosync/cpg.h>
-
-#include "loop.h"
-
-typedef struct dfsm dfsm_t;
-
-typedef struct {
-       uint32_t nodeid;
-       uint32_t pid;
-       gpointer state; 
-       unsigned int state_len;
-       gboolean synced;
-} dfsm_node_info_t;
-
-typedef struct {
-       int node_count;
-       gpointer data; /* app can use that */
-       dfsm_node_info_t *local;
-       dfsm_node_info_t nodes[];
-} dfsm_sync_info_t;
-
-/* return values:
- * res <= 0    ... serious error, leave group
- * res > 0    ... OK, continue
- */
-typedef int (*dfsm_deliver_fn_t)(
-       dfsm_t *dfsm,
-       gpointer data,
-       int *res_ptr,
-       uint32_t nodeid,
-       uint32_t pid,
-       uint16_t msgtype,
-       uint32_t msg_time,
-       const void *msg,
-       size_t msg_len);
-
-typedef void (*dfsm_confchg_fn_t)(
-       dfsm_t *dfsm,
-       gpointer data,
-       const struct cpg_address *member_list, 
-       size_t member_list_entries);
-
-/* return values:
- * res == NULL ... serious error, leave group
- * res != NULL ... OK, continue
- */
-typedef gpointer (*dfsm_get_state_fn_t)(
-       dfsm_t *dfsm,
-       gpointer data,
-       unsigned int *res_len);
-
-typedef gboolean (*dfsm_checksum_fn_t)(
-       dfsm_t *dfsm,
-       gpointer data,
-       unsigned char *csum,
-       size_t csum_len);
-
-typedef int (*dfsm_process_state_update_fn_t)(
-       dfsm_t *dfsm, 
-       gpointer data,
-       dfsm_sync_info_t *syncinfo);
-
-typedef void (*dfsm_synced_fn_t)(dfsm_t *dfsm);
-
-/* return values:
- * res < 0    ... serious error, leave group
- * res >=  0  ... OK
- */
-typedef int (*dfsm_process_update_fn_t)(
-       dfsm_t *dfsm, 
-       gpointer data,
-       dfsm_sync_info_t *syncinfo,
-       uint32_t nodeid,
-       uint32_t pid,
-       const void *msg,
-       size_t msg_len);
-
-typedef struct {
-       dfsm_deliver_fn_t dfsm_deliver_fn;
-       dfsm_confchg_fn_t dfsm_confchg_fn;
-       dfsm_get_state_fn_t dfsm_get_state_fn;
-       dfsm_process_state_update_fn_t dfsm_process_state_update_fn;
-       dfsm_process_state_update_fn_t dfsm_commit_fn;
-       dfsm_process_state_update_fn_t dfsm_cleanup_fn;
-       dfsm_process_update_fn_t dfsm_process_update_fn;
-       dfsm_checksum_fn_t dfsm_checksum_fn;
-       dfsm_synced_fn_t dfsm_synced_fn;
-} dfsm_callbacks_t;
-
-typedef struct {
-       uint64_t msgcount;
-       int result; /* we only have integer results for now */
-       int processed; 
-} dfsm_result_t;
-
-dfsm_t *
-dfsm_new(
-       gpointer data,
-       const char *group_name, 
-       const char *log_domain, 
-       guint32 protocol_version, 
-       dfsm_callbacks_t *callbacks);
-
-void 
-dfsm_destroy(dfsm_t *dfsm);
-
-cs_error_t
-dfsm_initialize(dfsm_t *dfsm, int *fd);
-
-gboolean 
-dfsm_finalize(dfsm_t *dfsm);
-
-cs_error_t
-dfsm_join(dfsm_t *dfsm);
-
-cs_error_t
-dfsm_leave(dfsm_t *dfsm);
-
-cs_error_t
-dfsm_dispatch(
-       dfsm_t *dfsm,
-       cs_dispatch_flags_t dispatch_types);
-
-gboolean 
-dfsm_restartable(dfsm_t *dfsm);
-
-gboolean
-dfsm_is_initialized(dfsm_t *dfsm);
-
-void
-dfsm_set_errormode(dfsm_t *dfsm);
-
-cs_error_t 
-dfsm_send_message(
-       dfsm_t *dfsm,
-       uint16_t msgtype,
-       struct iovec *iov, 
-       int len);
-
-/* only call this from another thread - else you get blocked forever */
-cs_error_t 
-dfsm_send_message_sync(
-       dfsm_t *dfsm,
-       uint16_t msgtype,
-       struct iovec *iov, 
-       int len,
-       dfsm_result_t *rp);
-
-cs_error_t 
-dfsm_send_update(
-       dfsm_t *dfsm,
-       struct iovec *iov, 
-       unsigned int len);
-
-cs_error_t 
-dfsm_send_update_complete(dfsm_t *dfsm);
-
-gboolean 
-dfsm_lowest_nodeid(dfsm_t *dfsm);
-
-gboolean 
-dfsm_nodeid_is_local(
-       dfsm_t *dfsm, 
-       uint32_t nodeid,
-       uint32_t pid);
-
-cs_error_t 
-dfsm_verify_request(dfsm_t *dfsm);
-
-cfs_service_t *
-service_dfsm_new(dfsm_t *dfsm);
-
-void 
-service_dfsm_destroy(cfs_service_t *service); 
-
-
-#endif /* _PVE_DFSM_H_ */
diff --git a/data/src/logger.c b/data/src/logger.c
deleted file mode 100644 (file)
index 3792650..0000000
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#define _XOPEN_SOURCE /* glibc2 needs this */
-#include <time.h> /* for strptime */
-
-#include <unistd.h>
-#include <stdio.h>
-#include <inttypes.h>
-#include <stdint.h>
-#include <glib.h>
-#include <string.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-
-#define SYSLOG_MAX_LINE_LENGTH 8192
-
-#include "cfs-utils.h"
-#include "logger.h"
-
-/*
- * 64 bit FNV-1a non-zero initial basis
- */
-#define FNV1A_64_INIT ((uint64_t) 0xcbf29ce484222325ULL)
-/*
- * 64 bit Fowler/Noll/Vo FNV-1a hash code
- * (copied from sheepdog sources)
- */
-static inline uint64_t fnv_64a_buf(const void *buf, size_t len, uint64_t hval)
-{
-       unsigned char *bp = (unsigned char *) buf;
-       unsigned char *be = bp + len;
-       while (bp < be) {
-               hval ^= (uint64_t) *bp++;
-               hval += (hval << 1) + (hval << 4) + (hval << 5) +
-                       (hval << 7) + (hval << 8) + (hval << 40);
-       }
-       return hval;
-}
-
-static uint32_t uid_counter = 0;
-
-struct clog_base {
-       uint32_t size;
-       uint32_t cpos;
-       char data[];
-};
-
-typedef struct {
-       uint64_t node_digest;
-       uint32_t uid;
-       uint32_t time;
-} dedup_entry_t;
-
-static clog_entry_t *
-clog_alloc_entry(
-       clog_base_t *clog,
-       uint32_t size)
-{
-       g_return_val_if_fail(clog != NULL, NULL);
-       g_return_val_if_fail(size > sizeof(clog_entry_t), NULL);
-       g_return_val_if_fail(size <= CLOG_MAX_ENTRY_SIZE, NULL);
-
-       uint32_t realsize = ((size + 7) & 0xfffffff8);
-
-       uint32_t newpos;
-
-       if (!clog->cpos) {
-               newpos = sizeof(clog_base_t);
-       } else {
-               clog_entry_t *cur = (clog_entry_t *)((char *)clog + clog->cpos);
-               newpos = cur->next;
-               if ((newpos + realsize) >= clog->size) {
-                       newpos = sizeof(clog_base_t);
-               }
-       }
-
-       clog_entry_t *entry = (clog_entry_t *)((char *)clog + newpos);
-
-       entry->prev = clog->cpos;
-       clog->cpos = newpos;
-       entry->next = newpos + realsize; 
-
-       return entry;
-}
-
-static void
-clog_dump_entry(clog_entry_t *cur, uint32_t cpos)
-{
-       g_return_if_fail(cur != NULL);
-
-       char *node = cur->data;
-       char *ident = node + cur->node_len;
-       char *tag = ident + cur->ident_len;
-       char *msg = tag + cur->tag_len;
-
-       time_t lt = cur->time;
-       char tbuf[256];
-       strftime(tbuf, sizeof(tbuf), "%F %T", localtime(&lt));
-       printf("cpos %05d %08x %s", cpos, cur->uid, tbuf);
-       printf(" %s{%016" PRIX64 "} %s[%s{%016" PRIX64 "}]: %s\n", node, cur->node_digest, tag, ident, cur->ident_digest, msg);
-       
-}
-
-void
-clog_dump(clog_base_t *clog)
-{
-       g_return_if_fail(clog != NULL);
-       
-       uint32_t cpos = clog->cpos;
-
-       while (cpos && (cpos <= clog->cpos || cpos > (clog->cpos + CLOG_MAX_ENTRY_SIZE))) {
-               clog_entry_t *cur = (clog_entry_t *)((char *)clog + cpos);
-               clog_dump_entry(cur, cpos);
-
-               // wrap-around has to land after initial position
-               if (cpos < cur->prev && cur->prev <= clog->cpos) {
-                       break;
-               }
-               cpos = cur->prev;
-       }
-}
-
-void
-clog_dump_json(
-       clog_base_t *clog, 
-       GString *str, 
-       const char *ident, 
-       guint max_entries)
-{
-       g_return_if_fail(clog != NULL);
-       g_return_if_fail(str != NULL);
-       
-       guint64 ident_digest = 0;
-
-       if (ident && ident[0]) {
-               ident_digest = fnv_64a_buf(ident, strlen(ident) + 1, FNV1A_64_INIT);
-       }
-
-       uint32_t cpos = clog->cpos;
-
-       g_string_append_printf(str, "{\n");
-
-       g_string_append_printf(str, "\"data\": [\n");
-
-       guint count = 0;
-       while (cpos && (cpos <= clog->cpos || cpos > (clog->cpos + CLOG_MAX_ENTRY_SIZE))) {
-               clog_entry_t *cur = (clog_entry_t *)((char *)clog + cpos);
-
-               // wrap-around has to land after initial position
-               if (cpos < cur->prev && cur->prev <= clog->cpos) {
-                       break;
-               }
-               cpos = cur->prev;
-
-               if (count >= max_entries)
-                       break;
-
-               if (ident_digest && ident_digest != cur->ident_digest)
-                       continue;
-
-               char *node = cur->data;
-               char *ident = node + cur->node_len;
-               char *tag = ident + cur->ident_len;
-               char *msg = tag + cur->tag_len;
-
-               if (count)
-                       g_string_append_printf(str, ",\n");
-
-               g_string_append_printf(str, "{\"uid\": %u, \"time\": %u, \"pri\": %d, \"tag\": \"%s\", "
-                                      "\"pid\": %u, \"node\": \"%s\", \"user\": \"%s\", "
-                                      "\"msg\": \"%s\"}", cur->uid, cur->time, cur->priority, tag, 
-                                      cur->pid, node, ident, msg);
-               
-               count++;
-
-       }
-
-       if (count)
-               g_string_append_printf(str, "\n");
-
-       g_string_append_printf(str, "]\n");
-       g_string_append_printf(str, "}\n");
-
-}
-
-uint32_t
-clog_entry_size(const clog_entry_t *entry)
-{
-       g_return_val_if_fail(entry != NULL, 0);
-
-       return sizeof(clog_entry_t) + entry->node_len + 
-               entry->ident_len + entry->tag_len + entry->msg_len;
-}
-
-void
-clog_copy(
-       clog_base_t *clog,
-       const clog_entry_t *entry)
-{
-       g_return_if_fail(clog != NULL);
-       g_return_if_fail(entry != NULL);
-
-       uint32_t size = clog_entry_size(entry);
-       
-       clog_entry_t *new;
-       if ((new = clog_alloc_entry(clog, size)))
-               memcpy((char *)new + 8, (char *)entry + 8, size - 8);
-}
-
-uint32_t
-clog_pack(
-       clog_entry_t *entry,
-       const char *node, 
-       const char *ident, 
-       const char *tag,
-       uint32_t pid,
-       time_t logtime,
-       uint8_t priority,
-       const char *msg)
-{
-       g_return_val_if_fail(entry != NULL, 0);
-       g_return_val_if_fail(ident != NULL, 0);
-       g_return_val_if_fail(tag != NULL, 0);
-       g_return_val_if_fail(msg != NULL, 0);
-       g_return_val_if_fail(priority >= 0, 0);
-       g_return_val_if_fail(priority < 8, 0);
-
-       uint8_t node_len = CFS_MIN(strlen(node) + 1, 255);
-       uint8_t ident_len = CFS_MIN(strlen(ident) + 1, 255);
-       uint8_t tag_len = CFS_MIN(strlen(tag) + 1, 255);
-
-       char *msg_start = entry->data + node_len + ident_len + tag_len;
-       *msg_start = 0;
-
-       int buf_len = CLOG_MAX_ENTRY_SIZE - (msg_start - (char *)entry);
-       utf8_to_ascii(msg_start, buf_len, msg, TRUE);
-
-       uint32_t msg_len = strlen(msg_start) + 1;
-
-       uint32_t size = sizeof(clog_entry_t) + node_len + ident_len + 
-               tag_len + msg_len;
-
-       if (size > CLOG_MAX_ENTRY_SIZE) {
-               int diff = size - CLOG_MAX_ENTRY_SIZE;
-               msg_len -= diff;
-               size = CLOG_MAX_ENTRY_SIZE;
-       }
-
-       entry->prev = 0;
-       entry->next = 0;
-       entry->uid = ++uid_counter;
-       entry->time = logtime;
-       entry->node_digest = fnv_64a_buf(node, node_len, FNV1A_64_INIT);
-       entry->ident_digest = fnv_64a_buf(ident, ident_len, FNV1A_64_INIT);
-       entry->pid = pid;
-       entry->priority = priority;
-       entry->node_len = node_len;
-       entry->ident_len = ident_len;
-       entry->tag_len = tag_len;
-       entry->msg_len = msg_len;
-
-       char *p = entry->data;
-       g_strlcpy(p, node, node_len);
-       p = p + node_len;
-       g_strlcpy(p, ident, ident_len);
-       p = p + ident_len;
-       g_strlcpy(p, tag, tag_len);
-
-       return size;
-}
-
-clog_base_t *
-clog_new(uint32_t size)
-{
-       g_return_val_if_fail(sizeof(clog_base_t) == 8, NULL);
-
-       if (!size)
-               size = CLOG_DEFAULT_SIZE;
-
-       g_return_val_if_fail(size >= (CLOG_MAX_ENTRY_SIZE*10), NULL);
-
-
-       clog_base_t *clog = (clog_base_t *)g_malloc0(size);
-       if (clog) {
-               clog->size = size;
-       }
-
-       return clog;
-}
-
-static gint
-clog_entry_sort_fn(
-       gconstpointer v1, 
-       gconstpointer v2,
-       gpointer user_data)
-{
-       clog_entry_t *entry1 = (clog_entry_t *)v1;
-       clog_entry_t *entry2 = (clog_entry_t *)v2;
-
-       if (entry1->time != entry2->time)
-               return entry1->time - entry2->time;
-
-       if (entry1->node_digest != entry2->node_digest)
-               return entry1->node_digest - entry2->node_digest;
-
-       return entry1->uid - entry2->uid;
-}
-
-static gboolean
-clog_tree_foreach_fn(
-       gpointer key,
-       gpointer value,
-       gpointer data)
-{
-       clog_entry_t *entry = (clog_entry_t *)value;
-       clog_base_t *clog = (clog_base_t *)data;
-
-       clog_copy(clog, entry);
-
-       return FALSE;
-}
-
-clog_base_t *
-clog_sort(clog_base_t *clog)
-{
-       g_return_val_if_fail(clog != NULL, NULL);
-       g_return_val_if_fail(clog->cpos != 0, NULL);
-       
-       clog_base_t *res = clog_new(clog->size);
-       if (!res)
-               return NULL;
-
-       GTree *tree = g_tree_new_with_data(clog_entry_sort_fn, NULL);
-       if (!tree) {
-               g_free(res);
-               return NULL;
-       }
-
-       uint32_t cpos = clog->cpos;
-
-       while (cpos && (cpos <= clog->cpos || cpos > (clog->cpos + CLOG_MAX_ENTRY_SIZE))) {
-               clog_entry_t *cur = (clog_entry_t *)((char *)clog + cpos);
-
-               g_tree_insert(tree, cur, cur);
-
-               // wrap-around has to land after initial position
-               if (cpos < cur->prev && cur->prev <= clog->cpos) {
-                       break;
-               }
-
-               cpos = cur->prev;
-       }
-
-       g_tree_foreach(tree, clog_tree_foreach_fn, res);
-       g_tree_destroy(tree);
-
-       return res;
-}
-
-uint32_t
-clog_size(clog_base_t *clog)
-{
-       g_return_val_if_fail(clog != NULL, 0);
-       return clog->size;
-}
-
-static gboolean
-dedup_lookup(
-       GHashTable *dedup,
-       const clog_entry_t *entry)
-{
-       g_return_val_if_fail(dedup != NULL, FALSE);
-       g_return_val_if_fail(entry != NULL, FALSE);
-
-       dedup_entry_t *dd = g_hash_table_lookup(dedup, &entry->node_digest);
-       if (!dd) {
-               if (!(dd = g_new0(dedup_entry_t, 1)))
-                       return FALSE;
-
-               dd->node_digest = entry->node_digest;
-               dd->time = entry->time;
-               dd->uid = entry->uid;
-
-               g_hash_table_insert(dedup, dd, dd); 
-
-               return TRUE;
-       }
-
-       if (entry->time > dd->time ||
-           (entry->time == dd->time && entry->uid > dd->uid)) {
-               dd->time = entry->time; 
-               dd->uid = entry->uid; 
-               return TRUE;
-       }
-
-       return FALSE;
-}
-
-struct clusterlog {
-       GHashTable *dedup;
-       GMutex mutex;
-       clog_base_t *base;
-};
-
-void 
-clusterlog_dump(
-       clusterlog_t *cl,
-       GString *str, 
-       const char *user, 
-       guint max_entries)
-{
-       g_return_if_fail(cl != NULL);
-       g_return_if_fail(str != NULL);
-
-       g_mutex_lock(&cl->mutex);
-       clog_dump_json(cl->base, str, user, max_entries);
-       g_mutex_unlock(&cl->mutex);
-}
-
-clog_base_t *
-clusterlog_merge(
-       clusterlog_t *cl,
-       clog_base_t **clog, 
-       int count,
-       int local_index)
-{
-       g_return_val_if_fail(cl != NULL, NULL);
-       g_return_val_if_fail(clog != NULL, NULL);
-       g_return_val_if_fail(count >= 2, NULL);
-       g_return_val_if_fail(local_index >= 0, NULL);
-       g_return_val_if_fail(local_index < count, NULL);
-
-       uint32_t cpos[count];
-       uint32_t maxsize = 0;
-
-       GHashTable *dedup;
-       if (!(dedup = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, g_free)))
-               return NULL;
-
-       GTree *tree = g_tree_new_with_data(clog_entry_sort_fn, NULL);
-       if (!tree) {
-               g_hash_table_destroy(dedup);
-               return NULL;
-       }
-
-       clog_base_t *res = clog_new(maxsize);
-       if (!res) {
-               g_hash_table_destroy(dedup);
-               g_tree_destroy(tree);
-               return NULL;
-       }
-
-       g_mutex_lock(&cl->mutex);
-
-       for (int i = 0; i < count; i++) {
-               if (i == local_index)
-                       clog[i] = cl->base;
-
-               if (!clog[i]) {
-                       cfs_critical("log pointer is NULL!");
-                       cpos[i] = 0;
-                       continue;
-               }
-               cpos[i] = clog[i]->cpos;
-               if (clog[i]->size > maxsize)
-                       maxsize = clog[i]->size;
-       }
-
-       size_t logsize = 0;
-       maxsize = res->size - sizeof(clog_base_t) - CLOG_MAX_ENTRY_SIZE;
-
-       int found = 0;
-       while (found >= 0) {
-
-               found = -1;
-               uint32_t last = 0;
-
-               /* select entry wit latest time */
-               for (int i = 0; i < count; i++) {
-                       if (!cpos[i])
-                               continue;
-                       clog_entry_t *cur = (clog_entry_t *)((char *)clog[i] + cpos[i]);
-                       if (cur->time > last) {
-                               last = cur->time;
-                               found = i;
-                       }
-               }
-
-               if (found < 0)
-                       break;
-
-               clog_entry_t *cur = (clog_entry_t *)((char *)clog[found] + cpos[found]);
-
-               if (!g_tree_lookup(tree, cur)) {
-                       g_tree_insert(tree, cur, cur);
-                       dedup_lookup(dedup, cur); /* just to record versions */
-                       logsize += cur->next - cpos[found];
-                       if (logsize >= maxsize)
-                               break;
-               }
-
-               // no previous entry or wrap-around into already overwritten entry
-               if (!cur->prev || (cpos[found] < cur->prev && cur->prev <= clog[found]->cpos)) {
-                       cpos[found] = 0;
-               } else {
-                       cpos[found] = cur->prev;
-                       // wrap-around into current entry
-                       if (!(cpos[found] <= clog[found]->cpos || 
-                             cpos[found] > (clog[found]->cpos + CLOG_MAX_ENTRY_SIZE))) {
-                               cpos[found] = 0;
-                       }
-               }
-       }
-
-       g_tree_foreach(tree, clog_tree_foreach_fn, res);
-       g_tree_destroy(tree);
-
-       g_hash_table_destroy(cl->dedup);
-       cl->dedup = dedup;
-
-       g_free(cl->base);
-       cl->base = res;
-
-       g_mutex_unlock(&cl->mutex);
-
-       return res;
-}
-
-void
-clusterlog_destroy(clusterlog_t *cl)
-{
-       g_return_if_fail(cl != NULL);
-
-       g_mutex_clear(&cl->mutex);
-
-       if (cl->base)
-               g_free(cl->base);
-
-       if (cl->dedup)
-               g_hash_table_destroy(cl->dedup);
-
-       g_free(cl);
-}
-
-clusterlog_t *
-clusterlog_new(void)
-{
-       clusterlog_t *cl = g_new0(clusterlog_t, 1);
-       if (!cl)
-               return NULL;
-
-       g_mutex_init(&cl->mutex);
-
-       if (!(cl->base = clog_new(0)))
-               goto fail;
-
-       if (!(cl->dedup = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, g_free)))
-               goto fail;
-
-       return cl;
-
-fail:
-       clusterlog_destroy(cl);
-       return NULL;
-}
-
-gpointer
-clusterlog_get_state(  
-       clusterlog_t *cl,
-       unsigned int *res_len)
-{
-       g_return_val_if_fail(cl != NULL, NULL);
-       g_return_val_if_fail(res_len != NULL, NULL);
-       
-       g_mutex_lock(&cl->mutex);
-
-       clog_base_t *new;
-       if ((new = clog_sort(cl->base))) {
-               g_free(cl->base);
-               cl->base = new;
-       }
-
-       *res_len = clog_size(cl->base);
-       gpointer msg = g_memdup2(cl->base, *res_len);
-
-       g_mutex_unlock(&cl->mutex);
-
-       return msg;
-}
-
-void
-clusterlog_insert(
-       clusterlog_t *cl,
-       const clog_entry_t *entry)
-{
-       g_return_if_fail(cl != NULL);
-       g_return_if_fail(entry != NULL);
-
-       g_mutex_lock(&cl->mutex);
-
-       if (dedup_lookup(cl->dedup, entry)) {
-               clog_copy(cl->base, entry);
-       } else {
-               cfs_message("ignore insert of duplicate cluster log");
-       }
-
-       g_mutex_unlock(&cl->mutex);
-}
-
-void
-clusterlog_add(
-       clusterlog_t *cl,
-       const char *ident, 
-       const char *tag, 
-       uint32_t pid,
-       uint8_t priority, 
-       const gchar *format,
-       ...)
-{
-       g_return_if_fail(cl != NULL);
-       g_return_if_fail(format != NULL);
-
-       va_list args;
-       va_start (args, format);
-       char *msg = g_strdup_vprintf (format, args);
-       va_end (args);
-
-       time_t ctime = time(NULL);
-       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
-       uint32_t size = clog_pack(entry, cfs.nodename, ident, tag, pid, ctime, priority, msg);
-       g_free(msg);
-
-       if (!size)
-               return;
-
-       clusterlog_insert(cl, entry);
-}
-
diff --git a/data/src/logger.h b/data/src/logger.h
deleted file mode 100644 (file)
index 7c029ed..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_LOGGER_H_
-#define _PVE_LOGGER_H_
-
-#define CLOG_MAX_ENTRY_SIZE 4096
-#define CLOG_DEFAULT_SIZE (8192*16)
-
-typedef struct clog_base clog_base_t;
-
-typedef struct clusterlog clusterlog_t;
-
-typedef struct {
-       uint32_t prev;
-       uint32_t next;
-       uint32_t uid; /* unique id */
-       uint32_t time;
-       uint64_t node_digest;
-       uint64_t ident_digest;
-       uint32_t pid;
-       uint8_t priority; 
-       uint8_t node_len;
-       uint8_t ident_len;
-       uint8_t tag_len;
-       uint32_t msg_len;
-       char data[];
-} clog_entry_t;
-
-clusterlog_t *
-clusterlog_new(void);
-
-void
-clusterlog_destroy(clusterlog_t *cl);
-
-gpointer
-clusterlog_get_state(  
-       clusterlog_t *cl,
-       unsigned int *res_len);
-
-void
-clusterlog_add(
-       clusterlog_t *cl,
-       const char *ident, 
-       const char *tag, 
-       uint32_t pid,
-       uint8_t priority, 
-       const gchar *format,
-       ...) G_GNUC_PRINTF (6, 7);
-
-void
-clusterlog_insert(
-       clusterlog_t *cl,
-       const clog_entry_t *entry);
-
-void 
-clusterlog_dump(
-       clusterlog_t *cl,
-       GString *str, 
-       const char *user, 
-       guint max_entries);
-
-clog_base_t *
-clusterlog_merge(
-       clusterlog_t *cl,
-       clog_base_t **clog, 
-       int count,
-       int local_index);
-
-clog_base_t *
-clog_new(uint32_t size);
-
-uint32_t
-clog_size(clog_base_t *clog);
-
-void
-clog_dump(clog_base_t *clog);
-
-void
-clog_dump_json(
-       clog_base_t *clog, 
-       GString *str, 
-       const char *ident, 
-       guint max_entries);
-
-clog_base_t *
-clog_sort(clog_base_t *clog);
-
-uint32_t
-clog_pack(
-       clog_entry_t *buffer,
-       const char *node, 
-       const char *ident, 
-       const char *tag, 
-       uint32_t pid,
-       time_t logtime,
-       uint8_t priority, 
-       const char *msg);
-
-uint32_t
-clog_entry_size(const clog_entry_t *entry);
-
-void
-clog_copy(
-       clog_base_t *clog,
-       const clog_entry_t *entry);
-
-#endif /* _PVE_LOGGER_H_ */
diff --git a/data/src/logtest.c b/data/src/logtest.c
deleted file mode 100644 (file)
index 15c49ef..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#define _XOPEN_SOURCE /* glibc2 needs this */
-#include <time.h> /* for strptime */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <glib.h>
-#include <sys/types.h>
-#include <sys/syslog.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <unistd.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "cfs-utils.h"
-#include "logger.h"
-
-cfs_t cfs = {
-        .debug = 0,
-       .nodename = "testnode",
-};
-
-
-int
-main(void)
-{
-
-       uint32_t pid = getpid();
-
-#if 1
-       clusterlog_t *cl3 = clusterlog_new();
-
-       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
-       clog_pack(entry, cfs.nodename, "root", "cluster", pid, time(NULL), LOG_INFO, "starting cluster log");
-       clusterlog_insert(cl3, entry);
-
-       for (int i = 0; i < 5000; i++) {
-               clusterlog_add(cl3, "user1", "TESTDOMAIN1", pid, LOG_INFO, 
-                              "test user1 Ã¤ message asdasd d dsgfdfgdgdg dgg dgdg %d", i);
-       }
-
-#if 0
-       for (int i = 0; i < 5000; i++) {
-               clog_base_t *clog[2] = { cl3->base, cl3->base };
-
-               clusterlog_merge(cl3, clog, 2);
-               //clusterlog_sort(cl3);
-       }
-#endif
-
-       //clog_dump(cl3->base);
-
-       clusterlog_destroy(cl3);
-
-       exit(0);
-
-#endif
-
-
-       clusterlog_t *cl1 = clusterlog_new();
-
-       for (int i = 0; i < 5; i++) {
-               clusterlog_add(cl1, "user1", "TESTDOMAIN1", pid, LOG_INFO, 
-                              "test user1 message asdasd %d", i);              
-       }
-       
-
-#if 0
-       for (int i = 0; i < 5; i++) {
-               clusterlog_add(cl1, "user2", "TESTDOMAIN1", pid, LOG_INFO, 
-                              "test user2 message asdasd %d", i);              
-       }
-#endif
-
-       clusterlog_t *cl2 = clusterlog_new();
-
-#if 1
-       for (int i = 0; i < 5; i++) {
-               clusterlog_add(cl2, "user3", "TESTDOMAIN2", pid, LOG_INFO, 
-                              "test user3 message asdasd %d", i);      
-       }
-#endif
-
-#if 0
-       clog_base_t *clog[2] = { cl1->base, cl2->base };
-
-       clusterlog_merge(cl1, clog, 2);
-       clog_dump(cl1->base);
-
-       clusterlog_destroy(cl1);
-       clusterlog_destroy(cl2);
-#endif
-}
diff --git a/data/src/logtest2.c b/data/src/logtest2.c
deleted file mode 100644 (file)
index 5f8f8f8..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#define _XOPEN_SOURCE /* glibc2 needs this */
-#include <time.h> /* for strptime */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <glib.h>
-#include <sys/types.h>
-#include <sys/syslog.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <unistd.h>
-#include <string.h>
-#include <ctype.h>
-
-#include "cfs-utils.h"
-#include "logger.h"
-
-struct clog_base {
-       uint32_t size;
-       uint32_t cpos;
-       char data[];
-};
-
-struct clusterlog {
-       GHashTable *dedup;
-       GMutex mutex;
-       clog_base_t *base;
-};
-
-cfs_t cfs = {
-        .debug = 0,
-       .nodename = "testnode",
-};
-
-void get_state(clusterlog_t *cl) {
-    unsigned int res_len;
-    clusterlog_get_state(cl, &res_len);
-}
-
-
-void insert(clusterlog_t *cl) {
-       uint32_t pid = getpid();
-       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
-       clog_pack(entry, cfs.nodename, "root", "cluster", pid, time(NULL), LOG_INFO, "short");
-       clusterlog_insert(cl, entry);
-}
-
-void insert2(clusterlog_t *cl) {
-       uint32_t pid = getpid();
-       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
-       clog_pack(entry, cfs.nodename, "root", "cluster", pid, time(NULL), LOG_INFO, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
-       clusterlog_insert(cl, entry);
-}
-
-int
-main(void)
-{
-       uint32_t pid = getpid();
-
-       clusterlog_t *cl3 = clusterlog_new();
-
-       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
-       clog_pack(entry, cfs.nodename, "root", "cluster", pid, time(NULL), LOG_INFO, "starting cluster log");
-       clusterlog_insert(cl3, entry);
-
-       for (int i = 0; i < 184; i++) {
-              insert2(cl3);
-       }
-
-       for (int i = 0; i < 1629; i++) {
-              insert(cl3);
-       }
-
-       GString *outbuf = g_string_new(NULL);
-
-       // all of these segfault if they don't handle wrap-arounds pointing to already overwritten entries
-       clusterlog_dump(cl3, outbuf, NULL, 8192);
-       clog_dump(cl3->base);
-       get_state(cl3);
-
-       clusterlog_destroy(cl3);
-}
diff --git a/data/src/loop.c b/data/src/loop.c
deleted file mode 100644 (file)
index a80fc66..0000000
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#define G_LOG_DOMAIN "loop"
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <utime.h>
-#include <sys/stat.h>
-#include <glib.h>
-#include <syslog.h>
-#include <poll.h>
-
-#include "cfs-utils.h"
-#include "loop.h"
-
-struct cfs_service {
-       qb_loop_t *qbloop;      
-       const char *log_domain;
-       cfs_service_callbacks_t *callbacks;
-       gboolean restartable;
-       gpointer context;
-       gboolean initialized;
-       int errcount;
-       time_t last_init;
-       enum qb_loop_priority priority;
-       time_t period;
-       time_t last_timeout;
-};
-
-struct cfs_loop {
-       GThread *worker;
-       gboolean server_started;
-       gboolean stop_worker_flag;
-       GCond server_started_cond;
-       GCond server_stopped_cond;
-       GMutex server_started_mutex;
-       qb_loop_t *qbloop;      
-       struct fuse *fuse;
-       GList *services;
-};
-
-gboolean 
-cfs_service_set_timer(
-       cfs_service_t *service,
-       time_t period)
-{
-       g_return_val_if_fail(service != NULL, FALSE);
-
-       service->period = period;
-
-       return TRUE;
-}
-
-gpointer 
-cfs_service_get_context(cfs_service_t *service)
-{
-       g_return_val_if_fail(service != NULL, NULL);
-
-       return service->context;
-}
-
-void 
-cfs_service_set_restartable(
-       cfs_service_t *service,
-       gboolean restartable)
-{
-       g_return_if_fail(service != NULL);
-
-       service->restartable = restartable;
-}
-
-cfs_service_t *
-cfs_service_new(
-       cfs_service_callbacks_t *callbacks,
-       const char *log_domain,
-       gpointer context)
-{
-       g_return_val_if_fail(callbacks != NULL, NULL);
-       g_return_val_if_fail(callbacks->cfs_service_initialize_fn != NULL, NULL);
-       g_return_val_if_fail(callbacks->cfs_service_finalize_fn != NULL, NULL);
-       g_return_val_if_fail(callbacks->cfs_service_dispatch_fn != NULL, NULL);
-
-       cfs_service_t *service = g_new0(cfs_service_t, 1);
-       if(!service)
-               return NULL;
-       
-       if (log_domain)
-               service->log_domain = log_domain;
-       else
-               service->log_domain = G_LOG_DOMAIN;
-
-       service->callbacks = callbacks;
-
-       service->restartable = TRUE;
-
-       service->context = context;
-
-       return service;
-}
-
-cfs_loop_t *
-cfs_loop_new(struct fuse *fuse)
-{
-       cfs_loop_t *loop = g_new0(cfs_loop_t, 1);
-
-       g_mutex_init(&loop->server_started_mutex);
-       g_cond_init(&loop->server_started_cond);
-       g_cond_init(&loop->server_stopped_cond);
-       
-       if (!(loop->qbloop = qb_loop_create())) {
-               cfs_critical("cant create event loop");
-               g_free(loop);
-               return NULL;
-       }
-
-       loop->fuse = fuse;
-
-       return loop;
-}
-
-void 
-cfs_loop_destroy(cfs_loop_t *loop)
-{
-       g_return_if_fail(loop != NULL);
-
-       if (loop->qbloop)
-               qb_loop_destroy(loop->qbloop);
-
-       if(loop->services)
-               g_list_free(loop->services);
-
-       g_mutex_clear(&loop->server_started_mutex);
-       g_cond_clear(&loop->server_started_cond);
-       g_cond_clear(&loop->server_stopped_cond);
-
-       g_free(loop);
-}
-
-gboolean 
-cfs_loop_add_service(
-       cfs_loop_t *loop, 
-       cfs_service_t *service, 
-       enum qb_loop_priority priority)
-{
-       g_return_val_if_fail(loop != NULL, FALSE);
-       g_return_val_if_fail(service != NULL, FALSE);
-       g_return_val_if_fail(service->log_domain != NULL, FALSE);
-
-       service->priority = priority;
-       service->qbloop = loop->qbloop;
-
-       loop->services = g_list_append(loop->services, service);
-
-       return TRUE;
-}
-
-static int32_t 
-poll_dispatch_fn(
-       int32_t fd, 
-       int32_t revents, 
-       void *data)
-{
-       cfs_service_t *service = (cfs_service_t *)data;
-
-       if (!service->callbacks->cfs_service_dispatch_fn(service, service->context)) {
-               qb_loop_poll_del(service->qbloop, fd);
-               service->initialized = FALSE;
-               service->errcount = 0;
-
-               if (!service->restartable)
-                       service->callbacks->cfs_service_finalize_fn(service, service->context);
-                       
-               return -1;
-       }
-
-       return 0;
-}
-
-static void 
-service_timer_job(void *data)
-{
-       g_return_if_fail(data != NULL);
-
-       cfs_loop_t *loop = (cfs_loop_t *)data;
-       qb_loop_t *qbloop = loop->qbloop;
-
-       gboolean terminate = FALSE;
-       
-       g_mutex_lock (&loop->server_started_mutex);
-
-       if (loop->stop_worker_flag) {
-               cfs_debug ("got terminate request");
-               qb_loop_stop(qbloop);
-               loop->server_started = 0;
-               g_cond_signal (&loop->server_stopped_cond);
-               terminate = TRUE;
-       } else if (!loop->server_started) {
-               loop->server_started = 1;
-               g_cond_signal (&loop->server_started_cond);
-       }
-       
-       g_mutex_unlock (&loop->server_started_mutex);
-
-       if (terminate)
-               return;
-       
-       GList *l = loop->services;
-       while (l) {
-               cfs_service_t *service = (cfs_service_t *)l->data;
-               l = g_list_next(l);
-
-               if (!service->initialized)
-                       continue;
-
-               time_t ctime = time(NULL);
-               if (service->period && service->callbacks->cfs_service_timer_fn &&
-                   ((ctime - service->last_timeout) >= service->period)) {
-                       service->last_timeout = ctime;
-                       service->callbacks->cfs_service_timer_fn(service, service->context);    
-               }
-       }
-
-       qb_loop_timer_handle th;
-       qb_loop_timer_add(qbloop, QB_LOOP_LOW, 1000000000, data, service_timer_job, &th);
-}
-
-static void 
-service_start_job(void *data)
-{
-       g_return_if_fail(data != NULL);
-
-       cfs_loop_t *loop = (cfs_loop_t *)data;
-       qb_loop_t *qbloop = loop->qbloop;
-
-       gboolean terminate = FALSE;
-       g_mutex_lock (&loop->server_started_mutex);
-       terminate = loop->stop_worker_flag;
-       g_mutex_unlock (&loop->server_started_mutex);
-
-       if (terminate)
-               return;
-
-       GList *l = loop->services;
-       time_t ctime = time(NULL);
-
-       while (l) {
-               cfs_service_t *service = (cfs_service_t *)l->data;
-               l = g_list_next(l);
-
-               if (service->restartable && !service->initialized && 
-                   ((ctime - service->last_init) > 5)) {
-                       int fd = service->callbacks->cfs_service_initialize_fn(service, service->context);
-                       service->last_init = ctime;
-
-                       if (fd >= 0) {
-                               service->initialized = TRUE;
-                               service->errcount = 0;
-
-                               int res;
-                               if ((res = qb_loop_poll_add(qbloop, service->priority, fd, POLLIN, 
-                                                           service, poll_dispatch_fn)) != 0) {
-                                       cfs_critical("qb_loop_poll_add failed: %s - disabling service", 
-                                                    g_strerror(-res));
-                                       service->initialized = FALSE;
-                                       service->restartable = FALSE;
-                                       service->callbacks->cfs_service_finalize_fn(service, service->context);
-                               }
-                       } else {
-                               if (!service->errcount) 
-                                       cfs_dom_critical(service->log_domain, "can't initialize service");
-                               service->errcount++;
-                       }
-               }
-       }
-
-       qb_loop_timer_handle th;
-       qb_loop_timer_add(qbloop, QB_LOOP_LOW, 1000000000, data, service_start_job, &th);
-}
-
-static gpointer 
-cfs_loop_worker_thread(gpointer data)
-{
-       g_return_val_if_fail(data != NULL, NULL);
-
-       cfs_loop_t *loop = (cfs_loop_t *)data;
-       qb_loop_t *qbloop = loop->qbloop;
-
-       GList *l;
-       time_t ctime = time(NULL);
-       l = loop->services;
-       while (l) {
-               cfs_service_t *service = (cfs_service_t *)l->data;
-               l = g_list_next(l);
-               service->last_timeout = ctime;
-       }
-
-       qb_loop_timer_handle th;
-       qb_loop_timer_add(qbloop, QB_LOOP_LOW, 10000000, loop, service_start_job, &th);
-
-       qb_loop_timer_add(qbloop, QB_LOOP_LOW, 1000000000, loop, service_timer_job, &th);
-
-       cfs_debug("start loop");
-       
-       qb_loop_run(qbloop);
-
-       cfs_debug("end loop");
-
-       l = loop->services;
-       while (l) {
-               cfs_service_t *service = (cfs_service_t *)l->data;
-               l = g_list_next(l);
-               service->callbacks->cfs_service_finalize_fn(service, service->context);
-       }
-
-       return NULL;
-}
-
-gboolean 
-cfs_loop_start_worker(cfs_loop_t *loop)
-{
-       g_return_val_if_fail(loop != NULL, FALSE);
-
-       loop->worker = g_thread_new("cfs_loop", cfs_loop_worker_thread, loop);
-       
-       g_mutex_lock (&loop->server_started_mutex);
-       while (!loop->server_started)
-               g_cond_wait (&loop->server_started_cond, &loop->server_started_mutex);
-       g_mutex_unlock (&loop->server_started_mutex);
-       
-       cfs_debug("worker started");
-       
-       return TRUE;
-}
-
-void
-cfs_loop_stop_worker(cfs_loop_t *loop)
-{
-       g_return_if_fail(loop != NULL);
-
-       cfs_debug("cfs_loop_stop_worker");
-
-       g_mutex_lock (&loop->server_started_mutex);
-       loop->stop_worker_flag = TRUE;
-       while (loop->server_started)
-               g_cond_wait (&loop->server_stopped_cond, &loop->server_started_mutex);
-       g_mutex_unlock (&loop->server_started_mutex);
-
-       if (loop->worker) {
-               g_thread_join(loop->worker);
-               loop->worker = NULL;
-       }
-}
diff --git a/data/src/loop.h b/data/src/loop.h
deleted file mode 100644 (file)
index 68b783c..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_LOOP_H_
-#define _PVE_LOOP_H_
-
-#define FUSE_USE_VERSION 26
-
-#include <glib.h>
-#include <fuse.h>
-#include <qb/qbdefs.h>
-#include <qb/qbutil.h>
-#include <qb/qbloop.h>
-
-typedef struct cfs_loop cfs_loop_t;
-
-typedef struct cfs_service cfs_service_t;
-
-typedef int (*cfs_service_initialize_fn_t)(
-       cfs_service_t *service, 
-       gpointer context);
-
-typedef        gboolean (*cfs_service_finalize_fn_t)(
-       cfs_service_t *service, 
-       gpointer context);
-
-typedef gboolean (*cfs_service_dispatch_fn_t)(
-       cfs_service_t *service, 
-       gpointer context);
-
-typedef void (*cfs_service_timer_fn_t)(
-       cfs_service_t *service, 
-       gpointer context);
-
-typedef struct {
-       cfs_service_initialize_fn_t cfs_service_initialize_fn;
-       cfs_service_finalize_fn_t cfs_service_finalize_fn;
-       cfs_service_dispatch_fn_t cfs_service_dispatch_fn;
-       cfs_service_timer_fn_t cfs_service_timer_fn;
-} cfs_service_callbacks_t;
-
-cfs_service_t *cfs_service_new(
-       cfs_service_callbacks_t *callbacks,
-       const char *log_domain,
-       gpointer context);
-
-gpointer cfs_service_get_context(
-       cfs_service_t *service);
-
-gboolean cfs_service_set_timer(
-       cfs_service_t *service,
-       time_t period);
-
-void cfs_service_set_restartable(
-       cfs_service_t *service,
-       gboolean restartable);
-
-cfs_loop_t *cfs_loop_new(struct fuse *fuse);
-
-void cfs_loop_destroy(
-       cfs_loop_t *loop);
-
-gboolean cfs_loop_add_service(
-       cfs_loop_t *loop,
-       cfs_service_t *service,
-       enum qb_loop_priority priority);
-
-gboolean cfs_loop_start_worker(
-       cfs_loop_t *loop);
-
-void cfs_loop_stop_worker(
-       cfs_loop_t *loop);
-
-
-#endif /* _PVE_LOOP_H_ */
diff --git a/data/src/memdb.c b/data/src/memdb.c
deleted file mode 100644 (file)
index 6953227..0000000
+++ /dev/null
@@ -1,1601 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdio.h>
-#include <inttypes.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <sys/file.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <string.h>
-#include <errno.h>
-#include <glib.h>
-
-#include "cfs-utils.h"
-#include "memdb.h"
-#include "status.h"
-
-#define CFS_LOCK_TIMEOUT (60*2)
-
-memdb_tree_entry_t *
-memdb_tree_entry_new(
-       const char *name)
-{
-       g_return_val_if_fail(name != NULL, NULL);
-
-       memdb_tree_entry_t *te = g_malloc0(sizeof(memdb_tree_entry_t) + strlen(name) + 1);
-       g_return_val_if_fail(te != NULL, NULL);
-
-       strcpy(te->name, name);
-
-       return te;
-}
-
-memdb_tree_entry_t *
-memdb_tree_entry_copy(
-       memdb_tree_entry_t *te, 
-       gboolean with_data)
-{
-       g_return_val_if_fail(te != NULL, NULL);
-
-       memdb_tree_entry_t *cpy = memdb_tree_entry_new(te->name);
-
-       cpy->parent = te->parent;
-       cpy->inode = te->inode;
-       cpy->version = te->version;
-       cpy->writer = te->writer;
-       cpy->mtime = te->mtime;
-       cpy->type = te->type;
-       cpy->size = te->size;
-
-       if (with_data && te->size && te->type == DT_REG) {
-               cpy->data.value = g_memdup2(te->data.value, te->size);
-       } else { 
-               cpy->data.value = NULL;
-       }
-
-       return cpy;
-}
-
-void 
-memdb_tree_entry_free(
-       memdb_tree_entry_t *te)
-{
-       if (!te)
-               return;
-
-       if (te->type == DT_REG) {
-               if (te->data.value)
-                       g_free(te->data.value);
-       }
-
-       if (te->type == DT_DIR) {
-               if (te->data.entries)
-                       g_hash_table_destroy(te->data.entries);
-       }
-
-       g_free(te);
-}
-
-void 
-memdb_lock_info_free(memdb_lock_info_t *li)
-{
-       g_return_if_fail(li != NULL);
-
-       if (li->path)
-               g_free(li->path);
-
-       g_free(li);
-}
-
-static gint
-memdb_tree_compare(
-       gconstpointer v1,
-       gconstpointer v2)
-{
-       guint64 a = ((const memdb_tree_entry_t *)v1)->inode;
-       guint64 b = ((const memdb_tree_entry_t *)v2)->inode;
-
-       if (a == b)
-               return 0;
-
-       if (a > b)
-               return 1;
-       
-       return -1;
-}
-
-static void 
-split_path(
-       const char *path, 
-       char **dirname, 
-       char **basename)
-{
-       char *dup = g_strdup (path);
-       int len = strlen (dup) - 1;
-       while (len >= 0 && dup[len] == '/') dup[len--] = 0;
-
-       char *dn = g_path_get_dirname (dup);
-       char *bn = g_path_get_basename (dup);
-
-       g_free (dup);
-
-       *dirname = dn;
-       *basename = bn;
-}
-
-static memdb_tree_entry_t *
-memdb_lookup_dir_entry(
-       memdb_t *memdb, 
-       const char *name, 
-       memdb_tree_entry_t *parent)
-{
-
-       g_return_val_if_fail(memdb != NULL, NULL);
-       g_return_val_if_fail(name != NULL, NULL);
-       g_return_val_if_fail(parent != NULL, NULL);
-       g_return_val_if_fail(parent->type == DT_DIR, NULL);
-       
-       GHashTable *ht = parent->data.entries;
-
-       g_return_val_if_fail(ht != NULL, NULL);
-
-       return g_hash_table_lookup(ht, name);
-}
-
-static memdb_tree_entry_t *
-memdb_lookup_path(
-       memdb_t *memdb, 
-       const char *path,
-       memdb_tree_entry_t **parent)
-{
-       g_return_val_if_fail(memdb != NULL, NULL);
-       g_return_val_if_fail(path != NULL, NULL);
-       g_return_val_if_fail(parent != NULL, NULL);
-
-       memdb_tree_entry_t *cdir = memdb->root; 
-       *parent = NULL;
-
-       if (path[0] == 0 || ((path[0] == '.' || path[0] == '/')  && path[1] == 0))
-               return cdir;
-
-       gchar **set = g_strsplit_set(path, "/", 0);
-
-       int i = 0;
-       char *name;
-       
-       while ((name = set[i++])) {
-
-               if (name[0] == 0) continue;
-
-               *parent = cdir;
-               if ((cdir = memdb_lookup_dir_entry(memdb, name, cdir)) == NULL)
-                       break;
-       }
-
-       g_strfreev(set);
-
-       return cdir;
-}
-
-
-static gboolean 
-name_is_vm_config(
-       const char *name, 
-       guint32 *vmid_ret)
-{
-       if (!name || name[0] < '1' || name[0] > '9')
-               return FALSE;
-
-       char *end = NULL;
-
-       errno = 0; /* see man strtoul */
-
-       unsigned long int vmid = strtoul(name, &end, 10);
-
-       if (!end || end[0] != '.' || end[1] != 'c'|| end[2] != 'o' || end[3] != 'n' ||
-           end[4] != 'f' || end[5] != 0 || errno != 0 || vmid > G_MAXUINT32)
-               return FALSE;
-
-       if (vmid_ret)
-               *vmid_ret = (guint32)vmid;
-
-       return TRUE;
-}
-
-static gboolean
-valid_nodename(
-       const char *nodename) 
-{
-       g_return_val_if_fail(nodename != NULL, FALSE);
-
-       /* LDH rule (letters, digits, hyphen) */
-
-       int len = strlen(nodename);
-
-       if (len < 1) {
-               return FALSE;
-       }
-
-       for (int i = 0; i < len; i ++) {
-               char c = nodename[i];
-               if ((c >= 'A' && c <= 'Z') ||
-                   (c >= 'a' && c <= 'z') ||
-                   (c >= '0' && c <= '9') ||
-                   (i != 0 && i != (len-1) && c == '-'))
-                       continue;
-               return FALSE;
-       }
-
-       return TRUE;
-}
-
-static char*
-dir_contain_vm_config(
-       const char *dirname, 
-       int *vmtype_ret)
-{
-       if (!dirname)
-               return NULL;
-
-       if (strncmp(dirname, "nodes/", 6) != 0)
-               return NULL;
-
-       dirname += 6;
-
-       char *nodename = NULL;
-
-       char **sa = g_strsplit(dirname, "/", 2);
-       if (sa[0] && sa[1] && valid_nodename(sa[0])) {
-               if (strcmp(sa[1], "qemu-server") == 0) {
-                       *vmtype_ret = VMTYPE_QEMU;
-                       nodename = g_strdup(sa[0]);
-               } else if (strcmp(sa[1], "openvz") == 0) {
-                       // FIXME: remove openvz stuff for 7.x
-                       *vmtype_ret = VMTYPE_OPENVZ;
-                       nodename = g_strdup(sa[0]);
-               } else if (strcmp(sa[1], "lxc") == 0) {
-                       *vmtype_ret = VMTYPE_LXC;
-                       nodename = g_strdup(sa[0]);
-               } 
-       }
-
-       g_strfreev(sa);
-
-       return nodename;
-}
-
-static char *
-path_contain_vm_config(
-       const char *path, 
-       int *vmtype_ret, 
-       guint32 *vmid_ret)
-{
-       if (!path)
-               return NULL;
-
-       char *dirname = NULL;
-       char *base = NULL;
-       char *nodename = NULL;
-
-       split_path(path, &dirname, &base);
-
-       if (name_is_vm_config(base, vmid_ret))
-               nodename = dir_contain_vm_config(dirname, vmtype_ret);
-       
-       g_free (dirname);
-       g_free (base);
-
-       return nodename;
-}
-
-static gboolean
-vmlist_add_dir(
-       memdb_t *memdb, 
-       GHashTable *vmlist,
-       const char *nodename, 
-       const int vmtype, 
-       memdb_tree_entry_t *subdir)
-{
-       g_return_val_if_fail(memdb != NULL, FALSE);
-       g_return_val_if_fail(vmlist != NULL, FALSE);
-       g_return_val_if_fail(subdir != NULL, FALSE);
-       g_return_val_if_fail(subdir->type == DT_DIR, FALSE);
-       g_return_val_if_fail(subdir->data.entries != NULL, FALSE);
-
-       gboolean ret = TRUE;
-
-       GHashTable *ht = subdir->data.entries;
-       GHashTableIter iter;
-       gpointer key, value;
-
-       g_hash_table_iter_init (&iter, ht);
-
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-
-               memdb_tree_entry_t *node_te = (memdb_tree_entry_t *)value;
-
-               if (node_te->type != DT_REG)
-                       continue;
-
-               guint32 vmid = 0;
-               if (!name_is_vm_config(node_te->name, &vmid))
-                       continue;
-
-               if (!vmlist_hash_insert_vm(vmlist, vmtype, vmid, nodename, FALSE))
-                       ret = FALSE;
-       }
-
-       return ret;
-}
-
-
-gboolean
-memdb_lock_expired(
-       memdb_t *memdb,
-       const char *path,
-       const guchar csum[32])
-{
-       g_return_val_if_fail(memdb != NULL, FALSE);
-       g_return_val_if_fail(memdb->locks != NULL, FALSE);
-       g_return_val_if_fail(path != NULL, FALSE);
-       g_return_val_if_fail(csum != NULL, FALSE);
-
-       memdb_lock_info_t *li;
-       uint32_t ctime = time(NULL);
-
-       if ((li = g_hash_table_lookup(memdb->locks, path))) {
-               if (memcmp(csum, li->csum, 32) != 0) {
-                       li->ltime = ctime;
-                       memcpy(li->csum, csum, 32);
-                       g_critical("wrong lock csum - reset timeout");
-                       return FALSE;
-               }
-               if ((ctime > li->ltime) && ((ctime - li->ltime) > CFS_LOCK_TIMEOUT))
-                       return TRUE;
-       } else {
-               li = g_new0(memdb_lock_info_t, 1);
-               li->path = g_strdup(path);
-               li->ltime = ctime;
-               memcpy(li->csum, csum, 32);
-               g_hash_table_replace(memdb->locks, li->path, li);
-       }
-
-       return FALSE;
-}
-
-void
-memdb_update_locks(memdb_t *memdb)
-{
-       g_return_if_fail(memdb != NULL);
-       g_return_if_fail(memdb->locks != NULL);
-
-       memdb_tree_entry_t *te, *parent;
-
-       if (!(te = memdb_lookup_path(memdb, "priv/lock", &parent)))
-               return;
-               
-       if (te->type != DT_DIR)
-               return;
-
-
-       GHashTable *old =  memdb->locks;
-       memdb->locks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
-                                            (GDestroyNotify)memdb_lock_info_free);
-       GHashTableIter iter;
-       GHashTable *ht = te->data.entries;
-
-       gpointer key, value;
-
-       g_hash_table_iter_init (&iter, ht);
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-
-               memdb_tree_entry_t *lock_te = (memdb_tree_entry_t *)value;
-               if (lock_te->type != DT_DIR)
-                       continue;
-
-               memdb_lock_info_t *li;
-               li = g_new0(memdb_lock_info_t, 1);
-               li->path = g_strdup_printf("priv/lock/%s", lock_te->name);
-
-               guchar csum[32];
-               if (memdb_tree_entry_csum(lock_te, csum)) {
-                       memcpy(li->csum, csum, 32);
-                       memdb_lock_info_t *oldli;
-                       if ((oldli = g_hash_table_lookup(memdb->locks, lock_te->name)) &&
-                           (memcmp(csum, oldli->csum, 32) == 0)) {
-                               li->ltime = oldli->ltime;
-                       } else {
-                               li->ltime = time(NULL);
-                       }
-                       g_hash_table_insert(memdb->locks, li->path, li);
-               } else {
-                       memdb_lock_info_free(li);
-               }
-       }
-
-       if (old)
-               g_hash_table_destroy(old);
-       
-}
-
-gboolean 
-memdb_recreate_vmlist(
-       memdb_t *memdb)
-{
-       g_return_val_if_fail(memdb != NULL, FALSE);
-
-       memdb_tree_entry_t *te, *parent;
-
-       if (!(te = memdb_lookup_path(memdb, "nodes", &parent)))
-               return TRUE;
-               
-       if (te->type != DT_DIR)
-               return TRUE;
-
-       GHashTable *vmlist = vmlist_hash_new();
-
-       GHashTable *ht = te->data.entries;
-
-       gboolean ret = TRUE;
-
-       GHashTableIter iter;
-       gpointer key, value;
-
-       g_hash_table_iter_init (&iter, ht);
-
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-
-               memdb_tree_entry_t *node_te = (memdb_tree_entry_t *)value;
-               if (node_te->type != DT_DIR)
-                       continue;
-
-               if (!valid_nodename(node_te->name))
-                       continue;
-
-               if ((te = g_hash_table_lookup(node_te->data.entries, "qemu-server"))) {
-                       if (!vmlist_add_dir(memdb, vmlist, node_te->name, VMTYPE_QEMU, te))
-                               ret = FALSE;
-               }
-               // FIXME: remove openvz stuff for 7.x
-               if ((te = g_hash_table_lookup(node_te->data.entries, "openvz"))) {
-                       if (!vmlist_add_dir(memdb, vmlist, node_te->name, VMTYPE_OPENVZ, te))
-                               ret = FALSE;
-               }
-               if ((te = g_hash_table_lookup(node_te->data.entries, "lxc"))) {
-                       if (!vmlist_add_dir(memdb, vmlist, node_te->name, VMTYPE_LXC, te))
-                               ret = FALSE;
-               }
-       }
-
-       /* always update list - even if we detected duplicates */
-       cfs_status_set_vmlist(vmlist);
-
-       return ret;
-}
-
-memdb_t *
-memdb_open(const char *dbfilename)
-{
-       memdb_t *memdb = g_new0(memdb_t, 1);
-       
-       g_mutex_init(&memdb->mutex);
-
-       memdb->dbfilename = g_strdup(dbfilename);
-
-       memdb->root = memdb_tree_entry_new("");
-       memdb->root->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
-       memdb->root->type = DT_DIR;
-
-       memdb->index = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL,
-                                            (GDestroyNotify)memdb_tree_entry_free);
-
-       g_hash_table_replace(memdb->index, &memdb->root->inode, memdb->root);
-
-       memdb->locks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
-                                            (GDestroyNotify)memdb_lock_info_free);
-
-       if (!(memdb->bdb = bdb_backend_open(dbfilename, memdb->root, memdb->index))) {
-               memdb_close(memdb);
-               return NULL;
-       }
-
-       record_memdb_reload();
-
-       if (!memdb_recreate_vmlist(memdb)) {
-               memdb_close(memdb);
-               return NULL;            
-       }
-
-       memdb_update_locks(memdb);
-
-       cfs_debug("memdb open '%s' successful (version = %016" PRIX64 ")",
-                 dbfilename, memdb->root->version);
-
-       return memdb;
-}
-
-void 
-memdb_close(memdb_t *memdb)
-{
-       g_return_if_fail(memdb != NULL);
-
-       g_mutex_lock (&memdb->mutex);
-
-       if (memdb->bdb)
-               bdb_backend_close(memdb->bdb);
-
-       if (memdb->index)
-               g_hash_table_destroy(memdb->index);
-
-       if (memdb->locks)
-               g_hash_table_destroy(memdb->locks);
-
-       if (memdb->dbfilename)
-               g_free(memdb->dbfilename);
-
-       memdb->index = NULL;
-       memdb->bdb = NULL;
-       memdb->dbfilename = NULL;
-
-       g_mutex_unlock (&memdb->mutex);
-
-       g_mutex_clear (&memdb->mutex);
-
-       g_free(memdb);
-}
-
-int memdb_mkdir(
-       memdb_t *memdb, 
-       const char *path,
-       guint32 writer,
-       guint32 mtime)
-{
-       g_return_val_if_fail(memdb != NULL, -EINVAL);
-       g_return_val_if_fail(path != NULL, -EINVAL);
-
-       int ret = -EACCES;
-
-       char *dirname = NULL;
-       char *base = NULL;
-
-       g_mutex_lock (&memdb->mutex);
-
-       if (memdb->errors) {
-               ret = -EIO;
-               goto ret;
-       }
-
-       split_path(path, &dirname, &base);
-
-       memdb_tree_entry_t *parent, *unused;
-
-       if (!(parent = memdb_lookup_path(memdb, dirname, &unused))) {
-               ret = -ENOENT;
-               goto ret;
-       }
-               
-       if (parent->type != DT_DIR) {
-               ret = -ENOTDIR;
-               goto ret;
-       }
-
-       /* do not allow '.' and '..' */
-       if ((base[0] == 0) ||
-           (base[0] == '.' && base[1] == 0) ||
-           (base[0] == '.' && base[1] == '.' && base[2] == 0)) {
-               ret = -EACCES;          
-               goto ret;
-       }
-
-       memdb_tree_entry_t *te;
-       if ((te = memdb_lookup_dir_entry(memdb, base, parent))) {
-               ret = -EEXIST;
-               goto ret;
-       }
-
-       memdb->root->version++;
-       memdb->root->mtime = mtime;
-       memdb->root->writer = writer;
-
-       te = memdb_tree_entry_new(base);
-       te->parent = parent->inode;
-       te->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
-       te->inode = te->version = memdb->root->version;
-       te->writer = writer;
-       te->type = DT_DIR;
-       te->mtime = mtime;
-
-       g_hash_table_replace(parent->data.entries, te->name, te);
-       g_hash_table_replace(memdb->index, &te->inode, te);
-
-       cfs_debug("memdb_mkdir %s %s %016" PRIX64, dirname, base, memdb->root->version);
-
-       if (bdb_backend_write(memdb->bdb, te->inode, te->parent, te->version, 
-                             te->writer, te->mtime, 0, DT_DIR, te->name, NULL, 0)) {
-               memdb->errors = 1;
-               ret = -EIO;
-               goto ret;
-       }
-
-       if (strcmp(dirname, "priv/lock") == 0) {
-               g_hash_table_remove(memdb->locks, path);
-               guchar csum[32];
-               if (memdb_tree_entry_csum(te, csum)) {
-                       memdb_lock_expired(memdb, path, csum); // insert a new entry
-               }
-       }
-
-       ret = 0;
-
- ret:
-       g_mutex_unlock (&memdb->mutex);
-
-       g_free (dirname);
-       g_free (base);
-
-       return ret;
-}
-
-// Original memdb_read without locking - Caller MUST handle the locking
-int
-memdb_read_nolock(
-       memdb_t *memdb,
-       const char *path,
-       gpointer *data_ret)
-{
-       g_return_val_if_fail(memdb != NULL, -EINVAL);
-       g_return_val_if_fail(path != NULL, -EINVAL);
-       g_return_val_if_fail(data_ret != NULL, -EINVAL);
-
-       memdb_tree_entry_t *te, *parent;
-
-       if ((te = memdb_lookup_path(memdb, path, &parent))) {
-               if (te->type == DT_REG) {
-                       *data_ret = g_memdup2(te->data.value, te->size);
-                       guint32 size = te->size;
-                       return size;
-               }
-       }
-
-       return -ENOENT;
-}
-
-int
-memdb_read(
-       memdb_t *memdb,
-       const char *path,
-       gpointer *data_ret)
-{
-       int res;
-       g_mutex_lock (&memdb->mutex);
-
-       res = memdb_read_nolock(memdb, path, data_ret);
-
-       g_mutex_unlock (&memdb->mutex);
-
-       return res;
-}
-
-static int 
-memdb_pwrite(
-       memdb_t *memdb, 
-       const char *path, 
-       guint32 writer, 
-       guint32 mtime, 
-       gconstpointer data, 
-       size_t count, 
-       off_t offset, 
-       gboolean truncate, 
-       gboolean create)
-{
-       g_return_val_if_fail(memdb != NULL, -EINVAL);
-       g_return_val_if_fail(path != NULL, -EINVAL);
-       g_return_val_if_fail(count == 0 || data != NULL, -EINVAL);
-
-       int ret = -EACCES;
-
-       char *dirname = NULL;
-       char *base = NULL;
-       char *nodename = NULL;
-
-       g_mutex_lock (&memdb->mutex);
-
-       if (memdb->errors) {
-               ret = -EIO;
-               goto ret;
-       }
-
-       if ((offset + count) > MEMDB_MAX_FILE_SIZE) {
-               ret = -EFBIG;
-               goto ret;
-       }
-
-       split_path(path, &dirname, &base);
-
-       memdb_tree_entry_t *parent, *unused;
-       if (!(parent = memdb_lookup_path(memdb, dirname, &unused))) {
-               ret = -ENOENT;
-               goto ret;
-       }
-       if (parent->type != DT_DIR) {
-               ret = -ENOTDIR;
-               goto ret;
-       }
-
-       /* do not allow '.' and '..' */
-       if ((base[0] == 0) ||
-           (base[0] == '.' && base[1] == 0) ||
-           (base[0] == '.' && base[1] == '.' && base[2] == 0)) {
-               ret = -EACCES;          
-               goto ret;
-       }
-
-       guint32 vmid = 0;
-       int vmtype = 0;
-
-       if ((nodename = path_contain_vm_config(path, &vmtype, &vmid))) {
-               if (vmlist_different_vm_exists(vmtype, vmid, nodename)) {
-                       ret = -EEXIST;
-                       goto ret;
-               }
-       }
-
-       gpointer olddata = NULL;
-
-       memdb_tree_entry_t *te, *old;
-       if ((old = te = memdb_lookup_dir_entry(memdb, base, parent))) {
-               if (te->type != DT_REG) { 
-                       ret = -ENOTDIR;
-                       goto ret;
-               }
-
-               if (create) {
-                       ret = -EEXIST;
-                       goto ret;
-               }
-
-               memdb->root->version++;
-               memdb->root->mtime = mtime;
-               memdb->root->writer = writer;
-
-               olddata = te->data.value;
-       } else {
-
-               if (!create) {
-                       ret = -ENOENT;
-                       goto ret;
-               }
-
-               memdb->root->version++;
-               memdb->root->mtime = mtime;
-               memdb->root->writer = writer;
-
-               te = memdb_tree_entry_new(base);
-               te->parent = parent->inode;
-               te->type = DT_REG;
-               te->inode = memdb->root->version;
-       }
-
-       te->version = memdb->root->version;
-       te->writer = writer;
-       te->mtime = mtime;
-
-       size_t newsize = offset + count;
-
-       gpointer newdata = NULL;
-
-       if (olddata) {
-
-               if (newsize > te->size) {
-                       newdata = g_malloc0(newsize);
-                       memcpy(newdata, olddata, te->size);
-
-               } else {
-
-                       if (!truncate) {
-                               newsize = te->size;
-                       }
-                       newdata = g_malloc0(newsize);
-                       memcpy(newdata, olddata, newsize);
-               }
-
-               if (count && data)
-                       memcpy((uint8_t *) newdata + offset, data, count);
-
-       } else {
-
-               if (count && data) {
-                       newdata = g_malloc0(newsize);
-                       memcpy((uint8_t *) newdata + offset, data, count);
-               }
-       }
-
-       te->size = newsize;
-       te->data.value = newdata;
-
-       g_free(olddata);
-
-       if (!old) {
-               g_hash_table_replace(parent->data.entries, te->name, te);
-               g_hash_table_replace(memdb->index, &te->inode, te);
-       }
-
-       record_memdb_change(path);
-
-       cfs_debug("memdb_pwrite %s %s %016" PRIX64 " %016" PRIX64, dirname, te->name, te->inode, te->version);
-
-       if (bdb_backend_write(memdb->bdb, te->inode, te->parent, te->version, 
-                             te->writer, te->mtime, te->size, te->type, te->name, 
-                             te->data.value, 0)) {
-               memdb->errors = 1;
-               ret = -EIO;
-               goto ret;
-       }
-
-       if (nodename)
-               vmlist_register_vm(vmtype, vmid, nodename);
-
-       ret = count;
-
- ret:
-       g_mutex_unlock (&memdb->mutex);
-
-       g_free (nodename);
-       g_free (dirname);
-       g_free (base);
-
-       return ret;
-}
-
-int 
-memdb_mtime(
-       memdb_t *memdb, 
-       const char *path, 
-       guint32 writer, 
-       guint32 mtime)
-{
-       g_return_val_if_fail(memdb != NULL, -EINVAL);
-       g_return_val_if_fail(path != NULL, -EINVAL);
-
-       int ret = -EACCES;
-
-       char *dirname = NULL;
-       char *base = NULL;
-
-       g_mutex_lock (&memdb->mutex);
-
-       if (memdb->errors) {
-               ret = -EIO;
-               goto ret;
-       }
-
-       split_path(path, &dirname, &base);
-
-       memdb_tree_entry_t *parent, *unused;
-       if (!(parent = memdb_lookup_path(memdb, dirname, &unused))) {
-               ret = -ENOENT;
-               goto ret;
-       }
-       if (parent->type != DT_DIR) {
-               ret = -ENOTDIR;
-               goto ret;
-       }
-
-       /* do not allow '.' and '..' */
-       if ((base[0] == 0) ||
-           (base[0] == '.' && base[1] == 0) ||
-           (base[0] == '.' && base[1] == '.' && base[2] == 0)) {
-               ret = -EACCES;          
-               goto ret;
-       }
-
-       memdb_tree_entry_t *te;
-       if (!(te = memdb_lookup_dir_entry(memdb, base, parent))) {
-               ret = -ENOENT;
-               goto ret;
-       }
-
-        int is_lock = (strcmp(dirname, "priv/lock") == 0) && (te->type == DT_DIR);
-
-       /* NOTE: we use utime(0,0) to trigger 'unlock', so we do not
-        * allow to change mtime for locks (only if mtime is newer).
-        * See README for details about locks.
-        */
-        if (is_lock) {
-            if (mtime < te->mtime) {
-               cfs_debug("dir is locked");
-               ret = -EACCES;          
-               goto ret;
-            } else {
-                /* only allow lock updates if the writer is the same */
-                if (te->writer != writer) { 
-                    ret = -EACCES;             
-                    goto ret;
-                }
-            }
-       }
-       
-       memdb->root->version++;
-       memdb->root->mtime = mtime;
-       memdb->root->writer = writer;
-
-       te->version = memdb->root->version;
-       te->writer = writer;
-       te->mtime = mtime;
-
-       record_memdb_change(path);
-
-       cfs_debug("memdb_mtime %s %s %016" PRIX64 " %016" PRIX64, dirname, te->name, te->inode, te->version);
-
-       if (bdb_backend_write(memdb->bdb, te->inode, te->parent, te->version, 
-                             te->writer, te->mtime, te->size, te->type, te->name, 
-                             te->data.value, 0)) {
-               memdb->errors = 1;
-               ret = -EIO;
-               goto ret;
-       }
-
-        if (is_lock) {
-            cfs_debug("update cfs lock");
-            g_hash_table_remove(memdb->locks, path);
-            guchar csum[32];
-            if (memdb_tree_entry_csum(te, csum)) {
-                memdb_lock_expired(memdb, path, csum); // insert a new entry
-            }
-        }
-
-       ret = 0;
-
- ret:
-       g_mutex_unlock (&memdb->mutex);
-
-       g_free (dirname);
-       g_free (base);
-
-       return ret;
-}
-
-int 
-memdb_create(
-       memdb_t *memdb, 
-       const char *path,
-       guint32 writer,
-       guint32 mtime)
-{
-       return memdb_pwrite(memdb, path, writer, mtime, NULL, 0, 0, FALSE, TRUE);
-}
-
-int 
-memdb_write(
-       memdb_t *memdb, 
-       const char *path, 
-       guint32 writer, 
-       guint32 mtime,
-       gconstpointer data, 
-       size_t count, 
-       off_t offset, 
-       gboolean truncate)
-{
-       return memdb_pwrite(memdb, path, writer, mtime, data, count, offset, truncate, FALSE);
-}
-
-memdb_tree_entry_t *
-memdb_getattr(
-       memdb_t *memdb, 
-       const char *path)
-{
-       memdb_tree_entry_t *te, *parent; 
-
-       g_mutex_lock (&memdb->mutex);
-
-       if ((te = memdb_lookup_path(memdb, path, &parent))) {
-
-               memdb_tree_entry_t *cpy = memdb_tree_entry_copy(te, 0);
-
-               g_mutex_unlock (&memdb->mutex);
-
-               return cpy;
-       }
-
-       g_mutex_unlock (&memdb->mutex);
-
-       return NULL;
-}
-
-GList *
-memdb_readdir(
-       memdb_t *memdb, 
-       const char *path)
-{
-       g_return_val_if_fail(memdb != NULL, NULL);
-       g_return_val_if_fail(path != NULL, NULL);
-
-       memdb_tree_entry_t *te, *parent;
-
-       GList *list = NULL;
-
-       g_mutex_lock (&memdb->mutex);
-
-       if (!(te = memdb_lookup_path(memdb, path, &parent)))
-               goto ret;
-               
-       if (te->type != DT_DIR)
-               goto ret;
-
-       GHashTable *ht = te->data.entries;
-
-       GHashTableIter iter;
-       gpointer key, value;
-
-       g_hash_table_iter_init (&iter, ht);
-
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-
-               te = (memdb_tree_entry_t *)value;
-
-               memdb_tree_entry_t *cpy = memdb_tree_entry_copy(te, 0);
-
-               list = g_list_append(list, cpy);
-       }
-
- ret:
-       g_mutex_unlock (&memdb->mutex);
-       
-       return list;
-}
-
-void 
-memdb_dirlist_free(GList *dirlist)
-{
-       GList *l = dirlist;
-
-       while (l) {
-               if (l->data) 
-                       g_free (l->data);
-
-               l = g_list_next(l);
-       }
-
-       if (dirlist) 
-               g_list_free(dirlist);
-}
-
-static int 
-unlink_tree_entry(
-       memdb_t *memdb,
-       memdb_tree_entry_t *parent,
-       memdb_tree_entry_t *te)
-{
-       g_return_val_if_fail(parent != NULL, -EACCES);
-       g_return_val_if_fail(parent->inode == te->parent, -EACCES);
-
-       if (te->type == DT_DIR)
-               if (g_hash_table_size(te->data.entries))
-                       return -ENOTEMPTY;
-
-       if (!g_hash_table_steal(parent->data.entries, te->name)) {
-               cfs_critical("internal error - can't delete entry");
-               memdb->errors = 1;
-               return -EIO;
-       }
-
-       if (!g_hash_table_steal(memdb->index, &te->inode)) {
-               cfs_critical("internal error - can't delete entry");
-               memdb->errors = 1;
-               return -EIO;
-       }
-
-       return 0;
-}
-
-int 
-memdb_rename(
-       memdb_t *memdb, 
-       const char *from, 
-       const char *to, 
-       guint32 writer,
-       guint32 mtime)
-{
-       int ret = -EACCES;
-
-       char *nodename = NULL;
-       char *dirname = NULL;
-       char *base = NULL;
-
-       guint32 vmid = 0;
-       guint32 from_vmid = 0;
-       int vmtype = 0;
-       int from_vmtype = 0;
-       char *from_node = NULL;
-
-       g_mutex_lock (&memdb->mutex);
-
-       if (memdb->errors) {
-               ret = -EIO;
-               goto ret;
-       }
-
-       memdb_tree_entry_t *from_te, *from_parent;
-       memdb_tree_entry_t *to_te, *to_parent;
-       memdb_tree_entry_t *target_te, *target_parent;
-
-       guint64 delete_inode = 0;
-
-       if (!(from_te = memdb_lookup_path(memdb, from, &from_parent))) {
-               ret = -ENOENT;
-               goto ret;
-       }       
-
-       if (!from_parent) { /* can't rename root */
-               ret = -EACCES;
-               goto ret;
-       }
-
-       from_node = path_contain_vm_config(from, &from_vmtype, &from_vmid);
-
-       if (from_te->type == DT_REG && (nodename = path_contain_vm_config(to, &vmtype, &vmid))) {
-               if (vmlist_different_vm_exists(vmtype, vmid, nodename)) {
-                       if (!(from_node && vmid == from_vmid)) {
-                               ret = -EEXIST;
-                               goto ret;
-                       }
-               }
-       }
-
-       /* we do not allow rename for locks */
-       if (from_te->type == DT_DIR && path_is_lockdir(from)) {
-               ret = -EACCES;
-               goto ret;
-       }
-
-       if ((to_te = memdb_lookup_path(memdb, to, &to_parent))) {
-
-               if ((ret = unlink_tree_entry(memdb, to_parent, to_te)) != 0)
-                       goto ret;
-
-               base = strdup(to_te->name);
-
-               delete_inode = to_te->inode;
-
-               target_te = to_parent;
-
-               memdb_tree_entry_free(to_te);
-
-       } else {
-
-               split_path(to, &dirname, &base);
-
-               if (!(target_te = memdb_lookup_path(memdb, dirname, &target_parent))) {
-                       ret = -ENOENT;
-                       goto ret;
-               }
-               
-               if (target_te->type != DT_DIR) {
-                       ret = -ENOTDIR;
-                       goto ret;
-               }
-       }
-
-       record_memdb_change(from);
-       record_memdb_change(to);
-
-       /* NOTE: unlink_tree_entry() make sure that we can only 
-          rename emtpy directories */
-
-       if ((ret = unlink_tree_entry(memdb, from_parent, from_te)) != 0)
-               goto ret;
-
-       memdb->root->version++;
-       memdb->root->mtime = mtime;
-       memdb->root->writer = writer;
-
-       memdb_tree_entry_t *new = memdb_tree_entry_new(base);
-       new->parent = target_te->inode;
-       new->inode = from_te->inode;
-       new->version = memdb->root->version;
-       new->writer = writer;
-       new->mtime = mtime;
-       new->size = from_te->size;
-       new->type =  from_te->type;
-       new->data = from_te->data;
-
-       g_free(from_te);
-
-       g_hash_table_replace(target_te->data.entries, new->name, new);
-       g_hash_table_replace(memdb->index, &new->inode, new);
-
-       if (bdb_backend_write(memdb->bdb, new->inode, new->parent, 
-                             new->version, new->writer, new->mtime,
-                             new->size, new->type, new->name, 
-                             new->data.value, delete_inode)) {
-               memdb->errors = 1;
-               ret = -EIO;
-               goto ret;
-       }
-
-       if (new->type == DT_REG) {
-
-               if (from_node)
-                       vmlist_delete_vm(from_vmid);
-
-               if (nodename)
-                       vmlist_register_vm(vmtype, vmid, nodename);
-               
-       } else if (new->type == DT_DIR) {
-               /* directories are alwayse empty (see unlink_tree_entry) */
-       }
-
-       ret = 0;
-
- ret:
-       g_mutex_unlock (&memdb->mutex);
-
-       g_free(from_node);
-       g_free (nodename);
-       g_free (dirname);
-       g_free (base);
-
-       return ret;
-}
-
-int 
-memdb_delete(
-       memdb_t *memdb, 
-       const char *path, 
-       guint32 writer, 
-       guint32 mtime)
-{
-       memdb_tree_entry_t *te, *parent;
-
-       g_mutex_lock (&memdb->mutex);
-
-       int ret = -EACCES;
-
-       if (memdb->errors) {
-               ret = -EIO;
-               goto ret;
-       }
-
-       if (!(te = memdb_lookup_path(memdb, path, &parent))) {
-               ret = -ENOENT;
-               goto ret;
-       }       
-               
-       if (!parent) { /* cant remove root */
-               ret = -EACCES;
-               goto ret;
-       }
-
-       if (te->type == DT_DIR) {
-               if (g_hash_table_size(te->data.entries)) {
-                       ret = -ENOTEMPTY;
-                       goto ret;
-               }
-
-               g_hash_table_remove(memdb->locks, path);
-       }
-
-       record_memdb_change(path);
-
-       if ((ret = unlink_tree_entry(memdb, parent, te)) != 0)
-               goto ret;
-
-       memdb->root->version++;
-       memdb->root->mtime = mtime;
-       memdb->root->writer = writer;
-
-       if (bdb_backend_write(memdb->bdb, 0, 0, memdb->root->version, writer, mtime, 0, 
-                             DT_REG, NULL, NULL, te->inode)) {
-               memdb->errors = 1;
-               memdb_tree_entry_free(te);
-               ret = -EIO;
-               goto ret;
-       }
-
-       memdb_tree_entry_free(te);
-
-       int vmtype = 0;
-       guint32 vmid = 0;
-       char *nodename;
-       if ((nodename = path_contain_vm_config(path, &vmtype, &vmid))) {
-               g_free(nodename); 
-               vmlist_delete_vm(vmid);
-       }
-
-       ret = 0;
-
- ret:
-       g_mutex_unlock (&memdb->mutex);
-
-       return ret;
-}
-
-int 
-memdb_statfs(
-       memdb_t *memdb, 
-       struct statvfs *stbuf)
-{
-       g_return_val_if_fail(memdb != NULL, -EINVAL);
-       g_return_val_if_fail(stbuf != NULL, -EINVAL);
-
-       g_mutex_lock (&memdb->mutex);
-
-       GHashTableIter iter;
-       gpointer key, value;
-
-       size_t size = 0;
-       size_t files = 0;
-
-       g_hash_table_iter_init (&iter, memdb->index);
-
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-               memdb_tree_entry_t *te = (memdb_tree_entry_t *)value;
-               files++;
-               size += te->size;
-       }
-
-       g_mutex_unlock (&memdb->mutex);
-
-       stbuf->f_bsize = MEMDB_BLOCKSIZE;
-       stbuf->f_blocks = MEMDB_BLOCKS;
-       stbuf->f_bfree = stbuf->f_bavail = stbuf->f_blocks - 
-               ((size + stbuf->f_bsize - 1)/stbuf->f_bsize);
-       stbuf->f_files = MEMDB_MAX_INODES;
-       stbuf->f_ffree = stbuf->f_files - files;
-
-       stbuf->f_namemax = 256;
-
-       return 0;
-}
-
-void 
-tree_entry_debug(memdb_tree_entry_t *te)
-{
-       g_return_if_fail(te != NULL);
-
-       // same as  tree_entry_print(), but use cfs_debug() instead of g_print()
-
-       cfs_debug("%016" PRIX64 " %c %016" PRIX64 " %016" PRIX64 " %08X %08X %08X %s\n",
-               te->inode, te->type == DT_DIR ? 'D' : 'R', te->parent, te->version,
-               te->writer, te->mtime, te->size, te->name); 
-}
-
-void 
-tree_entry_print(memdb_tree_entry_t *te)
-{
-       g_return_if_fail(te != NULL);
-
-       g_print("%016" PRIX64 " %c %016" PRIX64 " %016" PRIX64 " %08X %08X %08X %s\n",
-               te->inode, te->type == DT_DIR ? 'D' : 'R', te->parent, te->version,
-               te->writer, te->mtime, te->size, te->name); 
-}
-
-void 
-memdb_dump(memdb_t *memdb)
-{
-       g_return_if_fail(memdb != NULL);
-
-       g_mutex_lock (&memdb->mutex);
-       
-       GList *list = g_hash_table_get_values(memdb->index);
-      
-       list = g_list_sort(list, memdb_tree_compare);
-
-       g_print("%16s %c %16s %16s %8s %8s %8s %s\n",
-               "INODE", 'T', "PARENT", "VERSION", "WRITER", "MTIME", "SIZE", "NAME");
-
-       GList *l = list;
-       while (l) {
-               memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
-
-               tree_entry_print(te);
-
-               l = g_list_next(l);
-       }
-
-       g_list_free(list);
-
-       g_mutex_unlock (&memdb->mutex);
-}
-
-void 
-memdb_dump_index (memdb_index_t *idx)
-{
-       g_return_if_fail(idx != NULL);
-
-       g_print ("INDEX DUMP %016" PRIX64 "\n", idx->version);
-
-       int i;
-       for (i = 0; i < idx->size; i++) {
-               g_print ("%016" PRIX64 " %016" PRIX64 "%016" PRIX64 "%016" PRIX64 "%016" PRIX64 "\n", idx->entries[i].inode,
-                        *((guint64 *)idx->entries[i].digest),
-                        *((guint64 *)(idx->entries[i].digest + 8)),
-                        *((guint64 *)(idx->entries[i].digest + 16)),
-                        *((guint64 *)(idx->entries[i].digest + 24)));
-       }
-}
-
-memdb_index_t *
-memdb_index_copy(memdb_index_t *idx)
-{
-       g_return_val_if_fail(idx != NULL, NULL);
-
-       int bytes = sizeof(memdb_index_t) + idx->size*sizeof(memdb_index_extry_t);
-       if (idx->bytes != bytes) {
-               cfs_critical("memdb index contains wrong number of bytes");
-               return NULL;
-       }
-
-       memdb_index_t *copy = (memdb_index_t *)g_memdup2(idx, bytes);
-
-       return copy;
-}
-
-gboolean
-memdb_tree_entry_csum(
-       memdb_tree_entry_t *te,
-       guchar csum[32])
-{
-       g_return_val_if_fail(te != NULL, FALSE);
-       g_return_val_if_fail(csum != NULL, FALSE);
-
-       GChecksum *sha256 = g_checksum_new(G_CHECKSUM_SHA256); 
-
-       g_checksum_update(sha256, (unsigned char*)&te->inode, sizeof(te->inode));
-       g_checksum_update(sha256, (unsigned char*)&te->version, sizeof(te->version));
-       g_checksum_update(sha256, (unsigned char*)&te->writer, sizeof(te->writer));
-       g_checksum_update(sha256, (unsigned char*)&te->mtime, sizeof(te->mtime));
-       g_checksum_update(sha256, (unsigned char*)&te->size, sizeof(te->size));
-       g_checksum_update(sha256, (unsigned char*)&te->type, sizeof(te->type));
-       g_checksum_update(sha256, (unsigned char*)&te->parent, sizeof(te->parent));
-       g_checksum_update(sha256, (unsigned char*)te->name, strlen(te->name));
-
-       if (te->type == DT_REG && te->size) 
-               g_checksum_update(sha256, (unsigned char*)te->data.value, te->size);
-
-       size_t csum_len = 32;
-       g_checksum_get_digest(sha256, csum, &csum_len);
-       g_checksum_free(sha256);
-
-       return TRUE;
-} 
-
-gboolean 
-memdb_compute_checksum(
-       GHashTable *index, 
-       memdb_tree_entry_t *root, 
-       guchar *csum, 
-       size_t csum_len)
-{
-       g_return_val_if_fail(index != NULL, FALSE);
-       g_return_val_if_fail(root != NULL, FALSE);
-
-       GChecksum *sha256 = g_checksum_new(G_CHECKSUM_SHA256); 
-
-       GList *list = g_hash_table_get_values(index);
-      
-       list = g_list_sort(list, memdb_tree_compare);
-       
-       GList *l = list;
-       while (l) {
-               memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
-
-               g_checksum_update(sha256, (unsigned char*)&te->inode, sizeof(te->inode));
-               g_checksum_update(sha256, (unsigned char*)&te->version, sizeof(te->version));
-               g_checksum_update(sha256, (unsigned char*)&te->writer, sizeof(te->writer));
-               g_checksum_update(sha256, (unsigned char*)&te->mtime, sizeof(te->mtime));
-               g_checksum_update(sha256, (unsigned char*)&te->size, sizeof(te->size));
-               g_checksum_update(sha256, (unsigned char*)&te->type, sizeof(te->type));
-               g_checksum_update(sha256, (unsigned char*)&te->parent, sizeof(te->parent));
-               g_checksum_update(sha256, (unsigned char*)te->name, strlen(te->name));
-
-               if (te->type == DT_REG && te->size) 
-                       g_checksum_update(sha256, (unsigned char*)te->data.value, te->size);
-               
-               l = g_list_next(l);
-       }
-
-       g_list_free(list);
-
-       g_checksum_get_digest(sha256, csum, &csum_len);
-
-       cfs_debug("checksum: %s", g_checksum_get_string(sha256));
-
-       g_checksum_free(sha256);
-
-       return TRUE;
-}
-
-memdb_index_t *
-memdb_encode_index(
-       GHashTable *index, 
-       memdb_tree_entry_t *root)
-{
-       g_return_val_if_fail(index != NULL, NULL);
-       g_return_val_if_fail(root != NULL, NULL);
-
-       memdb_index_t *idx = NULL;
-
-       int count = g_hash_table_size(index);
-       if (!count) {
-               cfs_critical("memdb index has no entires");
-               return NULL;
-       }
-
-       int bytes = sizeof(memdb_index_t) + count*sizeof(memdb_index_extry_t);
-       idx = g_malloc0(bytes);
-
-       idx->size = count;
-       idx->bytes = bytes;
-       idx->version = root->version;
-       idx->mtime = root->mtime;
-       idx->writer = root->writer;
-
-       GChecksum *sha256 = g_checksum_new(G_CHECKSUM_SHA256); 
-
-       GList *list = g_hash_table_get_values(index);
-      
-       list = g_list_sort(list, memdb_tree_compare);
-       
-       int ind = 0;
-       GList *l = list;
-       while (l) {
-               memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
-
-               if (te->inode > idx->last_inode)
-                       idx->last_inode = te->inode;
-
-               idx->entries[ind].inode = te->inode;
-
-               g_checksum_reset (sha256);
-
-               g_checksum_update(sha256, (unsigned char*)&te->version, sizeof(te->version));
-               g_checksum_update(sha256, (unsigned char*)&te->writer, sizeof(te->writer));
-               g_checksum_update(sha256, (unsigned char*)&te->mtime, sizeof(te->mtime));
-               g_checksum_update(sha256, (unsigned char*)&te->size, sizeof(te->size));
-               g_checksum_update(sha256, (unsigned char*)&te->type, sizeof(te->type));
-               g_checksum_update(sha256, (unsigned char*)&te->parent, sizeof(te->parent));
-               g_checksum_update(sha256, (unsigned char*)te->name, strlen(te->name));
-
-               if (te->type == DT_REG && te->size) 
-                       g_checksum_update(sha256, (unsigned char*)te->data.value, te->size);
-
-               gsize len = 32;
-               g_checksum_get_digest(sha256, (guint8 *)idx->entries[ind].digest, &len);
-               
-               ind++;
-
-               l = g_list_next(l);
-       }
-
-       g_list_free(list);
-
-       g_checksum_free(sha256);
-
-       return idx;
-}
diff --git a/data/src/memdb.h b/data/src/memdb.h
deleted file mode 100644 (file)
index 2d7f54a..0000000
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
-  Copyright (C) 2010 - 2021 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_MEMDB_H_
-#define _PVE_MEMDB_H_
-
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <glib.h>
-#include <sys/statvfs.h>
-
-#define MEMDB_MAX_FILE_SIZE (1024 * 1024) // 1 MiB
-#define MEMDB_MAX_FSSIZE (128 * 1024 * 1024) // 128 MiB
-#define MEMDB_MAX_INODES (256 * 1024) // 256k
-
-#define MEMDB_BLOCKSIZE 4096
-#define MEMDB_BLOCKS ((MEMDB_MAX_FSSIZE + MEMDB_BLOCKSIZE - 1)/MEMDB_BLOCKSIZE)
-
-typedef struct memdb_tree_entry memdb_tree_entry_t;
-struct memdb_tree_entry {
-       guint64 parent;
-       guint64 inode;
-       guint64 version;
-       guint32 writer;
-       guint32 mtime;
-       guint32 size;
-       char type;       /* DT_REG .. regular file, DT_DIR ... directory */
-       union {
-               GHashTable *entries;
-               gpointer value;
-       } data;
-       char name[];
-};
-
-typedef struct {
-       guint64 inode;
-       char digest[32]; /* SHA256 digest */
-} memdb_index_extry_t;
-
-typedef struct {
-       guint64 version;
-       guint64 last_inode;
-       guint32 writer;
-       guint32 mtime;
-       guint32 size;  /* number of entries */
-       guint32 bytes; /* total bytes allocated */
-       memdb_index_extry_t entries[];
-} memdb_index_t;
-
-typedef struct db_backend db_backend_t;
-
-typedef struct {
-       char *path;
-       guint32 ltime;
-       guchar csum[32];
-} memdb_lock_info_t;
-
-typedef struct {
-       char *dbfilename;
-       gboolean errors;
-       memdb_tree_entry_t *root;
-       GHashTable *index; /* map version ==> memdb_tree_entry */
-       GHashTable *locks; /* contains memdb_lock_info_t */
-       GMutex mutex;
-       db_backend_t *bdb;
-} memdb_t;
-
-memdb_t *
-memdb_open(const char *dbfilename);
-
-void
-memdb_close(memdb_t *memdb);
-
-gboolean
-memdb_checkpoint(memdb_t *memdb);
-
-gboolean
-memdb_recreate_vmlist(memdb_t *memdb);
-
-gboolean
-memdb_lock_expired(
-       memdb_t *memdb,
-       const char *path,
-       const guchar csum[32]);
-
-void
-memdb_update_locks(memdb_t *memdb);
-
-int
-memdb_statfs(
-       memdb_t *memdb,
-       struct statvfs *stbuf);
-
-int
-memdb_mkdir(
-       memdb_t *memdb,
-       const char *path,
-       guint32 writer,
-       guint32 mtime);
-
-int
-memdb_mtime(
-       memdb_t *memdb,
-       const char *path,
-       guint32 writer,
-       guint32 mtime);
-
-GList *
-memdb_readdir(
-       memdb_t *memdb,
-       const char *path);
-
-void
-memdb_dirlist_free(GList *dirlist);
-
-void
-tree_entry_debug(memdb_tree_entry_t *te);
-
-void
-tree_entry_print(memdb_tree_entry_t *te);
-
-memdb_tree_entry_t *
-memdb_tree_entry_new(const char *name);
-
-memdb_tree_entry_t *
-memdb_tree_entry_copy(
-       memdb_tree_entry_t *te,
-       gboolean with_data);
-
-void
-memdb_tree_entry_free(memdb_tree_entry_t *te);
-
-int
-memdb_delete(
-       memdb_t *memdb,
-       const char *path,
-       guint32 writer,
-       guint32 mtime);
-
-int
-memdb_read(
-       memdb_t *memdb,
-       const char *path,
-       gpointer *data_ret);
-
-int
-memdb_read_nolock(
-        memdb_t *memdb,
-        const char *path,
-        gpointer *data_ret);
-
-int
-memdb_create(
-       memdb_t *memdb,
-       const char *path,
-       guint32 writer,
-       guint32 mtime);
-
-int
-memdb_write(
-       memdb_t *memdb,
-       const char *path,
-       guint32 writer,
-       guint32 mtime,
-       gconstpointer data,
-       size_t count,
-       off_t offset,
-       gboolean truncate);
-
-memdb_tree_entry_t *
-memdb_getattr(
-       memdb_t *memdb,
-       const char *path);
-
-int
-memdb_rename(
-       memdb_t *memdb,
-       const char *from,
-       const char *to,
-       guint32 writer,
-       guint32 mtime);
-
-void
-memdb_dump (
-       memdb_t *memdb);
-
-gboolean
-memdb_compute_checksum(
-       GHashTable *index,
-       memdb_tree_entry_t *root,
-       guchar *csum,
-       size_t csum_len);
-
-memdb_index_t *
-memdb_encode_index(
-       GHashTable *index,
-       memdb_tree_entry_t *root);
-
-void
-memdb_dump_index (memdb_index_t *idx);
-
-memdb_index_t *
-memdb_index_copy(memdb_index_t *idx);
-
-gboolean
-memdb_tree_entry_csum(
-       memdb_tree_entry_t *te,
-       guchar csum[32]);
-
-db_backend_t *
-bdb_backend_open(
-       const char *filename,
-       memdb_tree_entry_t *root,
-       GHashTable *index);
-
-void
-bdb_backend_close(db_backend_t *bdb);
-
-int
-bdb_backend_write(
-       db_backend_t *bdb,
-       guint64 inode,
-       guint64 parent,
-       guint64 version,
-       guint32 writer,
-       guint32 mtime,
-       guint32 size,
-       char type,
-       char *name,
-       gpointer value,
-       guint64 delete_inode);
-
-gboolean
-bdb_backend_commit_update(
-       memdb_t *memdb,
-       memdb_index_t *master,
-       memdb_index_t *slave,
-       GList *inodes);
-
-
-#endif /* _PVE_MEMDB_H_ */
diff --git a/data/src/pmxcfs.c b/data/src/pmxcfs.c
deleted file mode 100644 (file)
index d78a248..0000000
+++ /dev/null
@@ -1,1109 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <glib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mount.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <sys/file.h>
-#include <sys/types.h>
-#include <dirent.h>
-#include <sys/utsname.h>
-#include <grp.h>
-#include <netdb.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-
-#include <qb/qbdefs.h>
-#include <qb/qbutil.h>
-#include <qb/qblog.h>
-
-#include "cfs-utils.h"
-#include "cfs-plug.h"
-#include "cfs-plug-memdb.h"
-#include "status.h"
-#include "dcdb.h"
-#include "dfsm.h"
-#include "quorum.h"
-#include "confdb.h"
-#include "server.h"
-
-#define DBFILENAME VARLIBDIR "/config.db"
-#define LOCKFILE VARLIBDIR "/.pmxcfs.lockfile"
-#define RESTART_FLAG_FILE RUNDIR "/cfs-restart-flag"
-
-#define CFSDIR "/etc/pve"
-
-cfs_t cfs = {
-       .debug = 0,
-};
-
-static struct fuse *fuse = NULL;
-
-static cfs_plug_t *root_plug;
-
-static void glib_print_handler(const gchar *string)
-{
-       printf("%s", string);
-}
-
-static void glib_log_handler(const gchar *log_domain,
-                            GLogLevelFlags log_level,
-                            const gchar *message,
-                            gpointer user_data)
-{
-
-       cfs_log(log_domain, log_level, NULL, 0, NULL, "%s", message);
-}
-
-static gboolean write_pidfile(pid_t pid)
-{
-       char *strpid = g_strdup_printf("%d\n", pid);
-       gboolean res = atomic_write_file(CFS_PID_FN, strpid, strlen(strpid), 0644, getgid());
-       g_free(strpid);
-
-       return res;
-}
-
-static cfs_plug_t *find_plug(const char *path, char **sub)
-{
-       g_return_val_if_fail(root_plug != NULL, NULL);
-       g_return_val_if_fail(path != NULL, NULL);
-
-       while(*path == '/') path++;
-
-       cfs_debug("find_plug start %s", path);
-
-       char *tmppath = g_strdup(path);
-       char *subpath = tmppath;
-
-       cfs_plug_t *plug = root_plug->lookup_plug(root_plug, &subpath);
-
-       cfs_debug("find_plug end %s = %p (%s)", path, (void *) plug, subpath);
-
-       if (subpath && subpath[0])
-               *sub = g_strdup(subpath);
-
-       g_free(tmppath);
-
-       return plug;
-}
-
-void *cfs_fuse_init(struct fuse_conn_info *conn)
-{
-       return NULL;
-}
-
-static int cfs_fuse_getattr(const char *path, struct stat *stbuf)
-{
-       cfs_debug("enter cfs_fuse_getattr %s", path);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (plug && plug->ops && plug->ops->getattr) {
-               ret = plug->ops->getattr(plug, subpath ? subpath : "", stbuf);
-
-               stbuf->st_gid = cfs.gid;
-
-               if (path_is_private(path)) {
-                       stbuf->st_mode &= 0777700;
-               } else {
-                       if (S_ISDIR(stbuf->st_mode) || S_ISLNK(stbuf->st_mode)) {
-                               stbuf->st_mode &= 0777755; // access for other users
-                       } else {
-                               stbuf->st_mode &= 0777750; // no access for other users
-                       }
-               }
-       }
-
-       cfs_debug("leave cfs_fuse_getattr %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-
-}
-
-static int cfs_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
-                           off_t offset, struct fuse_file_info *fi)
-{
-       (void) offset;
-       (void) fi;
-
-       cfs_debug("enter cfs_fuse_readdir %s", path);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (!plug)
-               goto ret;
-
-       if (plug->ops && plug->ops->readdir)
-               ret = plug->ops->readdir(plug, subpath ? subpath : "", buf, filler, 0, fi);
-ret:
-       cfs_debug("leave cfs_fuse_readdir %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_chmod(const char *path, mode_t mode)
-{
-       int ret = -EPERM;
-
-       cfs_debug("enter cfs_fuse_chmod %s", path);
-
-       mode_t allowed_mode = (S_IRUSR | S_IWUSR);
-       if (!path_is_private(path))
-               allowed_mode |= (S_IRGRP);
-
-       // allow only setting our supported modes (0600 for priv, 0640 for rest)
-       // mode has additional bits set, which we ignore; see stat(2)
-       if ((mode & ALLPERMS) == allowed_mode)
-               ret = 0;
-
-       cfs_debug("leave cfs_fuse_chmod %s (%d) mode: %o", path, ret, (int)mode);
-
-       return ret;
-}
-
-static int cfs_fuse_chown(const char *path, uid_t user, gid_t group)
-{
-       int ret = -EPERM;
-
-       cfs_debug("enter cfs_fuse_chown %s", path);
-
-       // we get -1 if no change should be made
-       if ((user == 0 || user == -1) && (group == cfs.gid || group == -1))
-               ret = 0;
-
-       cfs_debug("leave cfs_fuse_chown %s (%d) (uid: %d; gid: %d)", path, ret, user, group);
-
-       return ret;
-}
-
-static int cfs_fuse_mkdir(const char *path, mode_t mode)
-{
-       cfs_debug("enter cfs_fuse_mkdir %s", path);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (!plug)
-               goto ret;
-
-       if (subpath && plug->ops && plug->ops->mkdir)
-               ret = plug->ops->mkdir(plug, subpath, mode);
-
- ret:
-       cfs_debug("leave cfs_fuse_mkdir %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_rmdir(const char *path)
-{
-       cfs_debug("enter cfs_fuse_rmdir %s", path);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (!plug)
-               goto ret;
-
-       if (subpath && plug->ops && plug->ops->rmdir)
-               ret = plug->ops->rmdir(plug, subpath);
-
- ret:
-       cfs_debug("leave cfs_fuse_rmdir %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_rename(const char *from, const char *to)
-{
-       cfs_debug("enter cfs_fuse_rename from %s to %s", from, to);
-
-       int ret = -EACCES;
-
-       char *sub_from = NULL;
-       cfs_plug_t *plug_from = find_plug(from, &sub_from);
-
-       char *sub_to = NULL;
-       cfs_plug_t *plug_to = find_plug(to, &sub_to);
-
-       if (!plug_from || !plug_to || plug_from != plug_to)
-               goto ret;
-
-       if (plug_from->ops && plug_from->ops->rename && sub_from && sub_to)
-               ret = plug_from->ops->rename(plug_from, sub_from, sub_to);
-
- ret:
-       cfs_debug("leave cfs_fuse_rename from %s to %s (%d)", from, to, ret);
-
-       if (sub_from)
-               g_free(sub_from);
-
-       if (sub_to)
-               g_free(sub_to);
-
-       return ret;
-}
-
-static int cfs_fuse_open(const char *path, struct fuse_file_info *fi)
-{
-       cfs_debug("enter cfs_fuse_open %s", path);
-
-       fi->direct_io = 1;
-       fi->keep_cache = 0;
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (plug && plug->ops) {
-               if ((subpath || !plug->ops->readdir) && plug->ops->open) {
-                       ret = plug->ops->open(plug, subpath ? subpath : "", fi);
-               }
-       }
-
-       cfs_debug("leave cfs_fuse_open %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_read(const char *path, char *buf, size_t size, off_t offset,
-                        struct fuse_file_info *fi)
-{
-       (void) fi;
-
-       cfs_debug("enter cfs_fuse_read %s %zu %jd", path, size, offset);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (plug && plug->ops) {
-               if ((subpath || !plug->ops->readdir) && plug->ops->read)
-                       ret = plug->ops->read(plug, subpath ? subpath : "", buf, size, offset, fi);
-       }
-
-       cfs_debug("leave cfs_fuse_read %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_write(const char *path, const char *buf, size_t size,
-                         off_t offset, struct fuse_file_info *fi)
-{
-       (void) fi;
-
-       cfs_debug("enter cfs_fuse_write %s %zu %jd", path, size, offset);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (plug && plug->ops) {
-               if ((subpath || !plug->ops->readdir) && plug->ops->write)
-               ret = plug->ops->write(plug, subpath ? subpath : "",
-                                      buf, size, offset, fi);
-       }
-
-       cfs_debug("leave cfs_fuse_write %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_truncate(const char *path, off_t size)
-{
-       cfs_debug("enter cfs_fuse_truncate %s %jd", path, size);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (plug && plug->ops) {
-               if ((subpath || !plug->ops->readdir) && plug->ops->truncate)
-                       ret = plug->ops->truncate(plug, subpath ? subpath : "", size);
-       }
-
-       cfs_debug("leave cfs_fuse_truncate %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_create(const char *path, mode_t mode, struct fuse_file_info *fi)
-{
-       cfs_debug("enter cfs_fuse_create %s", path);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (!plug)
-               goto ret;
-
-       if (subpath && plug->ops && plug->ops->create)
-               ret = plug->ops->create(plug, subpath, mode, fi);
-
-ret:
-       cfs_debug("leave cfs_fuse_create %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_unlink(const char *path)
-{
-       cfs_debug("enter cfs_fuse_unlink %s", path);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (!plug)
-               goto ret;
-
-       if (subpath && plug->ops && plug->ops->unlink)
-               ret = plug->ops->unlink(plug, subpath);
-
-ret:
-       cfs_debug("leave cfs_fuse_unlink %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_readlink(const char *path, char *buf, size_t max)
-{
-       cfs_debug("enter cfs_fuse_readlink %s", path);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (!plug)
-               goto ret;
-
-       if (plug->ops && plug->ops->readlink)
-               ret = plug->ops->readlink(plug, subpath ? subpath : "", buf, max);
-
-ret:
-       cfs_debug("leave cfs_fuse_readlink %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_utimens(const char *path, const struct timespec tv[2])
-{
-       cfs_debug("enter cfs_fuse_utimens %s", path);
-
-       int ret = -EACCES;
-
-       char *subpath = NULL;
-       cfs_plug_t *plug = find_plug(path, &subpath);
-
-       if (!plug)
-               goto ret;
-
-       if (plug->ops && plug->ops->utimens)
-               ret = plug->ops->utimens(plug, subpath ? subpath : "", tv);
-
-ret:
-       cfs_debug("leave cfs_fuse_utimens %s (%d)", path, ret);
-
-       if (subpath)
-               g_free(subpath);
-
-       return ret;
-}
-
-static int cfs_fuse_statfs(const char *path, struct statvfs *stbuf)
-{
-       g_return_val_if_fail(root_plug != NULL, PARAM_CHECK_ERRNO);
-
-       cfs_debug("enter cfs_fuse_statfs %s", path);
-
-       int ret = -EACCES;
-
-       if (root_plug && root_plug->ops && root_plug->ops->statfs)
-               ret = root_plug->ops->statfs(root_plug, "", stbuf);
-
-       return ret;
-}
-
-static struct fuse_operations fuse_ops = {
-       .getattr = cfs_fuse_getattr,
-       .readdir = cfs_fuse_readdir,
-       .mkdir = cfs_fuse_mkdir,
-       .rmdir = cfs_fuse_rmdir,
-       .rename = cfs_fuse_rename,
-       .open = cfs_fuse_open,
-       .read = cfs_fuse_read,
-       .write = cfs_fuse_write,
-       .truncate = cfs_fuse_truncate,
-       .create = cfs_fuse_create,
-       .unlink = cfs_fuse_unlink,
-       .readlink = cfs_fuse_readlink,
-       .utimens = cfs_fuse_utimens,
-       .statfs = cfs_fuse_statfs,
-       .init = cfs_fuse_init,
-       .chown = cfs_fuse_chown,
-       .chmod = cfs_fuse_chmod
-};
-
-static char *
-create_dot_version_cb(cfs_plug_t *plug)
-{
-       GString *outbuf = g_string_new(NULL);
-       char *data = NULL;
-
-       if (cfs_create_version_msg(outbuf) == 0) {
-               data = outbuf->str;
-               g_string_free(outbuf, FALSE);
-       } else {
-               g_string_free(outbuf, TRUE);
-       }
-
-       return data;
-}
-
-static char *
-create_dot_members_cb(cfs_plug_t *plug)
-{
-       GString *outbuf = g_string_new(NULL);
-       char *data = NULL;
-
-       if (cfs_create_memberlist_msg(outbuf) == 0) {
-               data = outbuf->str;
-               g_string_free(outbuf, FALSE);
-       } else {
-               g_string_free(outbuf, TRUE);
-       }
-
-       return data;
-}
-
-static char *
-create_dot_vmlist_cb(cfs_plug_t *plug)
-{
-       GString *outbuf = g_string_new(NULL);
-       char *data = NULL;
-
-       if (cfs_create_vmlist_msg(outbuf) == 0) {
-               data = outbuf->str;
-               g_string_free(outbuf, FALSE);
-       } else {
-               g_string_free(outbuf, TRUE);
-       }
-
-       return data;
-}
-
-static char *
-create_dot_rrd_cb(cfs_plug_t *plug)
-{
-       GString *outbuf = g_string_new(NULL);
-
-       cfs_rrd_dump(outbuf);
-       char *data = outbuf->str;
-       g_string_free(outbuf, FALSE);
-
-       return data;
-}
-
-static char *
-create_dot_clusterlog_cb(cfs_plug_t *plug)
-{
-       GString *outbuf = g_string_new(NULL);
-
-       cfs_cluster_log_dump(outbuf, NULL, 50);
-       char *data = outbuf->str;
-       g_string_free(outbuf, FALSE);
-
-       return data;
-}
-
-static char *
-read_debug_setting_cb(cfs_plug_t *plug)
-{
-       return g_strdup_printf("%d\n", !!cfs.debug);
-}
-
-static void
-my_qb_log_filter(struct qb_log_callsite *cs)
-{
-       int32_t priority = cfs.debug ? LOG_DEBUG : LOG_INFO;
-
-       if (qb_bit_is_set(cs->tags, QB_LOG_TAG_LIBQB_MSG_BIT)) {
-               if (cs->priority <= (cfs.debug ? priority : LOG_WARNING)) {
-                       qb_bit_set(cs->targets, QB_LOG_SYSLOG);
-               } else {
-                       qb_bit_clear(cs->targets, QB_LOG_SYSLOG);
-               }
-               if (cs->priority <= priority) {
-                       qb_bit_set(cs->targets, QB_LOG_STDERR);
-               } else {
-                       qb_bit_clear(cs->targets, QB_LOG_STDERR);
-               }
-       } else {
-               if (cs->priority <= priority) {
-                       qb_bit_set(cs->targets, QB_LOG_SYSLOG);
-                       qb_bit_set(cs->targets, QB_LOG_STDERR);
-               } else {
-                       qb_bit_clear(cs->targets, QB_LOG_SYSLOG);
-                       qb_bit_clear(cs->targets, QB_LOG_STDERR);
-               }
-       }
-}
-
-static void
-update_qb_log_settings(void)
-{
-       qb_log_filter_fn_set(my_qb_log_filter);
-
-       if (cfs.debug) {
-               qb_log_format_set(QB_LOG_SYSLOG, "[%g] %p: %b (%f:%l:%n)");
-               qb_log_format_set(QB_LOG_STDERR, "[%g] %p: %b (%f:%l:%n)");
-       } else {
-               qb_log_format_set(QB_LOG_SYSLOG, "[%g] %p: %b");
-               qb_log_format_set(QB_LOG_STDERR, "[%g] %p: %b");
-       }
-}
-
-static int
-write_debug_setting_cb(
-       cfs_plug_t *plug,
-       const char *buf,
-       size_t size)
-{
-       int res = -EIO;
-
-       if (size < 2)
-               return res;
-
-       if (strncmp(buf, "0\n", 2) == 0) {
-               if (cfs.debug) {
-                       cfs_message("disable debug mode");
-                       cfs.debug = 0;
-                       update_qb_log_settings();
-               }
-               return 2;
-       } else if (strncmp(buf, "1\n", 2) == 0) {
-               if (!cfs.debug) {
-                       cfs.debug = 1;
-                       update_qb_log_settings();
-                       cfs_message("enable debug mode");
-               }
-               return 2;
-       }
-
-       return res;
-}
-
-static void
-create_symlinks(cfs_plug_base_t *bplug, const char *nodename)
-{
-       g_return_if_fail(bplug != NULL);
-       g_return_if_fail(nodename != NULL);
-
-       char *lnktarget = g_strdup_printf("nodes/%s", nodename);
-       cfs_plug_link_t *lnk = cfs_plug_link_new("local", lnktarget);
-       g_free(lnktarget);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)lnk);
-
-       lnktarget = g_strdup_printf("nodes/%s/qemu-server", nodename);
-       lnk = cfs_plug_link_new("qemu-server", lnktarget);
-       g_free(lnktarget);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)lnk);
-
-       // FIXME: remove openvz stuff for 7.x
-       lnktarget = g_strdup_printf("nodes/%s/openvz", nodename);
-       lnk = cfs_plug_link_new("openvz", lnktarget);
-       g_free(lnktarget);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)lnk);
-
-       lnktarget = g_strdup_printf("nodes/%s/lxc", nodename);
-       lnk = cfs_plug_link_new("lxc", lnktarget);
-       g_free(lnktarget);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)lnk);
-
-       cfs_plug_func_t *fplug = cfs_plug_func_new(".version", 0440, create_dot_version_cb, NULL);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
-
-       fplug = cfs_plug_func_new(".members", 0440, create_dot_members_cb, NULL);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
-
-       fplug = cfs_plug_func_new(".vmlist", 0440, create_dot_vmlist_cb, NULL);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
-
-       fplug = cfs_plug_func_new(".rrd", 0440, create_dot_rrd_cb, NULL);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
-
-       fplug = cfs_plug_func_new(".clusterlog", 0440, create_dot_clusterlog_cb, NULL);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
-
-       fplug = cfs_plug_func_new(".debug", 0640, read_debug_setting_cb, write_debug_setting_cb);
-       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
-
-
-}
-
-static char *
-lookup_node_ip(const char *nodename)
-{
-       char buf[INET6_ADDRSTRLEN];
-       struct addrinfo *ainfo;
-       struct addrinfo ahints;
-       char *res = NULL;
-       memset(&ahints, 0, sizeof(ahints));
-
-       if (getaddrinfo(nodename, NULL, &ahints, &ainfo))
-               return NULL;
-
-       if (ainfo->ai_family == AF_INET) {
-               struct sockaddr_in *sa = (struct sockaddr_in *)ainfo->ai_addr;
-               inet_ntop(ainfo->ai_family, &sa->sin_addr, buf, sizeof(buf));
-               if (strncmp(buf, "127.", 4) != 0) {
-                       res = g_strdup(buf);
-               }
-       } else if (ainfo->ai_family == AF_INET6) {
-               struct sockaddr_in6 *sa = (struct sockaddr_in6 *)ainfo->ai_addr;
-               inet_ntop(ainfo->ai_family, &sa->sin6_addr, buf, sizeof(buf));
-               if (strcmp(buf, "::1") != 0) {
-                       res = g_strdup(buf);
-               }
-       }
-
-       freeaddrinfo(ainfo);
-
-       return res;
-}
-
-static const char*
-log_tags_stringify(uint32_t tags) {
-       if (qb_bit_is_set(tags, QB_LOG_TAG_LIBQB_MSG_BIT)) {
-               return "libqb";
-       } else {
-               GQuark quark = tags;
-               const char *domain = g_quark_to_string(quark);
-               if (domain != NULL) {
-                       return domain;
-               } else {
-                       return "main";
-               }
-       }
-}
-
-int main(int argc, char *argv[])
-{
-       int ret = -1;
-       int lockfd = -1;
-       int pipefd[2];
-
-       gboolean foreground = FALSE;
-       gboolean force_local_mode = FALSE;
-       gboolean wrote_pidfile = FALSE;
-       memdb_t *memdb = NULL;
-       dfsm_t *dcdb = NULL;
-       dfsm_t *status_fsm = NULL;
-
-       qb_log_init("pmxcfs", LOG_DAEMON, LOG_DEBUG);
-       /* remove default filter */
-       qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_REMOVE,
-                         QB_LOG_FILTER_FILE, "*", LOG_DEBUG);
-
-       qb_log_tags_stringify_fn_set(log_tags_stringify);
-
-       qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
-
-       update_qb_log_settings();
-
-       g_set_print_handler(glib_print_handler);
-       g_set_printerr_handler(glib_print_handler);
-       g_log_set_default_handler(glib_log_handler, NULL);
-
-       GOptionContext *context;
-
-       GOptionEntry entries[] = {
-               { "debug", 'd', 0, G_OPTION_ARG_NONE, &cfs.debug, "Turn on debug messages", NULL },
-               { "foreground", 'f', 0, G_OPTION_ARG_NONE, &foreground, "Do not daemonize server", NULL },
-               { "local", 'l', 0, G_OPTION_ARG_NONE, &force_local_mode,
-                 "Force local mode (ignore corosync.conf, force quorum)", NULL },
-               { NULL },
-       };
-
-       context = g_option_context_new ("");
-       g_option_context_add_main_entries (context, entries, NULL);
-
-       GError *err = NULL;
-       if (!g_option_context_parse (context, &argc, &argv, &err))
-       {
-               cfs_critical("option parsing failed: %s", err->message);
-               g_error_free (err);
-               qb_log_fini();
-               exit (1);
-       }
-       g_option_context_free(context);
-
-       if (optind < argc) {
-               cfs_critical("too many arguments");
-               qb_log_fini();
-               exit(-1);
-       }
-
-       if (cfs.debug) {
-               update_qb_log_settings();
-       }
-
-       struct utsname utsname;
-       if (uname(&utsname) != 0) {
-               cfs_critical("Unable to read local node name");
-               qb_log_fini();
-               exit (-1);
-       }
-
-       char *dot = strchr(utsname.nodename, '.');
-       if (dot)
-               *dot = 0;
-
-       cfs.nodename = g_strdup(utsname.nodename);
-
-       if (!(cfs.ip = lookup_node_ip(cfs.nodename))) {
-               cfs_critical("Unable to get local IP address");
-               qb_log_fini();
-               exit(-1);
-       }
-
-       struct group *www_data = getgrnam("www-data");
-       if (!www_data) {
-               cfs_critical("Unable to get www-data group ID");
-               qb_log_fini();
-               exit (-1);
-       }
-       cfs.gid = www_data->gr_gid;
-
-       umask(027);
-
-       mkdir(VARLIBDIR, 0755);
-       mkdir(RUNDIR, 0755);
-       chown(RUNDIR, 0, cfs.gid);
-
-       if ((lockfd = open(LOCKFILE, O_RDWR|O_CREAT|O_APPEND, 0600)) == -1) {
-               cfs_critical("unable to create lock '%s': %s", LOCKFILE, strerror (errno));
-               goto err;
-       }
-
-       for (int i = 10; i >= 0; i--) {
-               if (flock(lockfd, LOCK_EX|LOCK_NB) != 0) {
-                       if (!i) {
-                               cfs_critical("unable to acquire pmxcfs lock: %s", strerror (errno));
-                               goto err;
-                       }
-                       if (i == 10)
-                               cfs_message("unable to acquire pmxcfs lock - trying again");
-
-                       sleep(1);
-               }
-       }
-
-       cfs_status_init();
-
-       gboolean create = !g_file_test(DBFILENAME, G_FILE_TEST_EXISTS);
-
-       if (!(memdb = memdb_open (DBFILENAME))) {
-               cfs_critical("memdb_open failed - unable to open database '%s'", DBFILENAME);
-               goto err;
-       }
-
-       // automatically import corosync.conf from host
-       if (create && !force_local_mode) {
-               char *cdata = NULL;
-               gsize clen = 0;
-               if (g_file_get_contents(HOST_CLUSTER_CONF_FN, &cdata, &clen, NULL)) {
-
-                       guint32 mtime = time(NULL);
-
-                       memdb_create(memdb, "/corosync.conf", 0, mtime);
-                       if (memdb_write(memdb, "/corosync.conf", 0, mtime, cdata, clen, 0, 1) < 0) {
-                               cfs_critical("memdb_write failed - unable to import corosync.conf");
-                               goto err;
-                       }
-               }
-       }
-
-       // does corosync.conf exist?
-       gpointer conf_data = NULL;
-       int len = memdb_read(memdb, "corosync.conf", &conf_data);
-       if (len >= 0) {
-               if (force_local_mode) {
-                       cfs_message("forcing local mode (although corosync.conf exists)");
-                       cfs_set_quorate(1, TRUE);
-               } else {
-                       if (!(dcdb = dcdb_new(memdb)))
-                               goto err;
-                       dcdb_sync_corosync_conf(memdb, 1);
-               }
-       } else {
-               cfs_debug("using local mode (corosync.conf does not exist)");
-               cfs_set_quorate(1, TRUE);
-       }
-       if (conf_data) g_free(conf_data);
-
-       cfs_plug_memdb_t *config = cfs_plug_memdb_new("memdb", memdb, dcdb);
-
-       cfs_plug_base_t *bplug = cfs_plug_base_new("", (cfs_plug_t *)config);
-
-       create_symlinks(bplug, cfs.nodename);
-
-       root_plug = (cfs_plug_t *)bplug;
-
-       umount2(CFSDIR, MNT_FORCE);
-
-       mkdir(CFSDIR, 0755);
-
-       char *fa[] = { "-f", "-odefault_permissions", "-oallow_other", NULL};
-
-       struct fuse_args fuse_args = FUSE_ARGS_INIT(sizeof (fa)/sizeof(gpointer) - 1, fa);
-
-       struct fuse_chan *fuse_chan = fuse_mount(CFSDIR, &fuse_args);
-       if (!fuse_chan) {
-               cfs_critical("fuse_mount error: %s", strerror(errno));
-               goto err;
-       }
-
-       if (!(fuse = fuse_new(fuse_chan, &fuse_args, &fuse_ops, sizeof(fuse_ops), NULL))) {
-               cfs_critical("fuse_new error: %s", strerror(errno));
-               goto err;
-       }
-
-       fuse_set_signal_handlers(fuse_get_session(fuse));
-
-       if (!foreground) {
-               if (pipe(pipefd) == -1) {
-                       cfs_critical("pipe error: %s", strerror(errno));
-                       goto err;
-               }
-
-               pid_t cpid = fork();
-
-               if (cpid == -1) {
-                       cfs_critical("failed to daemonize program - %s", strerror (errno));
-                       goto err;
-               } else if (cpid) {
-                       int readbytes, errno_tmp;
-                       char readbuffer;
-                       close(pipefd[1]);
-                       readbytes = read(pipefd[0], &readbuffer, sizeof(readbuffer));
-                       errno_tmp = errno;
-                       close(pipefd[0]);
-                       if (readbytes == -1) {
-                               cfs_critical("read error: %s", strerror(errno_tmp));
-                               kill(cpid, SIGKILL);
-                               goto err;
-                       } else if (readbytes != 1 || readbuffer != '1') {
-                               cfs_critical("child failed to send '1'");
-                               kill(cpid, SIGKILL);
-                               goto err;
-                       }
-                       /* child finished starting up */
-                       write_pidfile(cpid);
-                       qb_log_fini();
-                       _exit (0);
-               } else {
-                       int nullfd;
-                       close(pipefd[0]);
-
-                       chroot("/");
-
-                       if ((nullfd = open("/dev/null", O_RDWR, 0)) != -1) {
-                               dup2(nullfd, 0);
-                               dup2(nullfd, 1);
-                               dup2(nullfd, 2);
-                               if (nullfd > 2)
-                                       close (nullfd);
-                       }
-
-                       // do not print to the console after this point
-                       qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE);
-
-                       setsid();
-               }
-       } else {
-               write_pidfile(getpid());
-       }
-
-       wrote_pidfile = TRUE;
-
-       cfs_loop_t *corosync_loop = cfs_loop_new(fuse);
-
-       cfs_service_t *service_quorum = NULL;
-       cfs_service_t *service_confdb = NULL;
-       cfs_service_t *service_dcdb = NULL;
-       cfs_service_t *service_status = NULL;
-
-       if (dcdb) {
-
-               service_quorum = service_quorum_new();
-
-               cfs_loop_add_service(corosync_loop, service_quorum, QB_LOOP_HIGH);
-
-               service_confdb = service_confdb_new();
-
-               cfs_loop_add_service(corosync_loop, service_confdb, QB_LOOP_MED);
-
-               service_dcdb = service_dfsm_new(dcdb);
-               cfs_service_set_timer(service_dcdb, DCDB_VERIFY_TIME);
-
-               cfs_loop_add_service(corosync_loop, service_dcdb, QB_LOOP_MED);
-
-               status_fsm = cfs_status_dfsm_new();
-               service_status = service_dfsm_new(status_fsm);
-
-               cfs_loop_add_service(corosync_loop, service_status, QB_LOOP_LOW);
-
-       }
-
-       cfs_loop_start_worker(corosync_loop);
-
-       server_start(memdb);
-
-       unlink(RESTART_FLAG_FILE);
-
-       if (!foreground) {
-               /* finished starting up, signaling parent */
-               write(pipefd[1], "1", 1);
-               close(pipefd[1]);
-       }
-
-       ret = fuse_loop_mt(fuse);
-
-       open(RESTART_FLAG_FILE, O_CREAT|O_NOCTTY|O_NONBLOCK, S_IRUSR | S_IRGRP);
-       chown(RESTART_FLAG_FILE, 0, cfs.gid);
-
-       cfs_message("teardown filesystem");
-
-       server_stop();
-
-       fuse_unmount(CFSDIR, fuse_chan);
-
-       fuse_destroy(fuse);
-
-       cfs_debug("set stop event loop flag");
-
-       cfs_loop_stop_worker(corosync_loop);
-
-       cfs_loop_destroy(corosync_loop);
-
-       cfs_debug("worker finished");
-
-       if (service_dcdb)
-               service_dfsm_destroy(service_dcdb);
-
-       if (service_confdb)
-               service_confdb_destroy(service_confdb);
-
-       if (service_quorum)
-               service_quorum_destroy(service_quorum);
-
-       if (service_status)
-               service_dfsm_destroy(service_status);
-
- ret:
-
-       if (status_fsm)
-               dfsm_destroy(status_fsm);
-
-       if (dcdb)
-               dfsm_destroy(dcdb);
-
-       if (memdb)
-               memdb_close(memdb);
-
-       if (wrote_pidfile)
-               unlink(CFS_PID_FN);
-
-       cfs_message("exit proxmox configuration filesystem (%d)", ret);
-
-       cfs_status_cleanup();
-
-       qb_log_fini();
-
-       exit(ret);
-
- err:
-       goto ret;
-}
diff --git a/data/src/quorum.c b/data/src/quorum.c
deleted file mode 100644 (file)
index 9df0e90..0000000
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#define G_LOG_DOMAIN "quorum"
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <glib.h>
-
-#include <corosync/quorum.h>
-
-#include "cfs-utils.h"
-#include "loop.h"
-#include "status.h"
-
-typedef struct {
-       quorum_handle_t handle;
-} qs_private_t;
-
-static void quorum_notification_fn(
-       quorum_handle_t handle,
-       uint32_t quorate,
-       uint64_t ring_id,
-       uint32_t view_list_entries,
-       uint32_t *view_list)
-{
-       cs_error_t result;
-
-       cfs_debug("quorum notification called, quorate = %d, "
-                 "number of nodes = %d", quorate, view_list_entries);
-
-       qs_private_t *private = NULL;
-
-       result = quorum_context_get(handle, (gconstpointer *)&private);
-       if (result != CS_OK || !private) {
-               cfs_critical("quorum_context_get error: %d (%p)", result, (void *) private);
-               return;
-       }
-
-       cfs_set_quorate(quorate, FALSE);
-}
-
-static quorum_callbacks_t quorum_callbacks = {
-       .quorum_notify_fn = quorum_notification_fn,
-};
-
-static gboolean service_quorum_finalize(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_val_if_fail(service != NULL, FALSE);
-       g_return_val_if_fail(context != NULL, FALSE);
-
-       qs_private_t *private = (qs_private_t *)context;
-       quorum_handle_t handle = private->handle;
-
-       cs_error_t result;
-
-       cfs_set_quorate(0, TRUE);
-
-       result = quorum_finalize(handle);
-       private->handle = 0;
-       if (result != CS_OK) {
-               cfs_critical("quorum_finalize failed: %d", result);
-               return FALSE;
-       }
-
-       return TRUE;
-}
-
-static int service_quorum_initialize(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_val_if_fail(service != NULL, FALSE);
-       g_return_val_if_fail(context != NULL, FALSE);
-
-       qs_private_t *private = (qs_private_t *)context;
-
-       quorum_handle_t handle = private->handle;
-       cs_error_t result;
-
-       if (!private->handle) {
-            
-                uint32_t quorum_type;
-            
-                result = quorum_initialize(&handle, &quorum_callbacks, &quorum_type);
-                if (result != CS_OK) {
-                        cfs_critical("quorum_initialize failed: %d", result);
-                       goto err_reset_handle;
-               }
-
-               if (quorum_type != QUORUM_SET) {
-                       cfs_critical("quorum_initialize returned wrong quorum_type: %d", quorum_type);
-                       goto err_finalize;
-               }
-
-               result = quorum_context_set(handle, private);
-               if (result != CS_OK) {
-                       cfs_critical("quorum_context_set failed: %d", result);
-                       goto err_finalize;
-               }
-
-               private->handle = handle;
-       }
-       
-
-       result = quorum_trackstart(handle, CS_TRACK_CHANGES);
-       if (result == CS_ERR_LIBRARY || result == CS_ERR_BAD_HANDLE) {
-               cfs_critical("quorum_trackstart failed: %d - closing handle", result);
-               goto err_finalize;
-       } else if (result != CS_OK) {
-               cfs_critical("quorum_trackstart failed: %d - trying again", result);
-               return -1;
-       }
-       
-       int quorum_fd = -1;
-       if ((result = quorum_fd_get(handle, &quorum_fd)) != CS_OK) {
-               cfs_critical("quorum_fd_get failed %d - trying again", result);
-               return -1;
-       }
-
-       return quorum_fd;
-
- err_finalize:
-       cfs_set_quorate(0, FALSE);
-       quorum_finalize(handle);
- err_reset_handle:
-       private->handle = 0;
-       return -1;
-}
-
-static gboolean service_quorum_dispatch(
-       cfs_service_t *service,
-       gpointer context)
-{
-       g_return_val_if_fail(service != NULL, FALSE);
-       g_return_val_if_fail(context != NULL, FALSE);
-
-       qs_private_t *private = (qs_private_t *)context;
-       quorum_handle_t handle =  private->handle;
-
-       cs_error_t result;
-
-       int retries = 0;
-loop:
-       result = quorum_dispatch(handle, CS_DISPATCH_ALL);
-       if (result == CS_ERR_TRY_AGAIN) {
-               usleep(100000);
-               ++retries;
-               if ((retries % 100) == 0)
-                       cfs_message("quorum_dispatch retry %d", retries);
-               goto loop;
-       }
-
-
-       if (result == CS_OK || result == CS_ERR_TRY_AGAIN)
-               return TRUE;
-
-       cfs_critical("quorum_dispatch failed: %d", result);
-
-       cfs_set_quorate(0, FALSE);
-       quorum_finalize(handle);
-       private->handle = 0;
-       return FALSE;
-}
-
-static cfs_service_callbacks_t cfs_quorum_callbacks = {
-       .cfs_service_initialize_fn =  service_quorum_initialize,
-       .cfs_service_finalize_fn = service_quorum_finalize,
-       .cfs_service_dispatch_fn = service_quorum_dispatch,
-};
-
-cfs_service_t *service_quorum_new(void)
-{
-       cfs_service_t *service;
-
-       qs_private_t *private = g_new0(qs_private_t, 1);
-       if (!private)
-               return NULL;
-
-       service = cfs_service_new(&cfs_quorum_callbacks, G_LOG_DOMAIN, private); 
-
-       return service;
-}
-
-void service_quorum_destroy(cfs_service_t *service) 
-{
-       g_return_if_fail(service != NULL);
-
-       qs_private_t *private = 
-               (qs_private_t *)cfs_service_get_context(service);
-
-       g_free(private);
-       g_free(service);
-}
diff --git a/data/src/quorum.h b/data/src/quorum.h
deleted file mode 100644 (file)
index 9ee4d52..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_QUORUM_H_
-#define _PVE_QUORUM_H_
-
-#include <glib.h>
-
-#include "loop.h"
-
-cfs_service_t *service_quorum_new(void);
-
-void service_quorum_destroy(
-       cfs_service_t *service);
-
-#endif /* _PVE_QUORUM_H_ */
diff --git a/data/src/server.c b/data/src/server.c
deleted file mode 100644 (file)
index 80f838b..0000000
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#define G_LOG_DOMAIN "ipcs"
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdint.h>
-#include <errno.h>
-#include <string.h>
-#include <sys/syslog.h>
-#include <sys/uio.h>
-
-#include <qb/qbdefs.h>
-#include <qb/qbutil.h>
-#include <qb/qbloop.h>
-#include <qb/qbipcs.h>
-
-#include <glib.h>
-
-#include "cfs-utils.h"
-#include "cfs-ipc-ops.h"
-#include "status.h"
-#include "memdb.h"
-#include "logger.h"
-
-static GThread *worker;
-static qb_loop_t *loop;
-static qb_ipcs_service_t* s1;
-static GString *outbuf;
-static memdb_t *memdb;
-
-static int server_started = 0;   /* protect with server_started_mutex */
-static int terminate_server = 0; /* protect with server_started_mutex */
-static GCond server_started_cond;
-static GCond server_stopped_cond;
-static GMutex server_started_mutex;
-
-
-typedef struct {
-       struct qb_ipc_request_header req_header;
-       char name[256];
-} cfs_status_update_request_header_t;
-
-typedef struct {
-       struct qb_ipc_request_header req_header;
-       char name[256];
-       char nodename[256];
-} cfs_status_get_request_header_t;
-
-typedef struct {
-       struct qb_ipc_request_header req_header;
-       uint8_t priority;
-       uint8_t ident_len;
-       uint8_t tag_len;
-       char data[];
-} cfs_log_msg_request_header_t;
-
-typedef struct {
-       struct qb_ipc_request_header req_header;
-       uint32_t max_entries;
-       uint32_t res1;
-       uint32_t res2;
-       uint32_t res3;
-} cfs_log_get_request_header_t;
-
-typedef struct {
-       struct qb_ipc_request_header req_header;
-       uint32_t vmid;
-       char property[];
-} cfs_guest_config_propery_get_request_header_t;
-
-typedef struct {
-       struct qb_ipc_request_header req_header;
-       uint32_t vmid;
-       uint8_t num_props;
-       char props[]; /* list of \0 terminated properties */
-} cfs_guest_config_properties_get_request_header_t;
-
-typedef struct {
-       struct qb_ipc_request_header req_header;
-       char token[];
-} cfs_verify_token_request_header_t;
-
-struct s1_context {
-       int32_t client_pid;
-       uid_t uid;
-       gid_t gid;      
-       gboolean read_only;
-};
-static int32_t s1_connection_accept_fn(
-       qb_ipcs_connection_t *c, 
-       uid_t uid, 
-       gid_t gid)
-{
-       if ((uid == 0 && gid == 0) || (gid == cfs.gid)) {
-               cfs_debug("authenticated connection %d/%d", uid, gid);
-               struct s1_context *ctx = g_new0(struct s1_context, 1);
-               ctx->uid = uid;
-               ctx->gid = gid;
-               ctx->read_only = (gid == cfs.gid);
-
-               struct qb_ipcs_connection_stats stats;
-               qb_ipcs_connection_stats_get(c, &stats, QB_FALSE);
-               ctx->client_pid = stats.client_pid;
-
-               qb_ipcs_context_set(c, ctx);
-               return 0;
-       }
-       cfs_critical("connection from bad user %d! - rejected", uid);
-       return 1;
-}
-
-static void s1_connection_created_fn(
-       qb_ipcs_connection_t *c)
-{
-       struct qb_ipcs_stats srv_stats;
-
-       qb_ipcs_stats_get(s1, &srv_stats, QB_FALSE);
-
-       cfs_debug("Connection created > active:%d > closed:%d",
-                   srv_stats.active_connections,
-                   srv_stats.closed_connections);
-}
-
-static void s1_connection_destroyed_fn(
-       qb_ipcs_connection_t *c)
-{
-       cfs_debug("connection about to be freed");
-       
-       gpointer ctx;
-       if ((ctx = qb_ipcs_context_get(c)))
-               g_free(ctx);
-
-}
-
-static int32_t s1_connection_closed_fn(
-       qb_ipcs_connection_t *c)
-{
-       struct qb_ipcs_connection_stats stats;
-
-       qb_ipcs_connection_stats_get(c, &stats, QB_FALSE);
-
-       cfs_debug("Connection to pid:%d destroyed", stats.client_pid);
-
-       return 0;
-}
-
-static int32_t s1_msg_process_fn(
-       qb_ipcs_connection_t *c,
-       void *data,
-       size_t size)
-{
-       struct qb_ipc_request_header *req_pt = 
-               (struct qb_ipc_request_header *)data;
-
-       struct s1_context *ctx = (struct s1_context *)qb_ipcs_context_get(c);
-
-       if (!ctx) {
-               cfs_critical("qb_ipcs_context_get failed");
-               qb_ipcs_disconnect(c);
-               return 0;
-       }
-
-       int32_t request_id __attribute__ ((aligned(8))) = req_pt->id;
-       int32_t request_size __attribute__ ((aligned(8))) = req_pt->size;
-       cfs_debug("process msg:%d, size:%d", request_id, request_size);
-
-       char *resp = NULL;
-
-       g_string_truncate(outbuf, 0);
-
-       int32_t result = -ECHRNG;
-       if (request_id == CFS_IPC_GET_FS_VERSION) {
-
-               if (request_size != sizeof(struct qb_ipc_request_header)) {
-                       result = -EINVAL;
-               } else {
-                       result = cfs_create_version_msg(outbuf);
-               }
-
-       } else if (request_id == CFS_IPC_GET_CLUSTER_INFO) {
-
-               if (request_size != sizeof(struct qb_ipc_request_header)) {
-                       result = -EINVAL;
-               } else {
-                       result = cfs_create_memberlist_msg(outbuf);
-               }
-
-       } else if (request_id == CFS_IPC_GET_GUEST_LIST) {
-               
-               if (request_size != sizeof(struct qb_ipc_request_header)) {
-                       result = -EINVAL;
-               } else {
-                       result = cfs_create_vmlist_msg(outbuf);
-               }
-       } else if (request_id == CFS_IPC_SET_STATUS) {
-
-               cfs_status_update_request_header_t *rh = 
-                       (cfs_status_update_request_header_t *)data;
-
-               int datasize = request_size - sizeof(cfs_status_update_request_header_t);
-
-               if (ctx->read_only) {
-                       result = -EPERM;
-               } else if (datasize < 0) {
-                       result = -EINVAL;
-               } else {        
-                       /* make sure name is 0 terminated */
-                       rh->name[sizeof(rh->name) - 1] = 0;
-
-                       char *dataptr = (char*) data + sizeof(cfs_status_update_request_header_t);
-
-                       result = cfs_status_set(rh->name, dataptr, datasize);
-               }
-       } else if (request_id == CFS_IPC_GET_STATUS) {
-
-               cfs_status_get_request_header_t *rh =
-                       (cfs_status_get_request_header_t *)data;
-
-               int datasize = request_size - sizeof(cfs_status_get_request_header_t);
-
-               if (datasize < 0) {
-                       result = -EINVAL;
-               } else {        
-                       /* make sure all names are 0 terminated */
-                       rh->name[sizeof(rh->name) - 1] = 0;
-                       rh->nodename[sizeof(rh->nodename) - 1] = 0;
-
-                       result = cfs_create_status_msg(outbuf, rh->nodename, rh->name);
-               }
-       } else if (request_id == CFS_IPC_GET_CONFIG) {
-
-               int pathlen = request_size - sizeof(struct qb_ipc_request_header);
-
-               if (pathlen <= 0) {
-                       result = -EINVAL;
-               } else {
-                       /* make sure path is 0 terminated */
-                       ((char *)data)[request_size - 1] = 0;
-                       char *path = (char*) data + sizeof(struct qb_ipc_request_header);
-
-                       if (ctx->read_only &&  path_is_private(path)) {
-                               result = -EPERM;
-                       } else {
-                               gpointer tmp = NULL;
-                               result = memdb_read(memdb, path, &tmp);
-                               if (result > 0) {
-                                       g_string_append_len(outbuf, tmp, result);
-                                       g_free(tmp);
-                               }
-                       }
-               }                       
-       } else if (request_id == CFS_IPC_LOG_CLUSTER_MSG) {
-
-               cfs_log_msg_request_header_t *rh = 
-                       (cfs_log_msg_request_header_t *)data;
-
-               int datasize = request_size - G_STRUCT_OFFSET(cfs_log_msg_request_header_t, data);
-               int msg_len = datasize - rh->ident_len - rh->tag_len;
-
-               if (ctx->read_only) {
-                       result = -EPERM;
-               } else if (msg_len  < 1) {
-                       result = -EINVAL;
-               } else {
-                       char *msg = rh->data;
-                       if ((msg[rh->ident_len - 1] == 0) &&
-                           (msg[rh->ident_len + rh->tag_len - 1] == 0) &&
-                           (((char *)data)[request_size] == 0)) {
-
-                               char *ident = msg;
-                               char *tag = msg + rh->ident_len;
-                               msg = msg + rh->ident_len + rh->tag_len;
-
-                               time_t ctime = time(NULL);
-                               clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
-                               if (clog_pack(entry, cfs.nodename, ident, tag, ctx->client_pid,
-                                             ctime, rh->priority, msg)) {
-                                       cfs_cluster_log(entry);
-                               }
-
-                               result = 0;
-
-                       } else {
-                               result = -EINVAL;
-                       }
-               }
-       } else if (request_id == CFS_IPC_GET_CLUSTER_LOG) {
-
-               cfs_log_get_request_header_t *rh = 
-                       (cfs_log_get_request_header_t *)data;
-
-               int userlen = request_size - sizeof(cfs_log_get_request_header_t);
-
-               if (userlen <= 0) {
-                       result = -EINVAL;
-               } else {
-                       /* make sure user string is 0 terminated */
-                       ((char *)data)[request_size - 1] = 0;
-                       char *user = (char*) data + sizeof(cfs_log_get_request_header_t);
-
-                       uint32_t max = rh->max_entries ?  rh->max_entries : 50;
-                       cfs_cluster_log_dump(outbuf, user, max);
-                       result = 0;
-               }
-       } else if (request_id == CFS_IPC_GET_RRD_DUMP) {
-       
-               if (request_size != sizeof(struct qb_ipc_request_header)) {
-                       result = -EINVAL;
-               } else {
-                       cfs_rrd_dump(outbuf);
-                       result = 0;
-               }
-       } else if (request_id == CFS_IPC_GET_GUEST_CONFIG_PROPERTY) {
-
-               cfs_guest_config_propery_get_request_header_t *rh =
-                       (cfs_guest_config_propery_get_request_header_t *) data;
-
-               int proplen = request_size - G_STRUCT_OFFSET(cfs_guest_config_propery_get_request_header_t, property);
-
-               result = 0;
-               if (rh->vmid < 100 && rh->vmid != 0) {
-                       cfs_debug("vmid out of range %u", rh->vmid);
-                       result = -EINVAL;
-               } else if (rh->vmid >= 100 && !vmlist_vm_exists(rh->vmid)) {
-                       result = -ENOENT;
-               } else if (proplen <= 0) {
-                       cfs_debug("proplen <= 0, %d", proplen);
-                       result = -EINVAL;
-               } else {
-                       ((char *)data)[request_size - 1] = 0; // ensure property is 0 terminated
-
-                       cfs_debug("cfs_get_guest_config_property: basic valid checked, do request");
-
-                       result = cfs_create_guest_conf_property_msg(outbuf, memdb, rh->property, rh->vmid);
-               }
-       } else if (request_id == CFS_IPC_GET_GUEST_CONFIG_PROPERTIES) {
-
-               cfs_guest_config_properties_get_request_header_t *rh =
-                       (cfs_guest_config_properties_get_request_header_t *) data;
-
-               size_t remaining = request_size - G_STRUCT_OFFSET(cfs_guest_config_properties_get_request_header_t, props);
-
-               result = 0;
-               if (rh->vmid < 100 && rh->vmid != 0) {
-                       cfs_debug("vmid out of range %u", rh->vmid);
-                       result = -EINVAL;
-               } else if (rh->vmid >= 100 && !vmlist_vm_exists(rh->vmid)) {
-                       result = -ENOENT;
-               } else if (rh->num_props == 0) {
-                       cfs_debug("num_props == 0");
-                       result = -EINVAL;
-               } else if (remaining <= 1) {
-                       cfs_debug("property length <= 1, %ld", remaining);
-                       result = -EINVAL;
-               } else {
-                       const char **properties = malloc(sizeof(char*) * rh->num_props);
-                       char *current = (rh->props);
-                       for (uint8_t i = 0; i < rh->num_props; i++) {
-                           size_t proplen = strnlen(current, remaining);
-                           if (proplen == 0) {
-                               cfs_debug("property length 0");
-                               result = -EINVAL;
-                               break;
-                           }
-                           if (proplen == remaining || current[proplen] != '\0') {
-                               cfs_debug("property not \\0 terminated");
-                               result = -EINVAL;
-                               break;
-                           }
-                           if (current[0] < 'a' || current[0] > 'z') {
-                               cfs_debug("property does not start with [a-z]");
-                               result = -EINVAL;
-                               break;
-                           }
-                           properties[i] = current;
-                           remaining -= (proplen + 1);
-                           current += proplen + 1;
-                       }
-
-                       if (remaining != 0) {
-                           cfs_debug("leftover data after parsing %u properties", rh->num_props);
-                           result = -EINVAL;
-                       }
-
-                       if (result == 0) {
-                           cfs_debug("cfs_get_guest_config_properties: basic validity checked, do request");
-                           result = cfs_create_guest_conf_properties_msg(outbuf, memdb, properties, rh->num_props, rh->vmid);
-                       }
-
-                       free(properties);
-               }
-       } else if (request_id == CFS_IPC_VERIFY_TOKEN) {
-
-               cfs_verify_token_request_header_t *rh = (cfs_verify_token_request_header_t *) data;
-               int tokenlen = request_size - G_STRUCT_OFFSET(cfs_verify_token_request_header_t, token) - 1;
-
-               if (tokenlen <= 0) {
-                       cfs_debug("tokenlen <= 0, %d", tokenlen);
-                       result = -EINVAL;
-               } else if (memchr(rh->token, '\n', tokenlen) != NULL) {
-                       cfs_debug("token contains newline");
-                       result = -EINVAL;
-               } else if (rh->token[tokenlen] != '\0') {
-                       cfs_debug("token not NULL-terminated");
-                       result = -EINVAL;
-               } else if (strnlen(rh->token, tokenlen) != tokenlen) {
-                       cfs_debug("token contains NULL-byte");
-                       result = -EINVAL;
-               } else {
-                       cfs_debug("cfs_verify_token: basic validity checked, reading token.cfg");
-                       gpointer tmp = NULL;
-                       int bytes_read = memdb_read(memdb, "priv/token.cfg", &tmp);
-                       size_t remaining = bytes_read > 0 ? bytes_read : 0;
-                       if (tmp != NULL && remaining >= tokenlen) {
-                               const char *line = (char *) tmp;
-                               const char *next_line;
-                               const char *const end = line + remaining;
-                               size_t linelen;
-
-                               while (line != NULL) {
-                                       next_line = memchr(line, '\n', remaining);
-                                       linelen = next_line == NULL ? remaining : next_line - line;
-                                       if (linelen == tokenlen && strncmp(line, rh->token, linelen) == 0) {
-                                               result = 0;
-                                               break;
-                                       }
-                                       line = next_line;
-                                       if (line != NULL) {
-                                               line += 1;
-                                               remaining = end - line;
-                                       }
-                               }
-                               if (line == NULL) {
-                                       result = -ENOENT;
-                               }
-                               g_free(tmp);
-                       } else {
-                               cfs_debug("token: token.cfg does not exist - ENOENT");
-                               result = -ENOENT;
-                       }
-               }
-       }
-
-       cfs_debug("process result %d", result);
-
-       if (result >= 0) {
-               resp = outbuf->str;
-               result = 0;
-       }
-
-       int iov_len = 2;
-       struct iovec iov[iov_len];
-       struct qb_ipc_response_header res_header;
-
-       int resp_data_len = resp ? outbuf->len : 0;
-
-       res_header.id = request_id;
-       res_header.size = sizeof(res_header) + resp_data_len;
-       res_header.error = result;
-
-       iov[0].iov_base = (char *)&res_header;
-       iov[0].iov_len = sizeof(res_header);
-       iov[1].iov_base = resp;
-       iov[1].iov_len = resp_data_len;
-
-       ssize_t res = qb_ipcs_response_sendv(c, iov, iov_len);
-       if (res < 0) {
-               cfs_critical("qb_ipcs_response_send: %s", strerror(errno));
-               qb_ipcs_disconnect(c);
-       }
-
-       return 0;
-}
-
-static int32_t my_job_add(
-       enum qb_loop_priority p, 
-       void *data, 
-       qb_loop_job_dispatch_fn fn)
-{
-       return qb_loop_job_add(loop, p, data, fn);
-}
-
-static int32_t my_dispatch_add(
-       enum qb_loop_priority p, 
-       int32_t fd, 
-       int32_t evts,
-       void *data, 
-       qb_ipcs_dispatch_fn_t fn)
-{
-       return qb_loop_poll_add(loop, p, fd, evts, data, fn);
-}
-
-static int32_t my_dispatch_mod(
-       enum qb_loop_priority p, 
-       int32_t fd, 
-       int32_t evts,
-       void *data, 
-       qb_ipcs_dispatch_fn_t fn)
-{
-       return qb_loop_poll_mod(loop, p, fd, evts, data, fn);
-}
-
-static int32_t my_dispatch_del(
-       int32_t fd)
-{
-       return qb_loop_poll_del(loop, fd);
-}
-
-static struct qb_ipcs_service_handlers service_handlers = {
-       .connection_accept = s1_connection_accept_fn,
-       .connection_created = s1_connection_created_fn,
-       .msg_process = s1_msg_process_fn,
-       .connection_destroyed = s1_connection_destroyed_fn,
-       .connection_closed = s1_connection_closed_fn,
-};
-
-static struct qb_ipcs_poll_handlers poll_handlers = {
-       .job_add = my_job_add,
-       .dispatch_add = my_dispatch_add,
-       .dispatch_mod = my_dispatch_mod,
-       .dispatch_del = my_dispatch_del,
-};
-
-static void timer_job(void *data)
-{
-       gboolean terminate = FALSE;
-
-       g_mutex_lock (&server_started_mutex);
-
-       if (terminate_server) {
-               cfs_debug ("got terminate request");
-
-               if (loop)
-                       qb_loop_stop (loop);
-               
-               if (s1) {
-                       qb_ipcs_destroy (s1);
-                       s1 = 0;
-               }
-               server_started = 0;
-
-               g_cond_signal (&server_stopped_cond);
-               
-               terminate = TRUE;
-       } else if (!server_started) {
-               server_started = 1;
-               g_cond_signal (&server_started_cond);
-       }
-       
-       g_mutex_unlock (&server_started_mutex);
-
-       if (terminate)
-               return;
-                              
-       qb_loop_timer_handle th;
-       qb_loop_timer_add(loop, QB_LOOP_LOW, 1000000000, NULL, timer_job, &th);
-}
-
-static gpointer worker_thread(gpointer data)
-{
-       g_return_val_if_fail(loop != NULL, NULL);
-
-       cfs_debug("start event loop");
-
-       qb_ipcs_run(s1);
-
-       qb_loop_timer_handle th;
-       qb_loop_timer_add(loop, QB_LOOP_LOW, 1000, NULL, timer_job, &th);
-
-       qb_loop_run(loop);
-
-       cfs_debug("event loop finished - exit worker thread");
-       
-       return NULL;
-}
-
-gboolean server_start(memdb_t *db)
-{
-       g_return_val_if_fail(loop == NULL, FALSE);
-       g_return_val_if_fail(worker == NULL, FALSE);
-       g_return_val_if_fail(db != NULL, FALSE);
-
-       terminate_server = 0;
-       server_started = 0;
-       
-       memdb = db;
-
-       outbuf = g_string_sized_new(8192*8);
-
-       if (!(loop = qb_loop_create())) {
-               cfs_critical("cant create event loop");
-               return FALSE;
-       }
-       
-       s1 = qb_ipcs_create("pve2", 1, QB_IPC_SHM, &service_handlers);
-       if (s1 == 0) {
-               cfs_critical("qb_ipcs_create failed: %s", strerror(errno));
-               return FALSE;
-       }
-       qb_ipcs_poll_handlers_set(s1, &poll_handlers);
-
-       worker = g_thread_new ("server", worker_thread, NULL);
-
-       g_mutex_lock (&server_started_mutex);
-       while (!server_started)
-               g_cond_wait (&server_started_cond, &server_started_mutex);
-       g_mutex_unlock (&server_started_mutex);
-       
-       cfs_debug("server started");
-       
-       return TRUE;
-}
-
-void server_stop(void)
-{
-       cfs_debug("server stop");
-
-       g_mutex_lock (&server_started_mutex);
-       terminate_server = 1;
-       while (server_started)
-               g_cond_wait (&server_stopped_cond, &server_started_mutex);
-       g_mutex_unlock (&server_started_mutex);
-
-       if (worker) {
-               g_thread_join(worker);
-               worker = NULL;
-       }
-       
-       cfs_debug("worker thread finished");
-
-       if (loop) {
-               qb_loop_destroy(loop);
-
-               loop = NULL;
-       }
-
-       if (outbuf) {
-               g_string_free(outbuf, TRUE);
-               outbuf = NULL;
-       }
-}
diff --git a/data/src/server.h b/data/src/server.h
deleted file mode 100644 (file)
index de33fbd..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_IPCS_H_
-#define _PVE_IPCS_H_
-
-
-gboolean server_start(memdb_t *memdb);
-void server_stop(void);
-
-
-#endif /* _PVE_IPCS_H_ */
diff --git a/data/src/status.c b/data/src/status.c
deleted file mode 100644 (file)
index 8d62986..0000000
+++ /dev/null
@@ -1,1980 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#define G_LOG_DOMAIN "status"
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif /* HAVE_CONFIG_H */
-
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
-#include <errno.h>
-#include <glib.h>
-#include <sys/syslog.h>
-#include <rrd.h>
-#include <rrd_client.h>
-#include <time.h>
-#include <ctype.h>
-
-#include "cfs-utils.h"
-#include "status.h"
-#include "memdb.h"
-#include "logger.h"
-
-#define KVSTORE_CPG_GROUP_NAME "pve_kvstore_v1"
-
-typedef enum {
-       KVSTORE_MESSAGE_UPDATE = 1,
-       KVSTORE_MESSAGE_UPDATE_COMPLETE = 2,
-       KVSTORE_MESSAGE_LOG = 3,
-} kvstore_message_t;
-
-static uint32_t vminfo_version_counter;
-
-typedef struct {
-       uint32_t vmid;
-       char *nodename;
-       int vmtype;
-       uint32_t version;
-} vminfo_t;
-
-typedef struct {
-       char *key;
-       gpointer data;
-       size_t len;
-       uint32_t version;
-} kventry_t;
-
-typedef struct {
-       char *key;
-       gpointer data;
-       size_t len;
-       uint32_t time;
-} rrdentry_t;
-
-typedef struct {
-       char *path;
-       uint32_t version;
-} memdb_change_t;
-
-static memdb_change_t memdb_change_array[] = {
-       { .path = "corosync.conf" },
-       { .path = "corosync.conf.new" },
-       { .path = "storage.cfg" },
-       { .path = "user.cfg" },
-       { .path = "domains.cfg" },
-       { .path = "priv/shadow.cfg" },
-       { .path = "priv/acme/plugins.cfg" },
-       { .path = "priv/tfa.cfg" },
-       { .path = "priv/token.cfg" },
-       { .path = "priv/ipam.db" },
-       { .path = "datacenter.cfg" },
-       { .path = "vzdump.cron" },
-       { .path = "vzdump.conf" },
-       { .path = "jobs.cfg" },
-       { .path = "ha/crm_commands" },
-       { .path = "ha/manager_status" },
-       { .path = "ha/resources.cfg" },
-       { .path = "ha/groups.cfg" },
-       { .path = "ha/fence.cfg" },
-       { .path = "status.cfg" },
-       { .path = "replication.cfg" },
-       { .path = "ceph.conf" },
-       { .path = "sdn/vnets.cfg" },
-       { .path = "sdn/zones.cfg" },
-       { .path = "sdn/controllers.cfg" },
-       { .path = "sdn/subnets.cfg" },
-       { .path = "sdn/ipams.cfg" },
-       { .path = "sdn/dns.cfg" },
-       { .path = "sdn/.running-config" },
-       { .path = "virtual-guest/cpu-models.conf" },
-       { .path = "firewall/cluster.fw" },
-};
-
-static GMutex mutex;
-
-typedef struct {
-       time_t start_time;
-
-       uint32_t quorate;
-
-       cfs_clinfo_t *clinfo;
-       uint32_t clinfo_version;
-
-       GHashTable *vmlist;
-       uint32_t vmlist_version;
-
-       dfsm_t *kvstore;
-       GHashTable *kvhash;
-       GHashTable *rrdhash;
-       GHashTable *iphash;
-
-       GHashTable *memdb_changes;
-
-       clusterlog_t *clusterlog;
-} cfs_status_t;
-
-static cfs_status_t cfs_status;
-
-struct cfs_clnode {
-       char *name;
-       uint32_t nodeid;
-       uint32_t votes;
-       gboolean online;
-       GHashTable *kvhash;
-};
-
-struct cfs_clinfo {
-       char *cluster_name;
-       uint32_t cman_version;
-
-       GHashTable *nodes_byid;
-       GHashTable *nodes_byname;
-};
-
-static guint
-g_int32_hash (gconstpointer v)
-{
-       return *(const uint32_t *) v;
-}
-
-static gboolean
-g_int32_equal (gconstpointer v1,
-              gconstpointer v2)
-{
-       return *((const uint32_t*) v1) == *((const uint32_t*) v2);
-}
-
-static void vminfo_free(vminfo_t *vminfo)
-{
-       g_return_if_fail(vminfo != NULL);
-
-       if (vminfo->nodename)
-               g_free(vminfo->nodename);
-
-
-       g_free(vminfo);
-}
-
-static const char *vminfo_type_to_string(vminfo_t *vminfo)
-{
-       if (vminfo->vmtype == VMTYPE_QEMU) {
-               return "qemu";
-       } else if (vminfo->vmtype == VMTYPE_OPENVZ) {
-               // FIXME: remove openvz stuff for 7.x
-               return "openvz";
-       } else if (vminfo->vmtype == VMTYPE_LXC) {
-               return "lxc";
-       } else {
-               return "unknown";
-       }
-}
-
-static const char *vminfo_type_to_path_type(vminfo_t *vminfo)
-{
-       if (vminfo->vmtype == VMTYPE_QEMU) {
-               return "qemu-server"; // special case..
-       } else {
-               return vminfo_type_to_string(vminfo);
-       }
-}
-
-int vminfo_to_path(vminfo_t *vminfo, GString *path)
-{
-       g_return_val_if_fail(vminfo != NULL, -1);
-       g_return_val_if_fail(path != NULL, -1);
-
-       if (!vminfo->nodename)
-               return 0;
-
-       const char *type = vminfo_type_to_path_type(vminfo);
-       g_string_printf(path, "/nodes/%s/%s/%u.conf", vminfo->nodename, type, vminfo->vmid);
-
-       return 1;
-}
-
-void cfs_clnode_destroy(
-       cfs_clnode_t *clnode)
-{
-       g_return_if_fail(clnode != NULL);
-
-       if (clnode->kvhash)
-               g_hash_table_destroy(clnode->kvhash);
-
-       if (clnode->name)
-               g_free(clnode->name);
-
-       g_free(clnode);
-}
-
-cfs_clnode_t *cfs_clnode_new(
-       const char *name,
-       uint32_t nodeid,
-       uint32_t votes)
-{
-       g_return_val_if_fail(name != NULL, NULL);
-
-       cfs_clnode_t *clnode = g_new0(cfs_clnode_t, 1);
-       if (!clnode)
-               return NULL;
-
-       clnode->name = g_strdup(name);
-       clnode->nodeid = nodeid;
-       clnode->votes = votes;
-
-       return clnode;
-}
-
-gboolean cfs_clinfo_destroy(
-       cfs_clinfo_t *clinfo)
-{
-       g_return_val_if_fail(clinfo != NULL, FALSE);
-
-       if (clinfo->cluster_name)
-               g_free(clinfo->cluster_name);
-
-       if (clinfo->nodes_byname)
-               g_hash_table_destroy(clinfo->nodes_byname);
-
-       if (clinfo->nodes_byid)
-               g_hash_table_destroy(clinfo->nodes_byid);
-
-       g_free(clinfo);
-
-       return TRUE;
-}
-
-cfs_clinfo_t *cfs_clinfo_new(
-       const char *cluster_name,
-       uint32_t cman_version)
-{
-       g_return_val_if_fail(cluster_name != NULL, NULL);
-
-       cfs_clinfo_t *clinfo = g_new0(cfs_clinfo_t, 1);
-       if (!clinfo)
-               return NULL;
-
-       clinfo->cluster_name = g_strdup(cluster_name);
-       clinfo->cman_version = cman_version;
-
-       if (!(clinfo->nodes_byid = g_hash_table_new_full(
-                     g_int32_hash, g_int32_equal, NULL,
-                     (GDestroyNotify)cfs_clnode_destroy)))
-               goto fail;
-
-       if (!(clinfo->nodes_byname = g_hash_table_new(g_str_hash, g_str_equal)))
-               goto fail;
-
-       return clinfo;
-
-fail:
-       cfs_clinfo_destroy(clinfo);
-
-       return NULL;
-}
-
-gboolean cfs_clinfo_add_node(
-       cfs_clinfo_t *clinfo,
-       cfs_clnode_t *clnode)
-{
-       g_return_val_if_fail(clinfo != NULL, FALSE);
-       g_return_val_if_fail(clnode != NULL, FALSE);
-
-       g_hash_table_replace(clinfo->nodes_byid, &clnode->nodeid, clnode);
-       g_hash_table_replace(clinfo->nodes_byname, clnode->name, clnode);
-
-       return TRUE;
-}
-
-int
-cfs_create_memberlist_msg(
-       GString *str)
-{
-       g_return_val_if_fail(str != NULL, -EINVAL);
-
-       g_mutex_lock (&mutex);
-
-       g_string_append_printf(str,"{\n");
-
-       guint nodecount = 0;
-
-       cfs_clinfo_t *clinfo = cfs_status.clinfo;
-
-       if (clinfo && clinfo->nodes_byid)
-               nodecount = g_hash_table_size(clinfo->nodes_byid);
-
-       if (nodecount) {
-               g_string_append_printf(str, "\"nodename\": \"%s\",\n", cfs.nodename);
-               g_string_append_printf(str, "\"version\": %u,\n", cfs_status.clinfo_version);
-
-               g_string_append_printf(str, "\"cluster\": { ");
-               g_string_append_printf(str, "\"name\": \"%s\", \"version\": %d, "
-                                      "\"nodes\": %d, \"quorate\": %d ",
-                                      clinfo->cluster_name, clinfo->cman_version,
-                                      nodecount, cfs_status.quorate);
-
-               g_string_append_printf(str,"},\n");
-               g_string_append_printf(str,"\"nodelist\": {\n");
-
-               GHashTable *ht = clinfo->nodes_byid;
-               GHashTableIter iter;
-               gpointer key, value;
-
-               g_hash_table_iter_init (&iter, ht);
-
-               int i = 0;
-               while (g_hash_table_iter_next (&iter, &key, &value)) {
-                       cfs_clnode_t *node = (cfs_clnode_t *)value;
-                       if (i) g_string_append_printf(str, ",\n");
-                       i++;
-
-                       g_string_append_printf(str, "  \"%s\": { \"id\": %d, \"online\": %d",
-                                              node->name, node->nodeid, node->online);
-
-
-                       char *ip = (char *)g_hash_table_lookup(cfs_status.iphash, node->name);
-                       if (ip) {
-                               g_string_append_printf(str, ", \"ip\": \"%s\"", ip);
-                       }
-
-                       g_string_append_printf(str, "}");
-
-               }
-               g_string_append_printf(str,"\n  }\n");
-       } else {
-               g_string_append_printf(str, "\"nodename\": \"%s\",\n", cfs.nodename);
-               g_string_append_printf(str, "\"version\": %u\n", cfs_status.clinfo_version);
-       }
-
-       g_string_append_printf(str,"}\n");
-
-       g_mutex_unlock (&mutex);
-
-       return 0;
-}
-
-static void
-kventry_free(kventry_t *entry)
-{
-       g_return_if_fail(entry != NULL);
-
-       g_free(entry->key);
-       g_free(entry->data);
-       g_free(entry);
-}
-
-static GHashTable *
-kventry_hash_new(void)
-{
-       return g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
-                                    (GDestroyNotify)kventry_free);
-}
-
-static void
-rrdentry_free(rrdentry_t *entry)
-{
-       g_return_if_fail(entry != NULL);
-
-       g_free(entry->key);
-       g_free(entry->data);
-       g_free(entry);
-}
-
-static GHashTable *
-rrdentry_hash_new(void)
-{
-       return g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
-                                    (GDestroyNotify)rrdentry_free);
-}
-
-void
-cfs_cluster_log_dump(GString *str, const char *user, guint max_entries)
-{
-       clusterlog_dump(cfs_status.clusterlog, str, user, max_entries);
-}
-
-void
-cfs_cluster_log(clog_entry_t *entry)
-{
-       g_return_if_fail(entry != NULL);
-
-       clusterlog_insert(cfs_status.clusterlog, entry);
-
-       if (cfs_status.kvstore) {
-               struct iovec iov[1];
-               iov[0].iov_base = (char *)entry;
-               iov[0].iov_len = clog_entry_size(entry);
-
-               if (dfsm_is_initialized(cfs_status.kvstore))
-                       dfsm_send_message(cfs_status.kvstore, KVSTORE_MESSAGE_LOG, iov, 1);
-       }
-}
-
-void cfs_status_init(void)
-{
-       g_mutex_lock (&mutex);
-
-       cfs_status.start_time = time(NULL);
-
-       cfs_status.vmlist = vmlist_hash_new();
-
-       cfs_status.kvhash = kventry_hash_new();
-
-       cfs_status.rrdhash = rrdentry_hash_new();
-
-       cfs_status.iphash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
-
-       cfs_status.memdb_changes = g_hash_table_new(g_str_hash, g_str_equal);
-
-       for (int i = 0; i < G_N_ELEMENTS(memdb_change_array); i++) {
-               g_hash_table_replace(cfs_status.memdb_changes,
-                                    memdb_change_array[i].path,
-                                    &memdb_change_array[i]);
-       }
-
-       cfs_status.clusterlog = clusterlog_new();
-
-       // fixme:
-       clusterlog_add(cfs_status.clusterlog, "root", "cluster", getpid(),
-                      LOG_INFO, "starting cluster log");
-
-       g_mutex_unlock (&mutex);
-}
-
-void cfs_status_cleanup(void)
-{
-       g_mutex_lock (&mutex);
-
-       cfs_status.clinfo_version++;
-
-       if (cfs_status.clinfo) {
-               cfs_clinfo_destroy(cfs_status.clinfo);
-               cfs_status.clinfo = NULL;
-       }
-
-       if (cfs_status.vmlist) {
-               g_hash_table_destroy(cfs_status.vmlist);
-               cfs_status.vmlist = NULL;
-       }
-
-       if (cfs_status.kvhash) {
-               g_hash_table_destroy(cfs_status.kvhash);
-               cfs_status.kvhash = NULL;
-       }
-
-       if (cfs_status.rrdhash) {
-               g_hash_table_destroy(cfs_status.rrdhash);
-               cfs_status.rrdhash = NULL;
-       }
-
-       if (cfs_status.iphash) {
-               g_hash_table_destroy(cfs_status.iphash);
-               cfs_status.iphash = NULL;
-       }
-
-       if (cfs_status.clusterlog)
-               clusterlog_destroy(cfs_status.clusterlog);
-
-       g_mutex_unlock (&mutex);
-}
-
-void cfs_status_set_clinfo(
-       cfs_clinfo_t *clinfo)
-{
-       g_return_if_fail(clinfo != NULL);
-
-       g_mutex_lock (&mutex);
-
-       cfs_status.clinfo_version++;
-
-       cfs_clinfo_t *old = cfs_status.clinfo;
-
-       cfs_status.clinfo = clinfo;
-
-       cfs_message("update cluster info (cluster name  %s, version = %d)",
-                   clinfo->cluster_name, clinfo->cman_version);
-
-
-       if (old && old->nodes_byid && clinfo->nodes_byid) {
-               /* copy kvstore */
-               GHashTable *ht = clinfo->nodes_byid;
-               GHashTableIter iter;
-               gpointer key, value;
-
-               g_hash_table_iter_init (&iter, ht);
-
-               while (g_hash_table_iter_next (&iter, &key, &value)) {
-                       cfs_clnode_t *node = (cfs_clnode_t *)value;
-                       cfs_clnode_t *oldnode;
-                       if ((oldnode = g_hash_table_lookup(old->nodes_byid, key))) {
-                               node->online = oldnode->online;
-                               node->kvhash = oldnode->kvhash;
-                               oldnode->kvhash = NULL;
-                       }
-               }
-
-       }
-
-       if (old)
-               cfs_clinfo_destroy(old);
-
-
-       g_mutex_unlock (&mutex);
-}
-
-static void
-dump_kvstore_versions(
-       GString *str,
-       GHashTable *kvhash,
-       const char *nodename)
-{
-       g_return_if_fail(kvhash != NULL);
-       g_return_if_fail(str != NULL);
-       g_return_if_fail(nodename != NULL);
-
-       GHashTable *ht = kvhash;
-       GHashTableIter iter;
-       gpointer key, value;
-
-       g_string_append_printf(str, "\"%s\": {\n", nodename);
-
-       g_hash_table_iter_init (&iter, ht);
-
-       int i = 0;
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-               kventry_t *entry = (kventry_t *)value;
-               if (i) g_string_append_printf(str, ",\n");
-               i++;
-               g_string_append_printf(str,"\"%s\": %u", entry->key, entry->version);
-       }
-
-       g_string_append_printf(str, "}\n");
-}
-
-int
-cfs_create_version_msg(GString *str)
-{
-       g_return_val_if_fail(str != NULL, -EINVAL);
-
-       g_mutex_lock (&mutex);
-
-       g_string_append_printf(str,"{\n");
-
-       g_string_append_printf(str, "\"starttime\": %lu,\n", (unsigned long)cfs_status.start_time);
-
-       g_string_append_printf(str, "\"clinfo\": %u,\n", cfs_status.clinfo_version);
-
-       g_string_append_printf(str, "\"vmlist\": %u,\n", cfs_status.vmlist_version);
-
-       for (int i = 0; i < G_N_ELEMENTS(memdb_change_array); i++) {
-               g_string_append_printf(str, "\"%s\": %u,\n",
-                                      memdb_change_array[i].path,
-                                      memdb_change_array[i].version);
-       }
-
-       g_string_append_printf(str, "\"kvstore\": {\n");
-
-       dump_kvstore_versions(str, cfs_status.kvhash, cfs.nodename);
-
-       cfs_clinfo_t *clinfo = cfs_status.clinfo;
-
-       if (clinfo && clinfo->nodes_byid) {
-               GHashTable *ht = clinfo->nodes_byid;
-               GHashTableIter iter;
-               gpointer key, value;
-
-               g_hash_table_iter_init (&iter, ht);
-
-               while (g_hash_table_iter_next (&iter, &key, &value)) {
-                       cfs_clnode_t *node = (cfs_clnode_t *)value;
-                       if (!node->kvhash)
-                               continue;
-                       g_string_append_printf(str, ",\n");
-                       dump_kvstore_versions(str, node->kvhash, node->name);
-               }
-       }
-
-       g_string_append_printf(str,"}\n");
-
-       g_string_append_printf(str,"}\n");
-
-       g_mutex_unlock (&mutex);
-
-       return 0;
-}
-
-GHashTable *
-vmlist_hash_new(void)
-{
-       return g_hash_table_new_full(g_int_hash, g_int_equal, NULL,
-                                    (GDestroyNotify)vminfo_free);
-}
-
-gboolean
-vmlist_hash_insert_vm(
-       GHashTable *vmlist,
-       int vmtype,
-       guint32 vmid,
-       const char *nodename,
-       gboolean replace)
-{
-       g_return_val_if_fail(vmlist != NULL, FALSE);
-       g_return_val_if_fail(nodename != NULL, FALSE);
-       g_return_val_if_fail(vmid != 0, FALSE);
-       // FIXME: remove openvz stuff for 7.x
-       g_return_val_if_fail(vmtype == VMTYPE_QEMU || vmtype == VMTYPE_OPENVZ ||
-                            vmtype == VMTYPE_LXC, FALSE);
-
-       if (!replace && g_hash_table_lookup(vmlist, &vmid)) {
-               cfs_critical("detected duplicate VMID %d", vmid);
-               return FALSE;
-       }
-
-       vminfo_t *vminfo = g_new0(vminfo_t, 1);
-
-       vminfo->vmid = vmid;
-       vminfo->vmtype = vmtype;
-       vminfo->nodename = g_strdup(nodename);
-
-       vminfo->version = ++vminfo_version_counter;
-
-       g_hash_table_replace(vmlist, &vminfo->vmid, vminfo);
-
-       return TRUE;
-}
-
-void
-vmlist_register_vm(
-       int vmtype,
-       guint32 vmid,
-       const char *nodename)
-{
-       g_return_if_fail(cfs_status.vmlist != NULL);
-       g_return_if_fail(nodename != NULL);
-       g_return_if_fail(vmid != 0);
-       // FIXME: remove openvz stuff for 7.x
-       g_return_if_fail(vmtype == VMTYPE_QEMU || vmtype == VMTYPE_OPENVZ ||
-                        vmtype == VMTYPE_LXC);
-
-       cfs_debug("vmlist_register_vm: %s/%u %d", nodename, vmid, vmtype);
-
-       g_mutex_lock (&mutex);
-
-       cfs_status.vmlist_version++;
-
-       vmlist_hash_insert_vm(cfs_status.vmlist, vmtype, vmid, nodename, TRUE);
-
-       g_mutex_unlock (&mutex);
-}
-
-gboolean
-vmlist_different_vm_exists(
-       int vmtype,
-       guint32 vmid,
-       const char *nodename)
-{
-       g_return_val_if_fail(cfs_status.vmlist != NULL, FALSE);
-       g_return_val_if_fail(vmid != 0, FALSE);
-
-       gboolean res = FALSE;
-
-       g_mutex_lock (&mutex);
-
-       vminfo_t *vminfo;
-       if ((vminfo = (vminfo_t *)g_hash_table_lookup(cfs_status.vmlist, &vmid))) {
-               if (!(vminfo->vmtype == vmtype && strcmp(vminfo->nodename, nodename) == 0))
-                       res = TRUE;
-       }
-       g_mutex_unlock (&mutex);
-
-       return res;
-}
-
-gboolean
-vmlist_vm_exists(
-       guint32 vmid)
-{
-       g_return_val_if_fail(cfs_status.vmlist != NULL, FALSE);
-       g_return_val_if_fail(vmid != 0, FALSE);
-
-       g_mutex_lock (&mutex);
-
-       gpointer res = g_hash_table_lookup(cfs_status.vmlist, &vmid);
-
-       g_mutex_unlock (&mutex);
-
-       return res != NULL;
-}
-
-void
-vmlist_delete_vm(
-       guint32 vmid)
-{
-       g_return_if_fail(cfs_status.vmlist != NULL);
-       g_return_if_fail(vmid != 0);
-
-       g_mutex_lock (&mutex);
-
-       cfs_status.vmlist_version++;
-
-       g_hash_table_remove(cfs_status.vmlist, &vmid);
-
-       g_mutex_unlock (&mutex);
-}
-
-void cfs_status_set_vmlist(
-       GHashTable *vmlist)
-{
-       g_return_if_fail(vmlist != NULL);
-
-       g_mutex_lock (&mutex);
-
-       cfs_status.vmlist_version++;
-
-       if (cfs_status.vmlist)
-               g_hash_table_destroy(cfs_status.vmlist);
-
-       cfs_status.vmlist = vmlist;
-
-       g_mutex_unlock (&mutex);
-}
-
-int
-cfs_create_vmlist_msg(GString *str)
-{
-       g_return_val_if_fail(cfs_status.vmlist != NULL, -EINVAL);
-       g_return_val_if_fail(str != NULL, -EINVAL);
-
-       g_mutex_lock (&mutex);
-
-       g_string_append_printf(str,"{\n");
-
-       GHashTable *ht = cfs_status.vmlist;
-
-       guint count = g_hash_table_size(ht);
-
-       if (!count) {
-               g_string_append_printf(str,"\"version\": %u\n", cfs_status.vmlist_version);
-       } else {
-               g_string_append_printf(str,"\"version\": %u,\n", cfs_status.vmlist_version);
-
-               g_string_append_printf(str,"\"ids\": {\n");
-
-               GHashTableIter iter;
-               gpointer key, value;
-
-               g_hash_table_iter_init (&iter, ht);
-
-               int first = 1;
-               while (g_hash_table_iter_next (&iter, &key, &value)) {
-                       vminfo_t *vminfo = (vminfo_t *)value;
-                       const char *type = vminfo_type_to_string(vminfo);
-
-                       if (!first)
-                               g_string_append_printf(str, ",\n");
-                       first = 0;
-
-                       g_string_append_printf(str,"\"%u\": { \"node\": \"%s\", \"type\": \"%s\", \"version\": %u }",
-                                              vminfo->vmid, vminfo->nodename, type, vminfo->version);
-               }
-
-               g_string_append_printf(str,"}\n");
-       }
-       g_string_append_printf(str,"\n}\n");
-
-       g_mutex_unlock (&mutex);
-
-       return 0;
-}
-
-// checks if a config line starts with the given prop. if yes, writes a '\0'
-// at the end of the value, and returns the pointer where the value starts
-// note: line[line_end] needs to be guaranteed a null byte
-char *
-_get_property_value_from_line(char *line, size_t line_len, const char *prop, size_t prop_len)
-{
-       if (line_len <= prop_len + 1) return NULL;
-
-       if (line[prop_len] == ':' && memcmp(line, prop, prop_len) == 0) { // found
-               char *v_start = &line[prop_len + 1];
-               char *v_end = &line[line_len - 1];
-
-               // drop initial value whitespaces here already
-               while (v_start < v_end && *v_start && isspace(*v_start)) v_start++;
-
-               if (!*v_start) return NULL;
-
-               while (v_end > v_start && isspace(*v_end)) v_end--;
-               if (v_end < &line[line_len - 1]) {
-                   v_end[1] = '\0';
-               }
-
-               return v_start;
-       }
-
-       return NULL;
-}
-
-// checks the conf for lines starting with the given props and
-// writes the pointers into the correct positions into the 'found' array
-// afterwards, without initial whitespace(s), we only deal with the format
-// restriction imposed by our perl VM config parser, main reference is
-// PVE::QemuServer::parse_vm_config this allows to be very fast and still
-// relatively simple
-// main restrictions used for our advantage is the properties match regex:
-// ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) from parse_vm_config
-// currently we only look at the current configuration in place, i.e., *no*
-// snapshot and *no* pending changes
-//
-// min..max is the char range of the first character of the given props,
-// so that we can return early when checking the line
-// note: conf must end with a newline
-void
-_get_property_values(char **found, char *conf, int conf_size, const char **props, uint8_t num_props, char min, char max)
-{
-       const char *const conf_end = conf + conf_size;
-       char *line = conf;
-       size_t remaining_size = conf_size;
-       uint8_t count = 0;
-
-       if (conf_size == 0) {
-           return;
-       }
-
-       char *next_newline = memchr(conf, '\n', conf_size);
-       if (next_newline == NULL) {
-               return; // valid property lines end with \n, but none in the config
-       }
-       *next_newline = '\0';
-
-       while (line != NULL) {
-               if (!line[0]) goto next;
-
-               // snapshot or pending section start, but nothing found yet -> not found
-               if (line[0] == '[') return;
-               // continue early if line does not begin with the min/max char of the properties
-               if (line[0] < min || line[0] > max) goto next;
-
-               size_t line_len = next_newline - line;
-               for (uint8_t i = 0; i < num_props; i++) {
-                       char * value = _get_property_value_from_line(line, line_len, props[i], strlen(props[i]));
-                       if (value != NULL) {
-                               count += (found[i] != NULL) & 0x1; // count newly found lines
-                               found[i] = value;
-                       }
-               }
-               if (count == num_props) {
-                       return; // found all
-               }
-next:
-               line = next_newline + 1;
-               remaining_size = conf_end - line;
-               next_newline = memchr(line, '\n', remaining_size);
-               if (next_newline == NULL) {
-                       return; // valid property lines end with \n, but none in the config
-               }
-               *next_newline = '\0';
-       }
-
-       return;
-}
-
-static void
-_g_str_append_kv_jsonescaped(GString *str, const char *k, const char *v)
-{
-       g_string_append_printf(str, "\"%s\": \"", k);
-
-       for (; *v; v++) {
-               if (*v == '\\' || *v == '"') {
-                       g_string_append_c(str, '\\');
-               }
-               g_string_append_c(str, *v);
-       }
-
-       g_string_append_c(str, '"');
-}
-
-int
-_print_found_properties(
-       GString *str,
-       gpointer conf,
-       int size,
-       const char **props,
-       uint8_t num_props,
-       uint32_t vmid,
-       char **values,
-       char min,
-       char max,
-       int first)
-{
-       _get_property_values(values, conf, size, props, num_props, min, max);
-
-       uint8_t found = 0;
-       for (uint8_t i = 0; i < num_props; i++) {
-               if (values[i] == NULL) {
-                       continue;
-               }
-               if (found) {
-                       g_string_append_c(str, ',');
-               } else {
-                       if (!first) {
-                               g_string_append_printf(str, ",\n");
-                       } else {
-                               first = 0;
-                       }
-                       g_string_append_printf(str, "\"%u\":{", vmid);
-                       found = 1;
-               }
-               _g_str_append_kv_jsonescaped(str, props[i], values[i]);
-       }
-
-       if (found) {
-               g_string_append_c(str, '}');
-       }
-
-       return first;
-}
-
-int
-cfs_create_guest_conf_properties_msg(GString *str, memdb_t *memdb, const char **props, uint8_t num_props, uint32_t vmid)
-{
-       g_return_val_if_fail(cfs_status.vmlist != NULL, -EINVAL);
-       g_return_val_if_fail(str != NULL, -EINVAL);
-
-       // Prelock &memdb->mutex in order to enable the usage of memdb_read_nolock
-       // to prevent Deadlocks as in #2553
-       g_mutex_lock (&memdb->mutex);
-       g_mutex_lock (&mutex);
-
-       g_string_printf(str, "{\n");
-
-       GHashTable *ht = cfs_status.vmlist;
-
-       int res = 0;
-       GString *path = NULL;
-       gpointer tmp = NULL;
-       char **values = calloc(num_props, sizeof(char*));
-       char min = 'z', max = 'a';
-
-       for (uint8_t i = 0; i < num_props; i++) {
-               if (props[i][0] > max) {
-                       max = props[i][0];
-               }
-
-               if (props[i][0] < min) {
-                       min = props[i][0];
-               }
-       }
-
-       if (!g_hash_table_size(ht)) {
-               goto ret;
-       }
-
-       if ((path = g_string_sized_new(256)) == NULL) {
-               res = -ENOMEM;
-               goto ret;
-       }
-
-       if (vmid >= 100) {
-               vminfo_t *vminfo = (vminfo_t *) g_hash_table_lookup(cfs_status.vmlist, &vmid);
-               if (vminfo == NULL) goto enoent;
-
-               if (!vminfo_to_path(vminfo, path)) goto err;
-
-               // use memdb_read_nolock because lock is handled here
-               int size = memdb_read_nolock(memdb, path->str, &tmp);
-               if (tmp == NULL) goto err;
-
-               // conf needs to be newline terminated
-               if (((char *)tmp)[size - 1] != '\n') {
-                       gpointer new = realloc(tmp, size + 1);
-                       if (new == NULL) goto err;
-                       tmp = new;
-                       ((char *)tmp)[size++] = '\n';
-               }
-               _print_found_properties(str, tmp, size, props, num_props, vmid, values, min, max, 1);
-       } else {
-               GHashTableIter iter;
-               g_hash_table_iter_init (&iter, ht);
-
-               gpointer key, value;
-               int first = 1;
-               while (g_hash_table_iter_next (&iter, &key, &value)) {
-                       vminfo_t *vminfo = (vminfo_t *)value;
-
-                       if (!vminfo_to_path(vminfo, path)) goto err;
-
-                       g_free(tmp); // no-op if already null
-                       tmp = NULL;
-                       // use memdb_read_nolock because lock is handled here
-                       int size = memdb_read_nolock(memdb, path->str, &tmp);
-                       if (tmp == NULL) continue;
-
-                       // conf needs to be newline terminated
-                       if (((char *)tmp)[size - 1] != '\n') {
-                           gpointer new = realloc(tmp, size + 1);
-                           if (new == NULL) continue;
-                           tmp = new;
-                           ((char *)tmp)[size++] = '\n';
-                       }
-
-                       memset(values, 0, sizeof(char*)*num_props); // reset array
-                       first = _print_found_properties(str, tmp, size, props, num_props,
-                                       vminfo->vmid, values, min, max, first);
-               }
-       }
-ret:
-       g_free(tmp);
-       free(values);
-       if (path != NULL) {
-               g_string_free(path, TRUE);
-       }
-       g_string_append_printf(str,"\n}\n");
-       g_mutex_unlock (&mutex);
-       g_mutex_unlock (&memdb->mutex);
-       return res;
-err:
-       res = -EIO;
-       goto ret;
-enoent:
-       res = -ENOENT;
-       goto ret;
-}
-
-int
-cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid)
-{
-       return cfs_create_guest_conf_properties_msg(str, memdb, &prop, 1, vmid);
-}
-
-void
-record_memdb_change(const char *path)
-{
-       g_return_if_fail(cfs_status.memdb_changes != 0);
-
-       memdb_change_t *ce;
-
-       if ((ce = (memdb_change_t *)g_hash_table_lookup(cfs_status.memdb_changes, path))) {
-               ce->version++;
-       }
-}
-
-void
-record_memdb_reload(void)
-{
-       for (int i = 0; i < G_N_ELEMENTS(memdb_change_array); i++) {
-               memdb_change_array[i].version++;
-       }
-}
-
-static gboolean
-kventry_hash_set(
-       GHashTable *kvhash,
-       const char *key,
-       gconstpointer data,
-       size_t len)
-{
-       g_return_val_if_fail(kvhash != NULL, FALSE);
-       g_return_val_if_fail(key != NULL, FALSE);
-       g_return_val_if_fail(data != NULL, FALSE);
-
-       kventry_t *entry;
-       if (!len) {
-               g_hash_table_remove(kvhash, key);
-       } else if ((entry = (kventry_t *)g_hash_table_lookup(kvhash, key))) {
-               g_free(entry->data);
-               entry->data = g_memdup2(data, len);
-               entry->len = len;
-               entry->version++;
-       } else {
-               kventry_t *entry = g_new0(kventry_t, 1);
-
-               entry->key = g_strdup(key);
-               entry->data = g_memdup2(data, len);
-               entry->len = len;
-
-               g_hash_table_replace(kvhash, entry->key, entry);
-       }
-
-       return TRUE;
-}
-
-static const char *rrd_def_node[] = {
-       "DS:loadavg:GAUGE:120:0:U",
-       "DS:maxcpu:GAUGE:120:0:U",
-       "DS:cpu:GAUGE:120:0:U",
-       "DS:iowait:GAUGE:120:0:U",
-       "DS:memtotal:GAUGE:120:0:U",
-       "DS:memused:GAUGE:120:0:U",
-       "DS:swaptotal:GAUGE:120:0:U",
-       "DS:swapused:GAUGE:120:0:U",
-       "DS:roottotal:GAUGE:120:0:U",
-       "DS:rootused:GAUGE:120:0:U",
-       "DS:netin:DERIVE:120:0:U",
-       "DS:netout:DERIVE:120:0:U",
-
-       "RRA:AVERAGE:0.5:1:70", // 1 min avg - one hour
-       "RRA:AVERAGE:0.5:30:70", // 30 min avg - one day
-       "RRA:AVERAGE:0.5:180:70", // 3 hour avg - one week
-       "RRA:AVERAGE:0.5:720:70", // 12 hour avg - one month
-       "RRA:AVERAGE:0.5:10080:70", // 7 day avg - ony year
-
-       "RRA:MAX:0.5:1:70", // 1 min max - one hour
-       "RRA:MAX:0.5:30:70", // 30 min max - one day
-       "RRA:MAX:0.5:180:70",  // 3 hour max - one week
-       "RRA:MAX:0.5:720:70", // 12 hour max - one month
-       "RRA:MAX:0.5:10080:70", // 7 day max - ony year
-       NULL,
-};
-
-static const char *rrd_def_vm[] = {
-       "DS:maxcpu:GAUGE:120:0:U",
-       "DS:cpu:GAUGE:120:0:U",
-       "DS:maxmem:GAUGE:120:0:U",
-       "DS:mem:GAUGE:120:0:U",
-       "DS:maxdisk:GAUGE:120:0:U",
-       "DS:disk:GAUGE:120:0:U",
-       "DS:netin:DERIVE:120:0:U",
-       "DS:netout:DERIVE:120:0:U",
-       "DS:diskread:DERIVE:120:0:U",
-       "DS:diskwrite:DERIVE:120:0:U",
-
-       "RRA:AVERAGE:0.5:1:70", // 1 min avg - one hour
-       "RRA:AVERAGE:0.5:30:70", // 30 min avg - one day
-       "RRA:AVERAGE:0.5:180:70", // 3 hour avg - one week
-       "RRA:AVERAGE:0.5:720:70", // 12 hour avg - one month
-       "RRA:AVERAGE:0.5:10080:70", // 7 day avg - ony year
-
-       "RRA:MAX:0.5:1:70", // 1 min max - one hour
-       "RRA:MAX:0.5:30:70", // 30 min max - one day
-       "RRA:MAX:0.5:180:70",  // 3 hour max - one week
-       "RRA:MAX:0.5:720:70", // 12 hour max - one month
-       "RRA:MAX:0.5:10080:70", // 7 day max - ony year
-       NULL,
-};
-
-static const char *rrd_def_storage[] = {
-       "DS:total:GAUGE:120:0:U",
-       "DS:used:GAUGE:120:0:U",
-
-       "RRA:AVERAGE:0.5:1:70", // 1 min avg - one hour
-       "RRA:AVERAGE:0.5:30:70", // 30 min avg - one day
-       "RRA:AVERAGE:0.5:180:70", // 3 hour avg - one week
-       "RRA:AVERAGE:0.5:720:70", // 12 hour avg - one month
-       "RRA:AVERAGE:0.5:10080:70", // 7 day avg - ony year
-
-       "RRA:MAX:0.5:1:70", // 1 min max - one hour
-       "RRA:MAX:0.5:30:70", // 30 min max - one day
-       "RRA:MAX:0.5:180:70",  // 3 hour max - one week
-       "RRA:MAX:0.5:720:70", // 12 hour max - one month
-       "RRA:MAX:0.5:10080:70", // 7 day max - ony year
-       NULL,
-};
-
-#define RRDDIR "/var/lib/rrdcached/db"
-
-static void
-create_rrd_file(
-       const char *filename,
-       int argcount,
-       const char *rrddef[])
-{
-       /* start at day boundary */
-       time_t ctime;
-       time(&ctime);
-       struct tm *ltm = localtime(&ctime);
-       ltm->tm_sec = 0;
-       ltm->tm_min = 0;
-       ltm->tm_hour = 0;
-
-       rrd_clear_error();
-       if (rrd_create_r(filename, 60, timelocal(ltm), argcount, rrddef)) {
-               cfs_message("RRD create error %s: %s", filename, rrd_get_error());
-       }
-}
-
-static inline const char *
-rrd_skip_data(
-       const char *data,
-       int count)
-{
-       int found = 0;
-       while (*data && found < count) {
-               if (*data++ == ':')
-                       found++;
-       }
-       return data;
-}
-
-static void
-update_rrd_data(
-       const char *key,
-       gconstpointer data,
-       size_t len)
-{
-       g_return_if_fail(key != NULL);
-       g_return_if_fail(data != NULL);
-       g_return_if_fail(len > 0);
-       g_return_if_fail(len < 4096);
-
-       static const char *rrdcsock = "unix:/var/run/rrdcached.sock";
-
-       int use_daemon = 1;
-        if (rrdc_connect(rrdcsock) != 0)
-               use_daemon = 0;
-
-       char *filename = NULL;
-
-       int skip = 0;
-
-       if (strncmp(key, "pve2-node/", 10) == 0) {
-               const char *node = key + 10;
-
-               skip = 2;
-
-               if (strchr(node, '/') != NULL)
-                       goto keyerror;
-
-               if (strlen(node) < 1)
-                       goto keyerror;
-
-               filename = g_strdup_printf(RRDDIR "/%s", key);
-
-               if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
-
-                       mkdir(RRDDIR "/pve2-node", 0755);
-                       int argcount = sizeof(rrd_def_node)/sizeof(void*) - 1;
-                       create_rrd_file(filename, argcount, rrd_def_node);
-               }
-
-       } else if ((strncmp(key, "pve2-vm/", 8) == 0) ||
-                  (strncmp(key, "pve2.3-vm/", 10) == 0)) {
-               const char *vmid;
-
-               if (strncmp(key, "pve2-vm/", 8) == 0) {
-                       vmid = key + 8;
-                       skip = 2;
-               } else {
-                       vmid = key + 10;
-                       skip = 4;
-               }
-
-               if (strchr(vmid, '/') != NULL)
-                       goto keyerror;
-
-               if (strlen(vmid) < 1)
-                       goto keyerror;
-
-               filename = g_strdup_printf(RRDDIR "/%s/%s", "pve2-vm", vmid);
-
-               if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
-
-                       mkdir(RRDDIR "/pve2-vm", 0755);
-                       int argcount = sizeof(rrd_def_vm)/sizeof(void*) - 1;
-                       create_rrd_file(filename, argcount, rrd_def_vm);
-               }
-
-       } else if (strncmp(key, "pve2-storage/", 13) == 0) {
-               const char *node = key + 13;
-
-               const char *storage = node;
-               while (*storage && *storage != '/')
-                       storage++;
-
-               if (*storage != '/' || ((storage - node) < 1))
-                       goto keyerror;
-
-               storage++;
-
-               if (strchr(storage, '/') != NULL)
-                       goto keyerror;
-
-               if (strlen(storage) < 1)
-                       goto keyerror;
-
-               filename = g_strdup_printf(RRDDIR "/%s", key);
-
-               if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
-
-                       mkdir(RRDDIR "/pve2-storage", 0755);
-
-                       char *dir = g_path_get_dirname(filename);
-                       mkdir(dir, 0755);
-                       g_free(dir);
-
-                       int argcount = sizeof(rrd_def_storage)/sizeof(void*) - 1;
-                       create_rrd_file(filename, argcount, rrd_def_storage);
-               }
-
-       } else {
-               goto keyerror;
-       }
-
-       const char *dp = skip ? rrd_skip_data(data, skip) : data;
-
-       const char *update_args[] = { dp, NULL };
-
-       if (use_daemon) {
-               int status;
-               if ((status = rrdc_update(filename, 1, update_args)) != 0) {
-                       cfs_message("RRDC update error %s: %d", filename, status);
-                       rrdc_disconnect();
-                       rrd_clear_error();
-                       if (rrd_update_r(filename, NULL, 1, update_args) != 0) {
-                               cfs_message("RRD update error %s: %s", filename, rrd_get_error());
-                       }
-               }
-
-       } else {
-               rrd_clear_error();
-               if (rrd_update_r(filename, NULL, 1, update_args) != 0) {
-                       cfs_message("RRD update error %s: %s", filename, rrd_get_error());
-               }
-       }
-
-ret:
-       if (filename) 
-               g_free(filename);
-
-       return;
-
-keyerror:
-       cfs_critical("RRD update error: unknown/wrong key %s", key);
-       goto ret;
-}
-
-static gboolean
-rrd_entry_is_old(
-       gpointer key,
-       gpointer value,
-       gpointer user_data)
-{
-       rrdentry_t *entry = (rrdentry_t *)value;
-       uint32_t ctime = GPOINTER_TO_UINT(user_data);
-
-       int diff = ctime - entry->time;
-
-       /* remove everything older than 5 minutes */
-       int expire = 60*5;
-
-       return (diff > expire) ? TRUE : FALSE;
-}
-
-static char *rrd_dump_buf = NULL;
-static time_t rrd_dump_last = 0;
-
-void
-cfs_rrd_dump(GString *str)
-{
-       time_t ctime;
-
-       g_mutex_lock (&mutex);
-
-       time(&ctime);
-       if (rrd_dump_buf && (ctime - rrd_dump_last) < 2) {
-               g_string_assign(str, rrd_dump_buf);
-               g_mutex_unlock (&mutex);
-               return;
-       }
-
-       /* remove old data */
-       g_hash_table_foreach_remove(cfs_status.rrdhash, rrd_entry_is_old,
-                                   GUINT_TO_POINTER(ctime));
-
-       g_string_set_size(str, 0);
-
-       GHashTableIter iter;
-       gpointer key, value;
-
-       g_hash_table_iter_init (&iter, cfs_status.rrdhash);
-
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-               rrdentry_t *entry = (rrdentry_t *)value;
-               g_string_append(str, key);
-               g_string_append(str, ":");
-               g_string_append(str, entry->data);
-               g_string_append(str, "\n");
-       }
-
-       g_string_append_c(str, 0); // never return undef
-
-       rrd_dump_last = ctime;
-       if (rrd_dump_buf)
-               g_free(rrd_dump_buf);
-       rrd_dump_buf = g_strdup(str->str);
-
-       g_mutex_unlock (&mutex);
-}
-
-static gboolean
-nodeip_hash_set(
-       GHashTable *iphash,
-       const char *nodename,
-       const char *ip,
-       size_t len)
-{
-       g_return_val_if_fail(iphash != NULL, FALSE);
-       g_return_val_if_fail(nodename != NULL, FALSE);
-       g_return_val_if_fail(ip != NULL, FALSE);
-       g_return_val_if_fail(len > 0, FALSE);
-       g_return_val_if_fail(len < 256, FALSE);
-       g_return_val_if_fail(ip[len-1] == 0, FALSE);
-
-       char *oldip = (char *)g_hash_table_lookup(iphash, nodename);
-
-       if (!oldip || (strcmp(oldip, ip) != 0)) {
-               cfs_status.clinfo_version++;
-               g_hash_table_replace(iphash, g_strdup(nodename), g_strdup(ip));
-       }
-
-       return TRUE;
-}
-
-static gboolean
-rrdentry_hash_set(
-       GHashTable *rrdhash,
-       const char *key,
-       const char *data,
-       size_t len)
-{
-       g_return_val_if_fail(rrdhash != NULL, FALSE);
-       g_return_val_if_fail(key != NULL, FALSE);
-       g_return_val_if_fail(data != NULL, FALSE);
-       g_return_val_if_fail(len > 0, FALSE);
-       g_return_val_if_fail(len < 4096, FALSE);
-       g_return_val_if_fail(data[len-1] == 0, FALSE);
-
-       rrdentry_t *entry;
-       if ((entry = (rrdentry_t *)g_hash_table_lookup(rrdhash, key))) {
-               g_free(entry->data);
-               entry->data = g_memdup2(data, len);
-               entry->len = len;
-               entry->time = time(NULL);
-       } else {
-               rrdentry_t *entry = g_new0(rrdentry_t, 1);
-
-               entry->key = g_strdup(key);
-               entry->data = g_memdup2(data, len);
-               entry->len = len;
-               entry->time = time(NULL);
-
-               g_hash_table_replace(rrdhash, entry->key, entry);
-       }
-
-       update_rrd_data(key, data, len);
-
-       return TRUE;
-}
-
-static int
-kvstore_send_update_message(
-       dfsm_t *dfsm,
-       const char *key,
-       gpointer data,
-       guint32 len)
-{
-       if (!dfsm_is_initialized(dfsm))
-               return -EACCES;
-
-       struct iovec iov[2];
-
-       char name[256];
-       g_strlcpy(name, key, sizeof(name));
-
-       iov[0].iov_base = &name;
-       iov[0].iov_len = sizeof(name);
-
-       iov[1].iov_base = (char *)data;
-       iov[1].iov_len = len;
-
-       if (dfsm_send_message(dfsm, KVSTORE_MESSAGE_UPDATE, iov, 2) == CS_OK)
-               return 0;
-
-       return -EACCES;
-}
-
-static clog_entry_t *
-kvstore_parse_log_message(
-       const void *msg,
-       size_t msg_len)
-{
-       g_return_val_if_fail(msg != NULL, NULL);
-
-       if (msg_len < sizeof(clog_entry_t)) {
-               cfs_critical("received short log message (%zu < %zu)", msg_len, sizeof(clog_entry_t));
-               return NULL;
-       }
-
-       clog_entry_t *entry = (clog_entry_t *)msg;
-
-       uint32_t size = sizeof(clog_entry_t) + entry->node_len +
-               entry->ident_len + entry->tag_len + entry->msg_len;
-
-       if (msg_len != size) {
-               cfs_critical("received log message with wrong size (%zu != %u)", msg_len, size);
-               return NULL;
-       }
-
-       char *msgptr = entry->data;
-
-       if (*((char *)msgptr + entry->node_len - 1)) {
-               cfs_critical("unterminated string in log message");
-               return NULL;
-       }
-       msgptr += entry->node_len;
-
-       if (*((char *)msgptr + entry->ident_len - 1)) {
-               cfs_critical("unterminated string in log message");
-               return NULL;
-       }
-       msgptr += entry->ident_len;
-
-       if (*((char *)msgptr + entry->tag_len - 1)) {
-               cfs_critical("unterminated string in log message");
-               return NULL;
-       }
-       msgptr += entry->tag_len;
-
-       if (*((char *)msgptr + entry->msg_len - 1)) {
-               cfs_critical("unterminated string in log message");
-               return NULL;
-       }
-
-       return entry;
-}
-
-static gboolean
-kvstore_parse_update_message(
-       const void *msg,
-       size_t msg_len,
-       const char **key,
-       gconstpointer *data,
-       guint32 *len)
-{
-       g_return_val_if_fail(msg != NULL, FALSE);
-       g_return_val_if_fail(key != NULL, FALSE);
-       g_return_val_if_fail(data != NULL, FALSE);
-       g_return_val_if_fail(len != NULL, FALSE);
-
-       if (msg_len < 256) {
-               cfs_critical("received short kvstore message (%zu < 256)", msg_len);
-               return FALSE;
-       }
-
-       /* test if key is null terminated */
-       int i = 0;
-       for (i = 0; i < 256; i++)
-               if (((char *)msg)[i] == 0)
-                       break;
-
-       if (i == 256)
-               return FALSE;
-
-
-       *len = msg_len - 256;
-       *key = msg;
-       *data = (char *) msg + 256;
-
-       return TRUE;
-}
-
-int
-cfs_create_status_msg(
-       GString *str,
-       const char *nodename,
-       const char *key)
-{
-       g_return_val_if_fail(str != NULL, -EINVAL);
-       g_return_val_if_fail(key != NULL, -EINVAL);
-
-       int res = -ENOENT;
-
-       GHashTable *kvhash = NULL;
-
-       g_mutex_lock (&mutex);
-
-       if (!nodename || !nodename[0] || !strcmp(nodename, cfs.nodename)) {
-               kvhash = cfs_status.kvhash;
-       } else if (cfs_status.clinfo && cfs_status.clinfo->nodes_byname) {
-               cfs_clnode_t *clnode;
-               if ((clnode = g_hash_table_lookup(cfs_status.clinfo->nodes_byname, nodename)))
-                       kvhash = clnode->kvhash;
-       }
-
-       kventry_t *entry;
-       if (kvhash && (entry = (kventry_t *)g_hash_table_lookup(kvhash, key))) {
-               g_string_append_len(str, entry->data, entry->len);
-               res = 0;
-       }
-
-       g_mutex_unlock (&mutex);
-
-       return res;
-}
-
-int
-cfs_status_set(
-       const char *key,
-       gpointer data,
-       size_t len)
-{
-       g_return_val_if_fail(key != NULL, FALSE);
-       g_return_val_if_fail(data != NULL, FALSE);
-       g_return_val_if_fail(cfs_status.kvhash != NULL, FALSE);
-
-       if (len > CFS_MAX_STATUS_SIZE)
-               return -EFBIG;
-
-       g_mutex_lock (&mutex);
-
-       gboolean res;
-
-       if (strncmp(key, "rrd/", 4) == 0) {
-               res = rrdentry_hash_set(cfs_status.rrdhash, key + 4, data, len);
-       } else if (!strcmp(key, "nodeip")) {
-               res = nodeip_hash_set(cfs_status.iphash, cfs.nodename, data, len);
-       } else {
-               res = kventry_hash_set(cfs_status.kvhash, key, data, len);
-       }
-       g_mutex_unlock (&mutex);
-
-       if (cfs_status.kvstore)
-               kvstore_send_update_message(cfs_status.kvstore, key, data, len);
-
-       return res ? 0 : -ENOMEM;
-}
-
-gboolean
-cfs_kvstore_node_set(
-       uint32_t nodeid,
-       const char *key,
-       gconstpointer data,
-       size_t len)
-{
-       g_return_val_if_fail(nodeid != 0, FALSE);
-       g_return_val_if_fail(key != NULL, FALSE);
-       g_return_val_if_fail(data != NULL, FALSE);
-
-       g_mutex_lock (&mutex);
-
-       if (!cfs_status.clinfo || !cfs_status.clinfo->nodes_byid)
-               goto ret; /* ignore */
-
-       cfs_clnode_t *clnode = g_hash_table_lookup(cfs_status.clinfo->nodes_byid, &nodeid);
-       if (!clnode)
-               goto ret; /* ignore */
-
-       cfs_debug("got node %d status update %s", nodeid, key);
-
-       if (strncmp(key, "rrd/", 4) == 0) {
-               rrdentry_hash_set(cfs_status.rrdhash, key + 4, data, len);
-       } else if (!strcmp(key, "nodeip")) {
-               nodeip_hash_set(cfs_status.iphash, clnode->name, data, len);
-       } else {
-               if (!clnode->kvhash) {
-                       if (!(clnode->kvhash = kventry_hash_new())) {
-                               goto ret; /*ignore */
-                       }
-               }
-
-               kventry_hash_set(clnode->kvhash, key, data, len);
-
-       }
-ret:
-       g_mutex_unlock (&mutex);
-
-       return TRUE;
-}
-
-static gboolean
-cfs_kvstore_sync(void)
-{
-       g_return_val_if_fail(cfs_status.kvhash != NULL, FALSE);
-       g_return_val_if_fail(cfs_status.kvstore != NULL, FALSE);
-
-       gboolean res = TRUE;
-
-       g_mutex_lock (&mutex);
-
-       GHashTable *ht = cfs_status.kvhash;
-       GHashTableIter iter;
-       gpointer key, value;
-
-       g_hash_table_iter_init (&iter, ht);
-
-       while (g_hash_table_iter_next (&iter, &key, &value)) {
-               kventry_t *entry = (kventry_t *)value;
-               kvstore_send_update_message(cfs_status.kvstore, entry->key, entry->data, entry->len);
-       }
-
-       g_mutex_unlock (&mutex);
-
-       return res;
-}
-
-static int
-dfsm_deliver(
-       dfsm_t *dfsm,
-       gpointer data,
-       int *res_ptr,
-       uint32_t nodeid,
-       uint32_t pid,
-       uint16_t msg_type,
-       uint32_t msg_time,
-       const void *msg,
-       size_t msg_len)
-{
-       g_return_val_if_fail(dfsm != NULL, -1);
-       g_return_val_if_fail(msg != NULL, -1);
-       g_return_val_if_fail(res_ptr != NULL, -1);
-
-       /* ignore message for ourself */
-       if (dfsm_nodeid_is_local(dfsm, nodeid, pid))
-               goto ret;
-
-       if (msg_type == KVSTORE_MESSAGE_UPDATE) {
-               const char *key;
-               gconstpointer data;
-               guint32 len;
-               if (kvstore_parse_update_message(msg, msg_len, &key, &data, &len)) {
-                       cfs_kvstore_node_set(nodeid, key, data, len);
-               } else {
-                       cfs_critical("cant parse update message");
-               }
-       } else if (msg_type == KVSTORE_MESSAGE_LOG) {
-               cfs_message("received log"); // fixme: remove
-               const clog_entry_t *entry;
-               if ((entry = kvstore_parse_log_message(msg, msg_len))) {
-                       clusterlog_insert(cfs_status.clusterlog, entry);
-               } else {
-                       cfs_critical("cant parse log message");
-               }
-       } else {
-               cfs_critical("received unknown message type %d\n", msg_type);
-               goto fail;
-       }
-
-ret:
-       *res_ptr = 0;
-       return 1;
-
-fail:
-       *res_ptr = -EACCES;
-       return 1;
-}
-
-static void
-dfsm_confchg(
-       dfsm_t *dfsm,
-       gpointer data,
-       const struct cpg_address *member_list,
-       size_t member_list_entries)
-{
-       g_return_if_fail(dfsm != NULL);
-       g_return_if_fail(member_list != NULL);
-
-       cfs_debug("enter %s", __func__);
-
-       g_mutex_lock (&mutex);
-
-       cfs_clinfo_t *clinfo = cfs_status.clinfo;
-
-       if (clinfo && clinfo->nodes_byid) {
-
-               GHashTable *ht = clinfo->nodes_byid;
-               GHashTableIter iter;
-               gpointer key, value;
-
-               g_hash_table_iter_init (&iter, ht);
-
-               while (g_hash_table_iter_next (&iter, &key, &value)) {
-                       cfs_clnode_t *node = (cfs_clnode_t *)value;
-                       node->online = FALSE;
-               }
-
-               for (int i = 0; i < member_list_entries; i++) {
-                       cfs_clnode_t *node;
-                       if ((node = g_hash_table_lookup(clinfo->nodes_byid, &member_list[i].nodeid))) {
-                               node->online = TRUE;
-                       }
-               }
-
-               cfs_status.clinfo_version++;
-       }
-
-       g_mutex_unlock (&mutex);
-}
-
-static gpointer
-dfsm_get_state(
-       dfsm_t *dfsm,
-       gpointer data,
-       unsigned int *res_len)
-{
-       g_return_val_if_fail(dfsm != NULL, NULL);
-
-       gpointer msg = clusterlog_get_state(cfs_status.clusterlog, res_len);
-
-       return msg;
-}
-
-static int
-dfsm_process_update(
-       dfsm_t *dfsm,
-       gpointer data,
-       dfsm_sync_info_t *syncinfo,
-       uint32_t nodeid,
-       uint32_t pid,
-       const void *msg,
-       size_t msg_len)
-{
-       cfs_critical("%s: received unexpected update message", __func__);
-
-       return -1;
-}
-
-static int
-dfsm_process_state_update(
-       dfsm_t *dfsm,
-       gpointer data,
-       dfsm_sync_info_t *syncinfo)
-{
-       g_return_val_if_fail(dfsm != NULL, -1);
-       g_return_val_if_fail(syncinfo != NULL, -1);
-
-       clog_base_t *clog[syncinfo->node_count];
-
-       int local_index = -1;
-       for (int i = 0; i < syncinfo->node_count; i++) {
-               dfsm_node_info_t *ni = &syncinfo->nodes[i];
-               ni->synced = 1;
-
-               if (syncinfo->local == ni)
-                       local_index = i;
-
-               clog_base_t *base = (clog_base_t *)ni->state;
-               if (ni->state_len > 8 && ni->state_len == clog_size(base)) {
-                       clog[i] = ni->state;
-               } else {
-                       cfs_critical("received log with wrong size %u", ni->state_len);
-                       clog[i] = NULL;
-               }
-       }
-
-       if (!clusterlog_merge(cfs_status.clusterlog, clog, syncinfo->node_count, local_index)) {
-               cfs_critical("unable to merge log files");
-       }
-
-       cfs_kvstore_sync();
-
-       return 1;
-}
-
-static int
-dfsm_commit(
-       dfsm_t *dfsm,
-       gpointer data,
-       dfsm_sync_info_t *syncinfo)
-{
-       g_return_val_if_fail(dfsm != NULL, -1);
-       g_return_val_if_fail(syncinfo != NULL, -1);
-
-       return 1;
-}
-
-static void
-dfsm_synced(dfsm_t *dfsm)
-{
-       g_return_if_fail(dfsm != NULL);
-
-       char *ip = (char *)g_hash_table_lookup(cfs_status.iphash, cfs.nodename);
-       if (!ip) 
-               ip = cfs.ip;
-
-       cfs_status_set("nodeip", ip, strlen(ip) + 1);
-}
-
-static int
-dfsm_cleanup(
-       dfsm_t *dfsm,
-       gpointer data,
-       dfsm_sync_info_t *syncinfo)
-{
-       return 1;
-}
-
-static dfsm_callbacks_t kvstore_dfsm_callbacks = {
-       .dfsm_deliver_fn = dfsm_deliver,
-       .dfsm_confchg_fn = dfsm_confchg,
-
-       .dfsm_get_state_fn = dfsm_get_state,
-       .dfsm_process_state_update_fn = dfsm_process_state_update,
-       .dfsm_process_update_fn = dfsm_process_update,
-       .dfsm_commit_fn = dfsm_commit,
-       .dfsm_cleanup_fn = dfsm_cleanup,
-       .dfsm_synced_fn = dfsm_synced,
-};
-
-dfsm_t *
-cfs_status_dfsm_new(void)
-{
-       g_mutex_lock (&mutex);
-
-       cfs_status.kvstore = dfsm_new(NULL, KVSTORE_CPG_GROUP_NAME, G_LOG_DOMAIN,
-                                     0, &kvstore_dfsm_callbacks);
-       g_mutex_unlock (&mutex);
-
-       return cfs_status.kvstore;
-}
-
-gboolean
-cfs_is_quorate(void)
-{
-       g_mutex_lock (&mutex);
-       gboolean res =  cfs_status.quorate;
-       g_mutex_unlock (&mutex);
-
-       return res;
-}
-
-void
-cfs_set_quorate(
-       uint32_t quorate,
-       gboolean quiet)
-{
-       g_mutex_lock (&mutex);
-
-       uint32_t prev_quorate = cfs_status.quorate;
-       cfs_status.quorate = quorate;
-
-       if (!prev_quorate && cfs_status.quorate) {
-               if (!quiet)
-                       cfs_message("node has quorum");
-       }
-
-       if (prev_quorate && !cfs_status.quorate) {
-               if (!quiet)
-                       cfs_message("node lost quorum");
-       }
-
-       g_mutex_unlock (&mutex);
-}
diff --git a/data/src/status.h b/data/src/status.h
deleted file mode 100644 (file)
index 041cb34..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
-  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
-
-  This program is free software: you can redistribute it and/or modify
-  it under the terms of the GNU Affero General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  This program is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Affero General Public License for more details.
-
-  You should have received a copy of the GNU Affero General Public License
-  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-  Author: Dietmar Maurer <dietmar@proxmox.com>
-
-*/
-
-#ifndef _PVE_STATUS_H_
-#define _PVE_STATUS_H_
-
-#include <glib.h>
-
-#include "dfsm.h"
-#include "logger.h"
-#include "memdb.h"
-
-#define VMTYPE_QEMU 1
-// FIXME: remove openvz stuff for 7.x
-#define VMTYPE_OPENVZ 2
-#define VMTYPE_LXC 3
-
-#define CFS_MAX_STATUS_SIZE (32*1024)
-
-typedef struct cfs_clnode cfs_clnode_t;
-typedef struct cfs_clinfo cfs_clinfo_t;
-
-void 
-cfs_status_init(void);
-
-void 
-cfs_status_cleanup(void);
-
-dfsm_t *
-cfs_status_dfsm_new(void);
-
-void
-cfs_cluster_log(clog_entry_t *entry);
-
-void 
-cfs_cluster_log_dump(
-       GString *str, 
-       const char *user, 
-       guint max_entries);
-
-void
-cfs_rrd_dump(GString *str); 
-
-int
-cfs_status_set(
-       const char *key,
-       gpointer data,
-       size_t len);
-
-void 
-cfs_status_set_clinfo(
-       cfs_clinfo_t *clinfo);
-
-void 
-cfs_status_set_vmlist(
-       GHashTable *vmlist);
-
-cfs_clnode_t *
-cfs_clnode_new(
-       const char *name, 
-       uint32_t nodeid, 
-       uint32_t votes);
-
-void 
-cfs_clnode_destroy(
-       cfs_clnode_t *clnode);
-
-cfs_clinfo_t *
-cfs_clinfo_new(
-       const char *cluster_name, 
-       uint32_t cman_version);
-
-gboolean 
-cfs_clinfo_destroy(
-       cfs_clinfo_t *clinfo);
-
-gboolean 
-cfs_clinfo_add_node(
-       cfs_clinfo_t *clinfo,
-       cfs_clnode_t *clnode);
-
-void 
-cfs_set_quorate(
-       uint32_t quorate, 
-       gboolean quiet);
-
-gboolean
-cfs_is_quorate(void);
-
-GHashTable *
-vmlist_hash_new(void);
-
-gboolean 
-vmlist_hash_insert_vm(
-       GHashTable *vmlist,
-       int vmtype, 
-       guint32 vmid, 
-       const char *nodename,
-       gboolean replace);
-
-void 
-vmlist_register_vm(
-       int vmtype, 
-       guint32 vmid, 
-       const char *nodename);
-
-void
-vmlist_delete_vm(
-       guint32 vmid);
-
-gboolean
-vmlist_vm_exists(
-       guint32 vmid);
-
-gboolean
-vmlist_different_vm_exists(
-       int vmtype,
-       guint32 vmid,
-       const char *nodename);
-
-void 
-record_memdb_change(const char *path);
-
-void 
-record_memdb_reload(void);
-
-
-int
-cfs_create_status_msg(
-       GString *str,
-       const char *nodename,
-       const char *key);
-
-int
-cfs_create_version_msg(
-       GString *str);
-
-int
-cfs_create_vmlist_msg(
-       GString *str);
-
-int
-cfs_create_memberlist_msg(
-       GString *str);
-
-int
-cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid);
-
-int
-cfs_create_guest_conf_properties_msg(GString *str, memdb_t *memdb, const char **props, uint8_t num_props, uint32_t vmid);
-
-#endif /* _PVE_STATUS_H_ */
diff --git a/data/test/Makefile b/data/test/Makefile
deleted file mode 100644 (file)
index 1948a88..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-all:
-
-.PHONY: check install clean distclean
-check:
-       ./corosync_parser_test.pl
-
-distclean: clean
-clean:
diff --git a/data/test/corosync_configs/multiple-nodes.conf b/data/test/corosync_configs/multiple-nodes.conf
deleted file mode 100644 (file)
index c951f5b..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-logging {
-  debug: off
-  to_syslog: yes
-}
-
-nodelist {
-  node {
-    name: prox1
-    nodeid: 1
-    quorum_votes: 1
-    ring0_addr: prox1
-    ring1_addr: prox1-ring1
-  }
-  node {
-    name: prox2
-    nodeid: 1
-    quorum_votes: 1
-    ring0_addr: prox2
-    ring1_addr: prox2-ring1
-  }
-  node {
-    name: prox3
-    nodeid: 1
-    quorum_votes: 1
-    ring0_addr: prox3
-    ring1_addr: prox3-ring1
-  }
-  node {
-    name: prox4
-    nodeid: 1
-    quorum_votes: 1
-    ring0_addr: prox4
-    ring1_addr: prox4-ring1
-  }
-}
-
-quorum {
-  provider: corosync_votequorum
-}
-
-totem {
-  cluster_name: cloud
-  config_version: 1
-  interface {
-    bindnetaddr: 192.168.0.42
-    ringnumber: 0
-  }
-  interface {
-    bindnetaddr: 192.168.1.42
-    ringnumber: 1
-  }
-  ip_version: ipv4
-  secauth: on
-  version: 2
-}
-
diff --git a/data/test/corosync_configs/old.conf b/data/test/corosync_configs/old.conf
deleted file mode 100644 (file)
index 7b12644..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-logging {
-  debug: off
-  to_syslog: yes
-}
-
-nodelist {
-  node {
-    nodeid: 1
-    quorum_votes: 1
-    ring0_addr: prox1
-  }
-}
-
-quorum {
-  provider: corosync_votequorum
-}
-
-totem {
-  cluster_name: sea
-  config_version: 1
-  interface {
-    bindnetaddr: 192.168.0.42
-    ringnumber: 0
-  }
-  ip_version: ipv4
-  secauth: on
-  version: 2
-}
-
diff --git a/data/test/corosync_configs/single-node.conf b/data/test/corosync_configs/single-node.conf
deleted file mode 100644 (file)
index c65a3e5..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-logging {
-  debug: off
-  to_syslog: yes
-}
-
-nodelist {
-  node {
-    name: prox1
-    nodeid: 1
-    quorum_votes: 1
-    ring0_addr: prox1
-  }
-}
-
-quorum {
-  provider: corosync_votequorum
-}
-
-totem {
-  cluster_name: cloud
-  config_version: 1
-  interface {
-    bindnetaddr: 192.168.0.42
-    ringnumber: 0
-  }
-  ip_version: ipv4
-  secauth: on
-  version: 2
-}
-
diff --git a/data/test/corosync_parser_test.pl b/data/test/corosync_parser_test.pl
deleted file mode 100755 (executable)
index 6999d3a..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-#!/usr/bin/perl
-
-use lib '..';
-
-use strict;
-use warnings;
-
-use Test::MockModule;
-use Test::More;
-
-use PVE::Corosync;
-
-my $known_hosts = {
-    "prox1" => "127.0.1.1",
-    "prox1-ring1" => "127.0.2.1",
-    "prox2" => "127.0.1.2",
-    "prox2-ring1" => "127.0.2.2",
-    "prox3" => "127.0.1.3",
-    "prox3-ring1" => "127.0.2.3",
-    "prox4" => "127.0.1.4",
-    "prox4-ring1" => "127.0.2.4",
-};
-
-sub mocked_resolve {
-    my ($hostname) = @_;
-
-    foreach my $host (keys %$known_hosts) {
-       return $known_hosts->{$host} if $hostname eq $host;
-    }
-
-    die "got unknown hostname '$hostname' during mocked resolve_hostname_like_corosync";
-}
-
-my $mocked_corosync_module = new Test::MockModule('PVE::Corosync');
-$mocked_corosync_module->mock('resolve_hostname_like_corosync', \&mocked_resolve);
-
-sub parser_self_check {
-    my $cfg_fn = shift;
-
-    my $outfile = "$cfg_fn.write";
-    my ($config1, $config2, $raw1, $raw2);
-
-    eval {
-       # read first time
-       $raw1 = PVE::Tools::file_get_contents($cfg_fn);
-       $config1 = PVE::Corosync::parse_conf($cfg_fn, $raw1);
-
-       # write config
-       $raw2 = PVE::Corosync::write_conf(undef, $config1);
-       # do not actually write cfg, but you can outcomment to do so, e.g. if
-       # you want to use diff for easy comparision
-       #PVE::Tools::file_set_contents($outfile, $raw2);
-
-       # reparse written config (must be the same as config1)
-       $config2 = PVE::Corosync::parse_conf(undef, $raw2);
-    }; warn $@ if $@;
-
-    # test verify_config
-    my ($err, $warn) = PVE::Corosync::verify_conf($config1);
-    die "verify_conf failed: " . join(" ++ ", @$err) if scalar(@$err);
-
-    # do not care for whitespace differences
-    delete $config1->{digest};
-    delete $config2->{digest};
-
-    is_deeply($config1, $config2, "self check hash: $cfg_fn");
-
-    # do not care about extra new lines
-    $raw1 =~ s/^\s*\n+//mg;
-    $raw2 =~ s/^\s*\n+//mg;
-
-    is($raw1, $raw2, "self check raw: $cfg_fn");
-}
-
-# exec tests
-if (my $file = shift) {
-    parser_self_check($file);
-} else {
-    foreach my $file (<corosync_configs/*.conf>) {
-       parser_self_check($file);
-    }
-}
-
-done_testing();
diff --git a/data/test/scripts/benchmark-config-get-property.pl b/data/test/scripts/benchmark-config-get-property.pl
deleted file mode 100755 (executable)
index f009c5a..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/perl
-
-use lib '../../';
-
-use strict;
-use warnings;
-
-#use Data::Dumper;
-use Time::HiRes qw( gettimeofday tv_interval );
-
-use PVE::Tools;
-use PVE::Cluster;
-use PVE::QemuConfig;
-use PVE::LXC::Config;
-
-sub sec_to_unit {
-       my $sec = shift;
-
-       my $unit_index = 0;
-       while ($sec < 1) {
-               $sec *= 1000;
-               $unit_index++;
-       }
-
-       my $unit = @{['s', 'ms', 'us', 'ns', 'ps']}[$unit_index];
-
-       return wantarray ? ($sec, $unit) : "$sec $unit";
-
-}
-
-my $results = {};
-
-sub perf {
-    my ($name, $loops, $code) = @_;
-
-    return if !defined($loops) || $loops <= 0;
-
-    my $loop = 0;
-    eval {
-       my $t0 = [gettimeofday];
-
-       for (my $i = 0; $i<$loops; $i++) {
-               $code->();
-       }
-
-       my $elapsed = tv_interval ($t0, [gettimeofday]);
-
-       my $total = sec_to_unit($elapsed);
-       my $per_loop = $elapsed/$loops;
-       $loop = sec_to_unit($per_loop);
-
-       $results->{$name} = [ $elapsed * 1000, $per_loop * 1000 ];
-
-       print STDERR "elapsed['$name' x $loops]: $total => $loop/loop\n";
-    }; warn $@ if $@;
-
-    return $loop;
-}
-
-my $loops = shift // 3;
-my $vmid = shift // 0;
-my $prop = shift // 'lock';
-
-perf('cfg-get-prop', $loops, sub {
-    my $res = PVE::Cluster::get_guest_config_property($prop, $vmid);
-});
-
-PVE::Cluster::cfs_update();
-perf('perl-manual', $loops, sub {
-    my $res = {};
-
-    # modeled after the manager API cluster/resource call
-    my $vmlist = PVE::Cluster::get_vmlist() || {};
-    my $idlist = $vmlist->{ids} || {};
-    foreach my $vmid (keys %$idlist) {
-
-       my $data = $idlist->{$vmid};
-       my $typedir = $data->{type} eq 'qemu' ? 'qemu-server' : 'lxc';
-
-       my $conf = PVE::Cluster::cfs_read_file("nodes/$data->{node}/$typedir/$vmid.conf");
-
-       my $v = $conf->{$prop};
-       $res->{$vmid} = { $prop => $v } if defined($v);
-    }
-});
-#PVE::Cluster::get_tasklist('dev5');
-
-my $a = $results->{'cfg-get-prop'};
-my $b = $results->{'perl-manual'};
-printf("$loops\t%.2f\t%.2f\t%.2f\t%.2f\n", $a->[0], $a->[1], $b->[0], $b->[1]);
-
-#print "res: " . Dumper($res) ."\n";
diff --git a/data/test/scripts/test-broadcast-kv.pl b/data/test/scripts/test-broadcast-kv.pl
deleted file mode 100755 (executable)
index d2166a2..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/perl
-
-use lib '../../';
-
-use strict;
-use warnings;
-
-use JSON;
-
-use PVE::Tools;
-use PVE::Cluster;
-
-## first broadcast a value for a key then you can check if you get it back by
-# omitting the value, or directly querys an already exisitng value (e.g., ceph
-# stats)
-
-my $k = shift // die "no key";
-my $v = shift;
-
-if (defined $v) {
-    print "broadcasting kv pair ($k, $v)\n";
-    PVE::Cluster::broadcast_node_kv($k, $v);
-}
-
-print "querying value for key: $k\n";
-my $res = PVE::Cluster::get_node_kv($k);
-
-print "res: " . to_json($res, {utf8 => 1, pretty => 1}) ."\n";
diff --git a/data/test/scripts/test-config-get-property.pl b/data/test/scripts/test-config-get-property.pl
deleted file mode 100755 (executable)
index 80a726c..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/perl
-
-use lib '../../';
-
-use strict;
-use warnings;
-
-use JSON;
-
-use PVE::Tools;
-use PVE::Cluster;
-
-## for quick test do:
-# echo 'lock: test' >> /etc/pve/lxc/104.conf
-
-my $vmid = shift // 104;
-my $prop = shift // 'lock';
-
-my $res = PVE::Cluster::get_guest_config_property($prop, $vmid);
-
-print "res: " . to_json($res, {utf8 => 1, pretty => 1}) ."\n";
diff --git a/data/test/scripts/test-verify-token.pl b/data/test/scripts/test-verify-token.pl
deleted file mode 100755 (executable)
index 11f07dc..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/perl
-
-use lib '../../';
-
-use strict;
-use warnings;
-
-use Data::Dumper;
-
-use PVE::Tools;
-use PVE::Cluster;
-
-## For quick test you can add arbitrary fake tokens to token.cfg:
-# echo 'root@pam 1234512345XXXXX' >> /etc/pve/priv/token.cfg
-
-my $token = shift // '1234512345XXXXX';
-my $userid = shift // 'root@pam';
-
-my $res = PVE::Cluster::verify_token($userid, $token);
-
-print "token '$userid $token' ". ($res ? '' : "not " ) ."found\n";
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..50dd6aa
--- /dev/null
@@ -0,0 +1,20 @@
+SUBDIRS := PVE pmxcfs test
+
+export LD_LIBRARY_PATH+=$(CURDIR)/PVE
+export PERLLIB+=$(CURDIR)/PVE
+
+all:
+       set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
+
+.PHONY: install
+install:
+       set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
+
+
+.PHONY: check
+check:
+       set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
+
+.PHONY: clean
+clean:
+       for i in $(SUBDIRS); do $(MAKE) -C $$i $@; done
diff --git a/src/PVE/API2/ClusterConfig.pm b/src/PVE/API2/ClusterConfig.pm
new file mode 100644 (file)
index 0000000..e7efe37
--- /dev/null
@@ -0,0 +1,711 @@
+package PVE::API2::ClusterConfig;
+
+use strict;
+use warnings;
+
+use PVE::Exception;
+use PVE::Tools;
+use PVE::SafeSyslog;
+use PVE::RESTHandler;
+use PVE::RPCEnvironment;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Cluster;
+use PVE::APIClient::LWP;
+use PVE::Corosync;
+use PVE::Cluster::Setup;
+
+use IO::Socket::UNIX;
+
+use base qw(PVE::RESTHandler);
+
+my $clusterconf = "/etc/pve/corosync.conf";
+my $authfile = "/etc/corosync/authkey";
+my $local_cluster_lock = "/var/lock/pvecm.lock";
+
+my $nodeid_desc = {
+    type => 'integer',
+    description => "Node id for this node.",
+    minimum => 1,
+    optional => 1,
+};
+PVE::JSONSchema::register_standard_option("corosync-nodeid", $nodeid_desc);
+
+__PACKAGE__->register_method({
+    name => 'index',
+    path => '',
+    method => 'GET',
+    description => "Directory index.",
+    permissions => {
+       check => ['perm', '/', [ 'Sys.Audit' ]],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => {},
+       },
+       links => [ { rel => 'child', href => "{name}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $result = [
+           { name => 'nodes' },
+           { name => 'totem' },
+           { name => 'join' },
+           { name => 'qdevice' },
+           { name => 'apiversion' },
+       ];
+
+       return $result;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'join_api_version',
+    path => 'apiversion',
+    method => 'GET',
+    description => "Return the version of the cluster join API available on this node.",
+    permissions => {
+       check => ['perm', '/', [ 'Sys.Audit' ]],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => {
+       type => 'integer',
+       minimum => 0,
+       description => "Cluster Join API version, currently " . PVE::Cluster::Setup::JOIN_API_VERSION,
+    },
+    code => sub {
+       return PVE::Cluster::Setup::JOIN_API_VERSION;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'create',
+    path => '',
+    method => 'POST',
+    protected => 1,
+    description => "Generate new cluster configuration. If no links given,"
+                . " default to local IP address as link0.",
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::Corosync::add_corosync_link_properties({
+           clustername => {
+               description => "The name of the cluster.",
+               type => 'string', format => 'pve-node',
+               maxLength => 15,
+           },
+           nodeid => get_standard_option('corosync-nodeid'),
+           votes => {
+               type => 'integer',
+               description => "Number of votes for this node.",
+               minimum => 1,
+               optional => 1,
+           },
+       }),
+    },
+    returns => { type => 'string' },
+    code => sub {
+       my ($param) = @_;
+
+       die "cluster config '$clusterconf' already exists\n" if -f $clusterconf;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+       my $code = sub {
+           STDOUT->autoflush();
+           PVE::Cluster::Setup::setup_sshd_config(1);
+           PVE::Cluster::Setup::setup_rootsshconfig();
+           PVE::Cluster::Setup::setup_ssh_keys();
+
+           PVE::Tools::run_command(['/usr/sbin/corosync-keygen', '-lk', $authfile])
+               if !-f $authfile;
+           die "no authentication key available\n" if -f !$authfile;
+
+           my $nodename = PVE::INotify::nodename();
+
+           # get the corosync basis config for the new cluster
+           my $config = PVE::Corosync::create_conf($nodename, $param);
+
+           print "Writing corosync config to /etc/pve/corosync.conf\n";
+           PVE::Corosync::atomic_write_conf($config);
+
+           my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
+           PVE::Cluster::Setup::ssh_merge_keys();
+           PVE::Cluster::Setup::gen_pve_node_files($nodename, $local_ip_address);
+           PVE::Cluster::Setup::ssh_merge_known_hosts($nodename, $local_ip_address, 1);
+
+           print "Restart corosync and cluster filesystem\n";
+           PVE::Tools::run_command('systemctl restart corosync pve-cluster');
+       };
+
+       my $worker = sub {
+           PVE::Tools::lock_file($local_cluster_lock, 10, $code);
+           die $@ if $@;
+       };
+
+       return $rpcenv->fork_worker('clustercreate', $param->{clustername},  $authuser, $worker);
+}});
+
+__PACKAGE__->register_method({
+    name => 'nodes',
+    path => 'nodes',
+    method => 'GET',
+    description => "Corosync node list.",
+    permissions => {
+       check => ['perm', '/', [ 'Sys.Audit' ]],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => {
+               node => { type => 'string' },
+           },
+       },
+       links => [ { rel => 'child', href => "{node}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+
+       my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
+       my $nodelist = PVE::Corosync::nodelist($conf);
+
+       return PVE::RESTHandler::hash_to_array($nodelist, 'node');
+    }});
+
+# lock method to ensure local and cluster wide atomicity
+# if we're a single node cluster just lock locally, we have no other cluster
+# node which we could contend with, else also acquire a cluster wide lock
+my $config_change_lock = sub {
+    my ($code) = @_;
+
+    PVE::Tools::lock_file($local_cluster_lock, 10, sub {
+       PVE::Cluster::cfs_update(1);
+       my $members = PVE::Cluster::get_members();
+       if (scalar(keys %$members) > 1) {
+           my $res = PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code);
+
+           # cfs_lock_file only sets $@ but lock_file doesn't propagates $@ unless we die here
+           die $@ if defined($@);
+
+           return $res;
+       } else {
+           return $code->();
+       }
+    });
+};
+
+__PACKAGE__->register_method ({
+    name => 'addnode',
+    path => 'nodes/{node}',
+    method => 'POST',
+    protected => 1,
+    description => "Adds a node to the cluster configuration. This call is for internal use.",
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::Corosync::add_corosync_link_properties({
+           node => get_standard_option('pve-node'),
+           nodeid => get_standard_option('corosync-nodeid'),
+           votes => {
+               type => 'integer',
+               description => "Number of votes for this node",
+               minimum => 0,
+               optional => 1,
+           },
+           force => {
+               type => 'boolean',
+               description => "Do not throw error if node already exists.",
+               optional => 1,
+           },
+           new_node_ip => {
+               type => 'string',
+               description => "IP Address of node to add. Used as fallback if no links are given.",
+               format => 'ip',
+               optional => 1,
+           },
+           apiversion => {
+               type => 'integer',
+               description => 'The JOIN_API_VERSION of the new node.',
+               optional => 1,
+           },
+       }),
+    },
+    returns => {
+       type => "object",
+       properties => {
+           corosync_authkey => {
+               type => 'string',
+           },
+           corosync_conf => {
+               type => 'string',
+           },
+           warnings => {
+               type => 'array',
+               items => {
+                   type => 'string',
+               },
+           },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       $param->{apiversion} //= 0;
+       PVE::Cluster::Setup::assert_node_can_join_our_version($param->{apiversion});
+
+       PVE::Cluster::check_cfs_quorum();
+
+       my $vc_errors;
+       my $vc_warnings;
+
+       my $code = sub {
+           my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
+           my $nodelist = PVE::Corosync::nodelist($conf);
+           my $totem_cfg = PVE::Corosync::totem_config($conf);
+
+           ($vc_errors, $vc_warnings) = PVE::Corosync::verify_conf($conf);
+           die if scalar(@$vc_errors);
+
+           my $name = $param->{node};
+
+           # ensure we do not reuse an address, that can crash the whole cluster!
+           my $check_duplicate_addr = sub {
+               my $link = shift;
+               return if !defined($link) || !defined($link->{address});
+               my $addr = $link->{address};
+
+               while (my ($k, $v) = each %$nodelist) {
+                   next if $k eq $name; # allows re-adding a node if force is set
+
+                   for my $linknumber (0..PVE::Corosync::MAX_LINK_INDEX) {
+                       my $id = "ring${linknumber}_addr";
+                       next if !defined($v->{$id});
+
+                       die "corosync: address '$addr' already used on link $id by node '$k'\n"
+                           if $v->{$id} eq $addr;
+                   }
+               }
+           };
+
+           my $links = PVE::Corosync::extract_corosync_link_args($param);
+           foreach my $link (values %$links) {
+               $check_duplicate_addr->($link);
+           }
+
+           # Make sure that the newly added node has links compatible with the
+           # rest of the cluster. To start, extract all links that currently
+           # exist. Check any node, all have the same links here (because of
+           # verify_conf above).
+           my $node_options = (values %$nodelist)[0];
+           my $cluster_links = {};
+           foreach my $opt (keys %$node_options) {
+               my ($linktype, $linkid) = PVE::Corosync::parse_link_entry($opt);
+               next if !defined($linktype);
+               $cluster_links->{$linkid} = $node_options->{$opt};
+           }
+
+           # in case no fallback IP was passed, but instead only a single link,
+           # treat it as fallback to allow link-IDs to be matched automatically
+           # FIXME: remove in 8.0 or when joining an old node not supporting
+           # new_node_ip becomes infeasible otherwise
+           my $legacy_fallback = 0;
+           if (!$param->{new_node_ip} && scalar(%$links) == 1 && $param->{apiversion} == 0) {
+               my $passed_link_id = (keys %$links)[0];
+               my $passed_link = delete $links->{$passed_link_id};
+               $param->{new_node_ip} = $passed_link->{address};
+               $legacy_fallback = 1;
+           }
+
+           if (scalar(%$links)) {
+               # verify specified links exist and none are missing
+               for my $linknum (0..PVE::Corosync::MAX_LINK_INDEX) {
+                   my $have_cluster_link = defined($cluster_links->{$linknum});
+                   my $have_new_link = defined($links->{$linknum});
+
+                   die "corosync: link $linknum exists in cluster config but wasn't specified for new node\n"
+                       if $have_cluster_link && !$have_new_link;
+                   die "corosync: link $linknum specified for new node doesn't exist in cluster config\n"
+                       if !$have_cluster_link && $have_new_link;
+               }
+           } else {
+               # when called without any link parameters, fall back to
+               # new_node_ip, if all existing nodes only have a single link too
+               die "no links and no fallback ip (new_node_ip) given, cannot join cluster\n"
+                   if !$param->{new_node_ip};
+
+               my $cluster_link_count = scalar(%$cluster_links);
+               if ($cluster_link_count == 1) {
+                   my $linknum = (keys %$cluster_links)[0];
+                   $links->{$linknum} = { address => $param->{new_node_ip} };
+               } else {
+                   die "cluster has $cluster_link_count links, but only 1 given"
+                       if $legacy_fallback;
+                   die "no links given but multiple links found on other nodes,"
+                     . " fallback only supported on single-link clusters\n";
+               }
+           }
+
+           if (defined(my $res = $nodelist->{$name})) {
+               $param->{nodeid} = $res->{nodeid} if !$param->{nodeid};
+               $param->{votes} = $res->{quorum_votes} if !defined($param->{votes});
+
+               if ($res->{quorum_votes} == $param->{votes} &&
+                   $res->{nodeid} == $param->{nodeid} && $param->{force}) {
+                   print "forcing overwrite of configured node '$name'\n";
+               } else {
+                   die "can't add existing node '$name'\n";
+               }
+           } elsif (!$param->{nodeid}) {
+               my $nodeid = 1;
+
+               while(1) {
+                   my $found = 0;
+                   foreach my $v (values %$nodelist) {
+                       if ($v->{nodeid} eq $nodeid) {
+                           $found = 1;
+                           $nodeid++;
+                           last;
+                       }
+                   }
+                   last if !$found;
+               };
+
+               $param->{nodeid} = $nodeid;
+           }
+
+           $param->{votes} = 1 if !defined($param->{votes});
+
+           PVE::Cluster::Setup::gen_local_dirs($name);
+
+           eval { PVE::Cluster::Setup::ssh_merge_keys(); };
+           warn $@ if $@;
+
+           $nodelist->{$name} = {
+               nodeid => $param->{nodeid},
+               name => $name,
+           };
+           $nodelist->{$name}->{quorum_votes} = $param->{votes} if $param->{votes};
+
+           foreach my $link (keys %$links) {
+               $nodelist->{$name}->{"ring${link}_addr"} = $links->{$link}->{address};
+           }
+
+           PVE::Cluster::log_msg('notice', 'root@pam', "adding node $name to cluster");
+
+           PVE::Corosync::update_nodelist($conf, $nodelist);
+       };
+
+       $config_change_lock->($code);
+
+       # If vc_errors is set, we died because of verify_conf.
+       # Raise this error, since it contains more information than just a single-line string.
+       if (defined($vc_errors) && scalar(@$vc_errors)) {
+           my $err_hash = {};
+           my $add_errs = sub {
+               my ($type, @arr) = @_;
+               return if !scalar(@arr);
+               $err_hash->{"${type}$_"} = $arr[$_] for 0..$#arr;
+           };
+
+           $add_errs->("warning", @$vc_warnings);
+           $add_errs->("error", @$vc_errors);
+
+           PVE::Exception::raise("invalid corosync.conf\n", errors => $err_hash);
+       }
+
+       die $@ if $@;
+
+       my $res = {
+           corosync_authkey => PVE::Tools::file_get_contents($authfile),
+           corosync_conf => PVE::Tools::file_get_contents($clusterconf),
+           warnings => $vc_warnings,
+       };
+
+       return $res;
+    }});
+
+
+__PACKAGE__->register_method ({
+    name => 'delnode',
+    path => 'nodes/{node}',
+    method => 'DELETE',
+    protected => 1,
+    description => "Removes a node from the cluster configuration.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $local_node = PVE::INotify::nodename();
+       die "Cannot delete myself from cluster!\n" if $param->{node} eq $local_node;
+
+       PVE::Cluster::check_cfs_quorum();
+
+       my $code = sub {
+           my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
+           my $nodelist = PVE::Corosync::nodelist($conf);
+
+           my $node;
+           my $nodeid;
+
+           foreach my $tmp_node (keys %$nodelist) {
+               my $d = $nodelist->{$tmp_node};
+               my $ring0_addr = $d->{ring0_addr};
+               my $ring1_addr = $d->{ring1_addr};
+               if (($tmp_node eq $param->{node}) ||
+                   (defined($ring0_addr) && ($ring0_addr eq $param->{node})) ||
+                   (defined($ring1_addr) && ($ring1_addr eq $param->{node}))) {
+                   $node = $tmp_node;
+                   $nodeid = $d->{nodeid};
+                   last;
+               }
+           }
+
+           die "Node/IP: $param->{node} is not a known host of the cluster.\n"
+               if !defined($node);
+
+           PVE::Cluster::log_msg('notice', 'root@pam', "deleting node $node from cluster");
+
+           delete $nodelist->{$node};
+
+           # allowed to fail when node is already shut down!
+           eval {
+               PVE::Tools::run_command(['corosync-cfgtool','-k', $nodeid])
+                   if defined($nodeid);
+           };
+
+           PVE::Corosync::update_nodelist($conf, $nodelist);
+       };
+
+       $config_change_lock->($code);
+       die $@ if $@;
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'join_info',
+    path => 'join',
+    permissions => {
+       check => ['perm', '/', [ 'Sys.Audit' ]],
+    },
+    method => 'GET',
+    description => "Get information needed to join this cluster over the connected node.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node', {
+               description => "The node for which the joinee gets the nodeinfo. ",
+               default => "current connected node",
+               optional => 1,
+           }),
+       },
+    },
+    returns => {
+       type => 'object',
+       additionalProperties => 0,
+       properties => {
+           nodelist => {
+               type => 'array',
+               items => {
+                   type => "object",
+                   additionalProperties => 1,
+                   properties => {
+                       name => get_standard_option('pve-node'),
+                       nodeid => get_standard_option('corosync-nodeid'),
+                       ring0_addr => get_standard_option('corosync-link'),
+                       quorum_votes => { type => 'integer', minimum => 0 },
+                       pve_addr => { type => 'string', format => 'ip' },
+                       pve_fp => get_standard_option('fingerprint-sha256'),
+                   },
+               },
+           },
+           preferred_node => get_standard_option('pve-node'),
+           totem => { type => 'object' },
+           config_digest => { type => 'string' },
+       },
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $nodename = $param->{node} // PVE::INotify::nodename();
+
+       PVE::Cluster::cfs_update(1);
+       my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
+
+       # FIXME: just return undef or empty object in PVE 8.0 (check if manager can handle it!)
+       PVE::Exception::raise(
+           'node is not in a cluster, no join info available!',
+           code => HTTP::Status::HTTP_FAILED_DEPENDENCY,
+       ) if !($conf && $conf->{main});
+
+       my $totem_cfg = $conf->{main}->{totem} // {};
+       my $nodelist = $conf->{main}->{nodelist}->{node} // {};
+       my $corosync_config_digest = $conf->{digest};
+
+       die "unknown node '$nodename'\n" if ! $nodelist->{$nodename};
+
+       foreach my $name (keys %$nodelist) {
+           my $node = $nodelist->{$name};
+           $node->{pve_fp} = PVE::Cluster::get_node_fingerprint($name);
+           $node->{pve_addr} = scalar(PVE::Cluster::remote_node_ip($name));
+       }
+
+       my $res = {
+           nodelist => [ values %$nodelist ],
+           preferred_node => $nodename,
+           totem => $totem_cfg,
+           config_digest => $corosync_config_digest,
+       };
+
+       return $res;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'join',
+    path => 'join',
+    method => 'POST',
+    protected => 1,
+    description => "Joins this node into an existing cluster. If no links are"
+                . " given, default to IP resolved by node's hostname on single"
+                . " link (fallback fails for clusters with multiple links).",
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::Corosync::add_corosync_link_properties({
+           hostname => {
+               type => 'string',
+               description => "Hostname (or IP) of an existing cluster member."
+           },
+           nodeid => get_standard_option('corosync-nodeid'),
+           votes => {
+               type => 'integer',
+               description => "Number of votes for this node",
+               minimum => 0,
+               optional => 1,
+           },
+           force => {
+               type => 'boolean',
+               description => "Do not throw error if node already exists.",
+               optional => 1,
+           },
+           fingerprint => get_standard_option('fingerprint-sha256'),
+           password => {
+               description => "Superuser (root) password of peer node.",
+               type => 'string',
+               maxLength => 128,
+           },
+       }),
+    },
+    returns => { type => 'string' },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+       my $worker = sub {
+           STDOUT->autoflush();
+           PVE::Tools::lock_file($local_cluster_lock, 10, \&PVE::Cluster::Setup::join, $param);
+           die $@ if $@;
+       };
+
+       return $rpcenv->fork_worker('clusterjoin', undef,  $authuser, $worker);
+    }});
+
+
+__PACKAGE__->register_method({
+    name => 'totem',
+    path => 'totem',
+    method => 'GET',
+    description => "Get corosync totem protocol settings.",
+    permissions => {
+       check => ['perm', '/', [ 'Sys.Audit' ]],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => {
+       type => "object",
+       properties => {},
+    },
+    code => sub {
+       my ($param) = @_;
+
+
+       my $conf = PVE::Cluster::cfs_read_file('corosync.conf');
+
+       my $totem_cfg = $conf->{main}->{totem};
+
+       return $totem_cfg;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'status',
+    path => 'qdevice',
+    method => 'GET',
+    protected => 1,
+    description => 'Get QDevice status',
+    permissions => {
+       check => ['perm', '/', [ 'Sys.Audit' ]],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => {
+       type => "object",
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $result = {};
+       my $socket_path = "/var/run/corosync-qdevice/corosync-qdevice.sock";
+       return $result if !-S $socket_path;
+
+       my $qdevice_socket = IO::Socket::UNIX->new(
+           Type => SOCK_STREAM,
+           Peer => $socket_path,
+       );
+
+       print $qdevice_socket "status verbose\n";
+       my $qdevice_keys = {
+           "Algorithm" => 1,
+           "Echo reply" => 1,
+           "Last poll call" => 1,
+           "Model" => 1,
+           "QNetd host" => 1,
+           "State" => 1,
+           "Tie-breaker" => 1,
+       };
+       while (my $line = <$qdevice_socket>) {
+           chomp $line;
+           next if $line =~ /^\s/;
+           if ($line =~ /^(.*?)\s*:\s*(.*)$/) {
+               $result->{$1} = $2 if $qdevice_keys->{$1};
+           }
+       }
+
+       return $result;
+    }});
+#TODO: possibly add setup and remove methods
+
+
+1;
diff --git a/src/PVE/API2/Makefile b/src/PVE/API2/Makefile
new file mode 100644 (file)
index 0000000..0eeba51
--- /dev/null
@@ -0,0 +1,11 @@
+PVEDIR=${DESTDIR}/usr/share/perl5/PVE
+
+SOURCES=ClusterConfig.pm
+
+.PHONY: install
+install: ${SOURCES}
+       install -d ${PVEDIR}/API2/
+       for f in ${SOURCES}; do install -m 0664 $$f ${PVEDIR}/API2/$$f; done
+
+.PHONY: clean
+clean:
diff --git a/src/PVE/CLI/Makefile b/src/PVE/CLI/Makefile
new file mode 100644 (file)
index 0000000..1e58e2f
--- /dev/null
@@ -0,0 +1,11 @@
+PVEDIR=${DESTDIR}/usr/share/perl5/PVE
+
+SOURCES=pvecm.pm
+
+.PHONY: install
+install: ${SOURCES}
+       install -d -m 0755 ${PVEDIR}/CLI
+       for f in ${SOURCES}; do install -D -m 0644 $$f ${PVEDIR}/CLI/$$f; done
+
+.PHONY: clean
+clean:
diff --git a/src/PVE/CLI/pvecm.pm b/src/PVE/CLI/pvecm.pm
new file mode 100755 (executable)
index 0000000..b0b5931
--- /dev/null
@@ -0,0 +1,717 @@
+package PVE::CLI::pvecm;
+
+use strict;
+use warnings;
+
+use Cwd qw(getcwd);
+use File::Path;
+use File::Basename;
+use PVE::Tools qw(run_command);
+use PVE::Cluster;
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RPCEnvironment;
+use PVE::CLIHandler;
+use PVE::PTY;
+use PVE::API2::ClusterConfig;
+use PVE::Corosync;
+use PVE::Cluster::Setup;
+
+use base qw(PVE::CLIHandler);
+
+$ENV{HOME} = '/root'; # for ssh-copy-id
+
+my $basedir = "/etc/pve";
+my $clusterconf = "$basedir/corosync.conf";
+my $libdir = "/var/lib/pve-cluster";
+my $authfile = "/etc/corosync/authkey";
+
+
+sub setup_environment {
+    PVE::RPCEnvironment->setup_default_cli_env();
+}
+
+__PACKAGE__->register_method ({
+    name => 'keygen',
+    path => 'keygen',
+    method => 'PUT',
+    description => "Generate new cryptographic key for corosync.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           filename => {
+               type => 'string',
+               description => "Output file name"
+           }
+       },
+    },
+    returns => { type => 'null' },
+
+    code => sub {
+       my ($param) = @_;
+
+       my $filename = $param->{filename};
+
+       # test EUID
+       $> == 0 || die "Error: Authorization key must be generated as root user.\n";
+       my $dirname = dirname($filename);
+
+       die "key file '$filename' already exists\n" if -e $filename;
+
+       File::Path::make_path($dirname) if $dirname;
+
+       run_command(['corosync-keygen', '-l', '-k', $filename]);
+
+       return undef;
+    }});
+
+my $foreach_member = sub {
+    my ($code, $noerr) = @_;
+
+    my $members = PVE::Cluster::get_members();
+    foreach my $node (sort keys %$members) {
+       if (my $ip = $members->{$node}->{ip}) {
+           $code->($node, $ip);
+       } else {
+           die "cannot get the cluster IP for node '$node'.\n" if !$noerr;
+           warn "cannot get the cluster IP for node '$node'.\n";
+           return undef;
+       }
+    }
+};
+
+__PACKAGE__->register_method ({
+    name => 'setup_qdevice',
+    path => 'setup_qdevice',
+    method => 'PUT',
+    description => "Setup the use of a QDevice",
+    parameters => {
+        additionalProperties => 0,
+       properties => {
+           address => {
+               type => 'string', format => 'ip',
+               description => "Specifies the network address of an external corosync QDevice" ,
+           },
+           network => {
+               type => 'string',
+               format => 'CIDR',
+               description => 'The network which should be used to connect to the external qdevice',
+               optional => 1,
+           },
+           force => {
+               type => 'boolean',
+               description => "Do not throw error on possible dangerous operations.",
+               optional => 1,
+           },
+       },
+    },
+    returns => { type => 'null' },
+
+    code => sub {
+       my ($param) = @_;
+
+       PVE::Corosync::check_conf_exists();
+
+       my $members = PVE::Cluster::get_members();
+       foreach my $node (sort keys %$members) {
+           die "All nodes must be online! Node $node is offline, aborting.\n"
+               if !$members->{$node}->{online};
+       }
+
+       my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
+
+       die "QDevice already configured!\n"
+           if defined($conf->{main}->{quorum}->{device}) && !$param->{force};
+
+       my $network = $param->{network};
+
+       my $model = "net";
+       my $algorithm = 'ffsplit';
+       if (scalar(%{$members}) & 1) {
+           if ($param->{force}) {
+               $algorithm = 'lms';
+           } else {
+               die "Clusters with an odd node count are not officially supported!\n";
+           }
+       }
+
+       my $qnetd_addr = $param->{address};
+       my $base_dir = "/etc/corosync/qdevice/net";
+       my $db_dir_qnetd = "/etc/corosync/qnetd/nssdb";
+       my $db_dir_node = "$base_dir/nssdb";
+       my $ca_export_base = "qnetd-cacert.crt";
+       my $ca_export_file = "$db_dir_qnetd/$ca_export_base";
+       my $crq_file_base = "qdevice-net-node.crq";
+       my $p12_file_base = "qdevice-net-node.p12";
+       my $qdevice_certutil = "corosync-qdevice-net-certutil";
+       my $qnetd_certutil= "corosync-qnetd-certutil";
+       my $clustername = $conf->{main}->{totem}->{cluster_name};
+
+       run_command(['ssh-copy-id', '-i', '/root/.ssh/id_rsa', "root\@$qnetd_addr"]);
+
+       if (-d $db_dir_node) {
+           # FIXME: check on all nodes?!
+           if ($param->{force}) {
+               rmtree $db_dir_node;
+           } else {
+               die "QDevice certificate store already initialised, set force to delete!\n";
+           }
+       }
+
+       my $ssh_cmd = ['ssh', '-o', 'BatchMode=yes', '-lroot'];
+       my $scp_cmd = ['scp', '-o', 'BatchMode=yes'];
+
+       print "\nINFO: initializing qnetd server\n";
+       run_command(
+           [@$ssh_cmd, $qnetd_addr, $qnetd_certutil, "-i"],
+           noerr => 1
+       );
+
+       print "\nINFO: copying CA cert and initializing on all nodes\n";
+       run_command([@$scp_cmd, "root\@\[$qnetd_addr\]:$ca_export_file", "/etc/pve/$ca_export_base"]);
+       $foreach_member->(sub {
+           my ($node, $ip) = @_;
+           my $outsub = sub { print "\nnode '$node': " . shift };
+           run_command(
+               [@$ssh_cmd, $ip, $qdevice_certutil, "-i", "-c", "/etc/pve/$ca_export_base"],
+               noerr => 1, outfunc => \&$outsub
+           );
+       });
+       unlink "/etc/pve/$ca_export_base";
+
+       print "\nINFO: generating cert request\n";
+       run_command([$qdevice_certutil, "-r", "-n", $clustername]);
+
+       print "\nINFO: copying exported cert request to qnetd server\n";
+       run_command([@$scp_cmd, "$db_dir_node/$crq_file_base", "root\@\[$qnetd_addr\]:/tmp"]);
+
+       print "\nINFO: sign and export cluster cert\n";
+       run_command([
+               @$ssh_cmd, $qnetd_addr, $qnetd_certutil, "-s", "-c",
+               "/tmp/$crq_file_base", "-n", "$clustername"
+           ]);
+
+       print "\nINFO: copy exported CRT\n";
+       run_command([
+               @$scp_cmd, "root\@\[$qnetd_addr\]:$db_dir_qnetd/cluster-$clustername.crt",
+               "$db_dir_node"
+           ]);
+
+       print "\nINFO: import certificate\n";
+       run_command(["$qdevice_certutil", "-M", "-c", "$db_dir_node/cluster-$clustername.crt"]);
+
+       print "\nINFO: copy and import pk12 cert to all nodes\n";
+       run_command([@$scp_cmd, "$db_dir_node/$p12_file_base", "/etc/pve/"]);
+       $foreach_member->(sub {
+           my ($node, $ip) = @_;
+           my $outsub = sub { print "\nnode '$node': " . shift };
+           run_command([
+                   @$ssh_cmd, $ip, "$qdevice_certutil", "-m", "-c",
+                   "/etc/pve/$p12_file_base"], outfunc => \&$outsub
+               );
+       });
+       unlink "/etc/pve/$p12_file_base";
+
+
+       my $code = sub {
+           my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
+           my $quorum_section = $conf->{main}->{quorum};
+
+           die "Qdevice already configured, must be removed before setting up new one!\n"
+               if defined($quorum_section->{device}); # must not be forced!
+
+           my $qdev_section = {
+               model => $model,
+               "$model" => {
+                   tls => 'on',
+                   host => $qnetd_addr,
+                   algorithm => $algorithm,
+               }
+           };
+           $qdev_section->{votes} = 1 if $algorithm eq 'ffsplit';
+
+           $quorum_section->{device} = $qdev_section;
+
+           PVE::Corosync::atomic_write_conf($conf);
+       };
+
+       print "\nINFO: add QDevice to cluster configuration\n";
+       PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code);
+       die $@ if $@;
+
+       $foreach_member->(sub {
+           my ($node, $ip) = @_;
+           my $outsub = sub { print "\nnode '$node': " . shift };
+           print "\nINFO: start and enable corosync qdevice daemon on node '$node'...\n";
+           run_command([@$ssh_cmd, $ip, 'systemctl', 'start', 'corosync-qdevice'], outfunc => \&$outsub);
+           run_command([@$ssh_cmd, $ip, 'systemctl', 'enable', 'corosync-qdevice'], outfunc => \&$outsub);
+       });
+
+       run_command(['corosync-cfgtool', '-R']); # do cluster wide config reload
+
+       return undef;
+}});
+
+__PACKAGE__->register_method ({
+    name => 'remove_qdevice',
+    path => 'remove_qdevice',
+    method => 'DELETE',
+    description => "Remove a configured QDevice",
+    parameters => {
+        additionalProperties => 0,
+       properties => {},
+    },
+    returns => { type => 'null' },
+
+    code => sub {
+       my ($param) = @_;
+
+       PVE::Corosync::check_conf_exists();
+
+       my $members = PVE::Cluster::get_members();
+       foreach my $node (sort keys %$members) {
+           die "All nodes must be online! Node $node is offline, aborting.\n"
+               if !$members->{$node}->{online};
+       }
+
+       my $ssh_cmd = ['ssh', '-o', 'BatchMode=yes', '-lroot'];
+
+       my $code = sub {
+           my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
+           my $quorum_section = $conf->{main}->{quorum};
+
+           die "No QDevice configured!\n" if !defined($quorum_section->{device});
+
+           delete $quorum_section->{device};
+
+           PVE::Corosync::atomic_write_conf($conf);
+
+           # cleanup qdev state (cert storage)
+           my $qdev_state_dir =  "/etc/corosync/qdevice";
+           $foreach_member->(sub {
+               my (undef, $ip) = @_;
+               run_command([@$ssh_cmd, $ip, '--', 'rm', '-rf', $qdev_state_dir]);
+           });
+       };
+
+       PVE::Cluster::cfs_lock_file('corosync.conf', 10, $code);
+       die $@ if $@;
+
+       $foreach_member->(sub {
+           my (undef, $ip) = @_;
+           run_command([@$ssh_cmd, $ip, 'systemctl', 'stop', 'corosync-qdevice']);
+           run_command([@$ssh_cmd, $ip, 'systemctl', 'disable', 'corosync-qdevice']);
+       });
+
+       run_command(['corosync-cfgtool', '-R']);
+
+       print "\nRemoved Qdevice.\n";
+
+       return undef;
+}});
+
+__PACKAGE__->register_method ({
+    name => 'add',
+    path => 'add',
+    method => 'PUT',
+    description => "Adds the current node to an existing cluster.",
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::Corosync::add_corosync_link_properties({
+           hostname => {
+               type => 'string',
+               description => "Hostname (or IP) of an existing cluster member."
+           },
+           nodeid => get_standard_option('corosync-nodeid'),
+           votes => {
+               type => 'integer',
+               description => "Number of votes for this node",
+               minimum => 0,
+               optional => 1,
+           },
+           force => {
+               type => 'boolean',
+               description => "Do not throw error if node already exists.",
+               optional => 1,
+           },
+           fingerprint => get_standard_option('fingerprint-sha256', {
+               optional => 1,
+           }),
+           'use_ssh' => {
+               type => 'boolean',
+               description => "Always use SSH to join, even if peer may do it over API.",
+               optional => 1,
+           },
+       }),
+    },
+    returns => { type => 'null' },
+
+    code => sub {
+       my ($param) = @_;
+
+       # avoid "transport endpoint not connected" errors that occur if
+       # restarting pmxcfs while in fuse-mounted /etc/pve
+       die "Navigate out of $basedir before running 'pvecm add', for example by running 'cd'.\n"
+           if getcwd() =~ m!^$basedir(/.*)?$!;
+
+       my $nodename = PVE::INotify::nodename();
+       my $host = $param->{hostname};
+
+       my $worker = sub {
+
+           if (!$param->{use_ssh}) {
+               my $password = PVE::PTY::read_password("Please enter superuser (root) password for '$host': ");
+
+               delete $param->{use_ssh};
+               $param->{password} = $password;
+
+               my $local_cluster_lock = "/var/lock/pvecm.lock";
+               PVE::Tools::lock_file($local_cluster_lock, 10, \&PVE::Cluster::Setup::join, $param);
+
+               if (my $err = $@) {
+                   if (ref($err) eq 'PVE::APIClient::Exception' && defined($err->{code}) && $err->{code} == 501) {
+                       $err = "Remote side is not able to use API for Cluster join!\n" .
+                              "Pass the 'use_ssh' switch or update the remote side.\n";
+                   }
+                   die $err;
+               }
+               return; # all OK, the API join endpoint successfully set us up
+           }
+
+           # allow fallback to old ssh only join if wished or needed
+
+           my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
+           my $links = PVE::Corosync::extract_corosync_link_args($param);
+
+           PVE::Cluster::Setup::assert_joinable($local_ip_address, $links, $param->{force});
+
+           PVE::Cluster::Setup::setup_sshd_config();
+           PVE::Cluster::Setup::setup_rootsshconfig();
+           PVE::Cluster::Setup::setup_ssh_keys();
+
+           # make sure known_hosts is on local filesystem
+           PVE::Cluster::Setup::ssh_unmerge_known_hosts();
+
+           my $cmd = ['ssh-copy-id', '-i', '/root/.ssh/id_rsa', "root\@$host"];
+           run_command($cmd, 'outfunc' => sub {}, 'errfunc' => sub {},
+                                   'errmsg' => "unable to copy ssh ID");
+
+           $cmd = ['ssh', $host, '-o', 'BatchMode=yes', 'pvecm', 'apiver'];
+           my $remote_apiver = 0;
+           run_command($cmd, 'outfunc' => sub {
+               $remote_apiver = shift;
+               chomp $remote_apiver;
+           }, 'noerr' => 1);
+
+           PVE::Cluster::Setup::assert_we_can_join_cluster_version($remote_apiver);
+
+           $cmd = ['ssh', $host, '-o', 'BatchMode=yes',
+                   'pvecm', 'addnode', $nodename, '--force', 1];
+
+           push @$cmd, '--nodeid', $param->{nodeid} if $param->{nodeid};
+           push @$cmd, '--votes', $param->{votes} if defined($param->{votes});
+
+           my $link_desc = get_standard_option('corosync-link');
+
+           foreach my $link (keys %$links) {
+               push @$cmd, "--link$link", PVE::JSONSchema::print_property_string(
+                   $links->{$link}, $link_desc->{format});
+           }
+
+           # this will be used as fallback if no links are specified
+           if (!%$links) {
+               push @$cmd, '--link0', $local_ip_address if $remote_apiver == 0;
+               push @$cmd, '--new_node_ip', $local_ip_address if $remote_apiver >= 1;
+
+               print "No cluster network links passed explicitly, fallback to local node"
+                   . " IP '$local_ip_address'\n";
+           }
+
+           if (system (@$cmd) != 0) {
+               my $cmdtxt = join (' ', @$cmd);
+               die "unable to add node: command failed ($cmdtxt)\n";
+           }
+
+           my $tmpdir = "$libdir/.pvecm_add.tmp.$$";
+           mkdir $tmpdir;
+
+           eval {
+               print "copy corosync auth key\n";
+               $cmd = ['rsync', '--rsh=ssh -l root -o BatchMode=yes', '-lpgoq',
+                       "[$host]:$authfile $clusterconf", $tmpdir];
+
+               system(@$cmd) == 0 || die "can't rsync data from host '$host'\n";
+
+               my $corosync_conf = PVE::Tools::file_get_contents("$tmpdir/corosync.conf");
+               my $corosync_authkey = PVE::Tools::file_get_contents("$tmpdir/authkey");
+
+               PVE::Cluster::Setup::finish_join($nodename, $corosync_conf, $corosync_authkey);
+           };
+           my $err = $@;
+
+           rmtree $tmpdir;
+
+           die $err if $err;
+       };
+
+       # use a synced worker so we get a nice task log when joining through CLI
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+       $rpcenv->fork_worker('clusterjoin', '',  $authuser, $worker);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'status',
+    path => 'status',
+    method => 'GET',
+    description => "Displays the local view of the cluster status.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => { type => 'null' },
+
+    code => sub {
+       my ($param) = @_;
+
+       PVE::Corosync::check_conf_exists();
+       my $conf = eval { PVE::Cluster::cfs_read_file("corosync.conf") } // {};
+       warn "$@" if $@;
+       my $totem = PVE::Corosync::totem_config($conf);
+
+       if (scalar(%$totem)) {
+           my $print_info = sub {
+               my ($label, $key, $default) = @_;
+               my $val = $totem->{$key} // $default;
+               printf "%-17s %s\n", "$label:", "$val";
+           };
+
+           printf "Cluster information\n";
+           printf "-------------------\n";
+           $print_info->('Name', 'cluster_name', 'UNKOWN?');
+           $print_info->('Config Version', 'config_version', -1);
+           $print_info->('Transport', 'transport', 'knet');
+           $print_info->('Secure auth', 'secauth', 'off');
+           printf "\n";
+       }
+
+       exec ('corosync-quorumtool', '-siH');
+       exit (-1); # should not be reached
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'nodes',
+    path => 'nodes',
+    method => 'GET',
+    description => "Displays the local view of the cluster nodes.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => { type => 'null' },
+
+    code => sub {
+       my ($param) = @_;
+
+       PVE::Corosync::check_conf_exists();
+
+       exec ('corosync-quorumtool', '-l');
+       exit (-1); # should not be reached
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'expected',
+    path => 'expected',
+    method => 'PUT',
+    description => "Tells corosync a new value of expected votes.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           expected => {
+               type => 'integer',
+               description => "Expected votes",
+               minimum => 1,
+           },
+       },
+    },
+    returns => { type => 'null' },
+
+    code => sub {
+       my ($param) = @_;
+
+       PVE::Corosync::check_conf_exists();
+
+       exec ('corosync-quorumtool', '-e', $param->{expected});
+       exit (-1); # should not be reached
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'updatecerts',
+    path => 'updatecerts',
+    method => 'PUT',
+    description => "Update node certificates (and generate all needed files/directories).",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           force => {
+               description => "Force generation of new SSL certificate.",
+               type => 'boolean',
+               optional => 1,
+           },
+           silent => {
+               description => "Ignore errors (i.e. when cluster has no quorum).",
+               type => 'boolean',
+               optional => 1,
+           },
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       # we get called by the pve-cluster.service ExecStartPost and as we do
+       # IO (on /etc/pve) which can hang (uninterruptedly D state). That'd be
+       # no-good for ExecStartPost as it fails the whole service in this case
+       PVE::Tools::run_fork_with_timeout(30, sub {
+           PVE::Cluster::Setup::updatecerts_and_ssh($param->@{qw(force silent)});
+           PVE::Cluster::prepare_observed_file_basedirs();
+       });
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'mtunnel',
+    path => 'mtunnel',
+    method => 'POST',
+    description => "Used by VM/CT migration - do not use manually.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           get_migration_ip => {
+               type => 'boolean',
+               default => 0,
+               description => 'return the migration IP, if configured',
+               optional => 1,
+           },
+           migration_network => {
+               type => 'string',
+               format => 'CIDR',
+               description => 'the migration network used to detect the local migration IP',
+               optional => 1,
+           },
+           'run-command' => {
+               type => 'boolean',
+               description => 'Run a command with a tcp socket as standard input.'
+                             .' The IP address and port are printed via this'
+                             ." command's stdandard output first, each on a separate line.",
+               optional => 1,
+           },
+           'extra-args' => PVE::JSONSchema::get_standard_option('extra-args'),
+       },
+    },
+    returns => { type => 'null'},
+    code => sub {
+       my ($param) = @_;
+
+       if (!PVE::Cluster::check_cfs_quorum(1)) {
+           print "no quorum\n";
+           return undef;
+       }
+
+       my $get_local_migration_ip = sub {
+           my ($cidr) = @_;
+
+           if (!defined($cidr)) {
+               my $dc_conf = cfs_read_file('datacenter.cfg');
+               $cidr = $dc_conf->{migration}->{network}
+               if defined($dc_conf->{migration}->{network});
+           }
+
+           if (defined($cidr)) {
+               my $ips = PVE::Network::get_local_ip_from_cidr($cidr);
+
+               die "could not get migration ip: no IP address configured on local " .
+                   "node for network '$cidr'\n" if scalar(@$ips) == 0;
+
+               die "could not get migration ip: multiple, different, IP address configured for " .
+                   "network '$cidr'\n" if scalar(@$ips) > 1 && grep { @$ips[0] ne $_ } @$ips;
+
+               return @$ips[0];
+           }
+
+           return undef;
+       };
+
+       my $network = $param->{migration_network};
+       if ($param->{get_migration_ip}) {
+           die "cannot use --run-command with --get_migration_ip\n"
+               if $param->{'run-command'};
+
+           if (my $ip = $get_local_migration_ip->($network)) {
+               print "ip: '$ip'\n";
+           } else {
+               print "no ip\n";
+           }
+           # do not keep tunnel open when asked for migration ip
+           return undef;
+       }
+
+       if ($param->{'run-command'}) {
+           my $cmd = $param->{'extra-args'};
+           die "missing command\n"
+               if !$cmd || !scalar(@$cmd);
+
+           # Get an ip address to listen on, and find a free migration port
+           my ($ip, $family);
+           if (defined($network)) {
+               $ip = $get_local_migration_ip->($network)
+                   or die "failed to get migration IP address to listen on\n";
+               $family = PVE::Tools::get_host_address_family($ip);
+           } else {
+               my $nodename = PVE::INotify::nodename();
+               ($ip, $family) = PVE::Network::get_ip_from_hostname($nodename, 0);
+           }
+           my $port = PVE::Tools::next_migrate_port($family, $ip);
+
+           PVE::Tools::pipe_socket_to_command($cmd, $ip, $port);
+           return undef;
+       }
+
+       print "tunnel online\n";
+       *STDOUT->flush();
+
+       while (my $line = <STDIN>) {
+           chomp $line;
+           last if $line =~ m/^quit$/;
+       }
+
+       return undef;
+    }});
+
+
+our $cmddef = {
+    apiver => [ 'PVE::API2::ClusterConfig', 'join_api_version', [], {}, sub {
+       my $apiver = shift;
+       print "$apiver\n";
+    }],
+    keygen => [ __PACKAGE__, 'keygen', ['filename']],
+    create => [ 'PVE::API2::ClusterConfig', 'create', ['clustername']],
+    add => [ __PACKAGE__, 'add', ['hostname']],
+    addnode => [ 'PVE::API2::ClusterConfig', 'addnode', ['node']],
+    delnode => [ 'PVE::API2::ClusterConfig', 'delnode', ['node']],
+    status => [ __PACKAGE__, 'status' ],
+    nodes => [ __PACKAGE__, 'nodes' ],
+    expected => [ __PACKAGE__, 'expected', ['expected']],
+    updatecerts => [ __PACKAGE__, 'updatecerts', []],
+    mtunnel => [ __PACKAGE__, 'mtunnel', ['extra-args']],
+    qdevice => {
+       setup => [ __PACKAGE__, 'setup_qdevice', ['address']],
+       remove => [ __PACKAGE__, 'remove_qdevice', []],
+    }
+};
+
+1;
diff --git a/src/PVE/Cluster.pm b/src/PVE/Cluster.pm
new file mode 100644 (file)
index 0000000..efca58f
--- /dev/null
@@ -0,0 +1,903 @@
+package PVE::Cluster;
+
+use strict;
+use warnings;
+
+use Encode;
+use File::stat qw();
+use File::Path qw(make_path);
+use JSON;
+use Net::SSLeay;
+use POSIX qw(ENOENT);
+use Socket;
+use Storable qw(dclone);
+
+use PVE::Certificate;
+use PVE::INotify;
+use PVE::IPCC;
+use PVE::JSONSchema;
+use PVE::Network;
+use PVE::SafeSyslog;
+use PVE::Tools qw(run_command);
+
+use PVE::Cluster::IPCConst;
+
+use base 'Exporter';
+
+our @EXPORT_OK = qw(
+cfs_read_file
+cfs_write_file
+cfs_register_file
+cfs_lock_file);
+
+# x509 certificate utils
+
+my $basedir = "/etc/pve";
+my $authdir = "$basedir/priv";
+my $lockdir = "/etc/pve/priv/lock";
+
+# cfs and corosync files
+my $dbfile = "/var/lib/pve-cluster/config.db";
+my $dbbackupdir = "/var/lib/pve-cluster/backup";
+
+# this is just a readonly copy, the relevant one is in status.c from pmxcfs
+# observed files are the one we can get directly through IPCC, they are cached
+# using a computed version and only those can be used by the cfs_*_file methods
+my $observed = {
+    'vzdump.cron' => 1,
+    'vzdump.conf' => 1,
+    'jobs.cfg' => 1,
+    'storage.cfg' => 1,
+    'datacenter.cfg' => 1,
+    'replication.cfg' => 1,
+    'corosync.conf' => 1,
+    'corosync.conf.new' => 1,
+    'firewall/cluster.fw' => 1,
+    'user.cfg' => 1,
+    'domains.cfg' => 1,
+    'priv/shadow.cfg' => 1,
+    'priv/tfa.cfg' => 1,
+    'priv/token.cfg' => 1,
+    'priv/acme/plugins.cfg' => 1,
+    'priv/ipam.db' => 1,
+    '/qemu-server/' => 1,
+    '/openvz/' => 1,
+    '/lxc/' => 1,
+    'ha/crm_commands' => 1,
+    'ha/manager_status' => 1,
+    'ha/resources.cfg' => 1,
+    'ha/groups.cfg' => 1,
+    'ha/fence.cfg' => 1,
+    'status.cfg' => 1,
+    'ceph.conf' => 1,
+    'sdn/vnets.cfg' => 1,
+    'sdn/zones.cfg' => 1,
+    'sdn/controllers.cfg' => 1,
+    'sdn/subnets.cfg' => 1,
+    'sdn/ipams.cfg' => 1,
+    'sdn/dns.cfg' => 1,
+    'sdn/.running-config' => 1,
+    'virtual-guest/cpu-models.conf' => 1,
+};
+
+sub prepare_observed_file_basedirs {
+
+    if (!check_cfs_is_mounted(1)) {
+       warn "pmxcfs isn't mounted (/etc/pve), chickening out..\n";
+       return;
+    }
+
+    for my $f (sort keys %$observed) {
+       next if $f !~ m!^(.*)/[^/]+$!;
+       my $dir = "$basedir/$1";
+       next if -e $dir; # can also be a link, so just use -e xist check
+       print "creating directory '$dir' for observerd files\n";
+       make_path($dir);
+    }
+}
+
+sub base_dir {
+    return $basedir;
+}
+
+sub auth_dir {
+    return $authdir;
+}
+
+sub check_cfs_quorum {
+    my ($noerr) = @_;
+
+    # note: -w filename always return 1 for root, so wee need
+    # to use File::lstat here
+    my $st = File::stat::lstat("$basedir/local");
+    my $quorate = ($st && (($st->mode & 0200) != 0));
+
+    die "cluster not ready - no quorum?\n" if !$quorate && !$noerr;
+
+    return $quorate;
+}
+
+sub check_cfs_is_mounted {
+    my ($noerr) = @_;
+
+    my $res = -l "$basedir/local";
+
+    die "pve configuration filesystem (pmxcfs) not mounted\n" if !$res && !$noerr;
+
+    return $res;
+}
+
+my $versions = {};
+my $vmlist = {};
+my $clinfo = {};
+
+my $ipcc_send_rec = sub {
+    my ($msgid, $data) = @_;
+
+    my $res = PVE::IPCC::ipcc_send_rec($msgid, $data);
+
+    die "ipcc_send_rec[$msgid] failed: $!\n" if !defined($res) && ($! != 0);
+
+    return $res;
+};
+
+my $ipcc_send_rec_json = sub {
+    my ($msgid, $data) = @_;
+
+    my $res = PVE::IPCC::ipcc_send_rec($msgid, $data);
+
+    die "ipcc_send_rec[$msgid] failed: $!\n" if !defined($res) && ($! != 0);
+
+    return decode_json($res);
+};
+
+my $ipcc_get_config = sub {
+    my ($path) = @_;
+
+    my $bindata = pack "Z*", $path;
+    my $res = PVE::IPCC::ipcc_send_rec(CFS_IPC_GET_CONFIG, $bindata);
+    if (!defined($res)) {
+       if ($! != 0) {
+           return undef if $! == ENOENT;
+           die "$!\n";
+       }
+       return '';
+    }
+
+    return $res;
+};
+
+my $ipcc_get_status = sub {
+    my ($name, $nodename) = @_;
+
+    my $bindata = pack "Z[256]Z[256]", $name, ($nodename || "");
+    return PVE::IPCC::ipcc_send_rec(CFS_IPC_GET_STATUS, $bindata);
+};
+
+my $ipcc_remove_status = sub {
+    my ($name) = @_;
+    # we just omit the data payload, pmxcfs takes this as hint and removes this
+    # key from the status hashtable
+    my $bindata = pack "Z[256]", $name;
+    return &$ipcc_send_rec(CFS_IPC_SET_STATUS, $bindata);
+};
+
+my $ipcc_update_status = sub {
+    my ($name, $data) = @_;
+
+    my $raw = ref($data) ? encode_json($data) : $data;
+    # update status
+    my $bindata = pack "Z[256]Z*", $name, $raw;
+
+    return &$ipcc_send_rec(CFS_IPC_SET_STATUS, $bindata);
+};
+
+my $ipcc_log = sub {
+    my ($priority, $ident, $tag, $msg) = @_;
+
+    my $bindata = pack "CCCZ*Z*Z*", $priority, bytes::length($ident) + 1,
+    bytes::length($tag) + 1, $ident, $tag, $msg;
+
+    return &$ipcc_send_rec(CFS_IPC_LOG_CLUSTER_MSG, $bindata);
+};
+
+my $ipcc_get_cluster_log = sub {
+    my ($user, $max) = @_;
+
+    $max = 0 if !defined($max);
+
+    my $bindata = pack "VVVVZ*", $max, 0, 0, 0, ($user || "");
+    return &$ipcc_send_rec(CFS_IPC_GET_CLUSTER_LOG, $bindata);
+};
+
+my $ipcc_verify_token = sub {
+    my ($full_token) = @_;
+
+    my $bindata = pack "Z*", $full_token;
+    my $res = PVE::IPCC::ipcc_send_rec(CFS_IPC_VERIFY_TOKEN, $bindata);
+
+    return 1 if $! == 0;
+    return 0 if $! == ENOENT;
+
+    die "$!\n";
+};
+
+my $ccache = {};
+
+sub cfs_update {
+    my ($fail) = @_;
+    eval {
+       my $res = &$ipcc_send_rec_json(CFS_IPC_GET_FS_VERSION);
+       die "no starttime\n" if !$res->{starttime};
+
+       if (!$res->{starttime} || !$versions->{starttime} ||
+           $res->{starttime} != $versions->{starttime}) {
+           #print "detected changed starttime\n";
+           $vmlist = {};
+           $clinfo = {};
+           $ccache = {};
+       }
+
+       $versions = $res;
+    };
+    my $err = $@;
+    if ($err) {
+       $versions = {};
+       $vmlist = {};
+       $clinfo = {};
+       $ccache = {};
+       die $err if $fail;
+       warn $err;
+    }
+
+    eval {
+       if (!$clinfo->{version} || $clinfo->{version} != $versions->{clinfo}) {
+           #warn "detected new clinfo\n";
+           $clinfo = &$ipcc_send_rec_json(CFS_IPC_GET_CLUSTER_INFO);
+       }
+    };
+    $err = $@;
+    if ($err) {
+       $clinfo = {};
+       die $err if $fail;
+       warn $err;
+    }
+
+    eval {
+       if (!$vmlist->{version} || $vmlist->{version} != $versions->{vmlist}) {
+           #warn "detected new vmlist1\n";
+           $vmlist = &$ipcc_send_rec_json(CFS_IPC_GET_GUEST_LIST);
+       }
+    };
+    $err = $@;
+    if ($err) {
+       $vmlist = {};
+       die $err if $fail;
+       warn $err;
+    }
+}
+
+sub get_vmlist {
+    return $vmlist;
+}
+
+sub get_clinfo {
+    return $clinfo;
+}
+
+sub get_members {
+    return $clinfo->{nodelist};
+}
+
+sub get_nodelist {
+    my $nodelist = $clinfo->{nodelist};
+
+    my $nodename = PVE::INotify::nodename();
+
+    if (!$nodelist || !$nodelist->{$nodename}) {
+       return [ $nodename ];
+    }
+
+    return [ keys %$nodelist ];
+}
+
+# only stored in a in-memory hashtable inside pmxcfs, local data is gone after
+# a restart (of pmxcfs or the node), peer data is still available then
+# best used for status data, like running (ceph) services, package versions, ...
+sub broadcast_node_kv {
+    my ($key, $data) = @_;
+
+    if (!defined($data)) {
+       eval { $ipcc_remove_status->("kv/$key") };
+    } else {
+       die "cannot send a reference\n" if ref($data);
+       my $size = length($data);
+       die "data for '$key' too big\n" if $size >= (32 * 1024); # limit from pmxfs
+
+       eval { $ipcc_update_status->("kv/$key", $data) };
+    }
+    warn $@ if $@;
+}
+
+# nodename is optional
+sub get_node_kv {
+    my ($key, $nodename) = @_;
+
+    my $res = {};
+    my $get_node_data = sub {
+       my ($node) = @_;
+       my $raw = $ipcc_get_status->("kv/$key", $node);
+       $res->{$node} = unpack("Z*", $raw) if $raw;
+    };
+
+    if ($nodename) {
+       $get_node_data->($nodename);
+    } else {
+       for my $node (get_nodelist()->@*) {
+           $get_node_data->($node);
+       }
+    }
+
+    return $res;
+}
+
+# properties: an array-ref of config properties you want to get, e.g., this
+# is perfect to get multiple properties of a guest _fast_
+# (>100 faster than manual parsing here)
+# vmid: optional, if a valid is passed we only check that one, else return all
+# NOTE: does *not* searches snapshot and PENDING entries sections!
+# NOTE: returns the guest config lines (excluding trailing whitespace) as is,
+#       so for non-trivial properties, checking the validity must be done
+# NOTE: no permission check is done, that is the responsibilty of the caller
+sub get_guest_config_properties {
+    my ($properties, $vmid) = @_;
+
+    die "properties required" if !defined($properties);
+
+    my $num_props = scalar(@$properties);
+    die "only up to 255 properties supported" if $num_props > 255;
+    my $bindata = pack "VC", $vmid // 0, $num_props;
+    for my $property (@$properties) {
+       $bindata .= pack "Z*", $property;
+    }
+    my $res = $ipcc_send_rec_json->(CFS_IPC_GET_GUEST_CONFIG_PROPERTIES, $bindata);
+
+    return $res;
+}
+
+# property: a config property you want to get, e.g., this is perfect to get
+# the 'lock' entry of a guest _fast_ (>100 faster than manual parsing here)
+# vmid: optional, if a valid is passed we only check that one, else return all
+# NOTE: does *not* searches snapshot and PENDING entries sections!
+# NOTE: returns the guest config lines (excluding trailing whitespace) as is,
+#       so for non-trivial properties, checking the validity must be done
+# NOTE: no permission check is done, that is the responsibilty of the caller
+sub get_guest_config_property {
+    my ($property, $vmid) = @_;
+
+    die "property is required" if !defined($property);
+
+    my $bindata = pack "VZ*", $vmid // 0, $property;
+    my $res = $ipcc_send_rec_json->(CFS_IPC_GET_GUEST_CONFIG_PROPERTY, $bindata);
+
+    return $res;
+}
+
+# $data must be a chronological descending ordered array of tasks
+sub broadcast_tasklist {
+    my ($data) = @_;
+
+    # the serialized list may not get bigger than 128 KiB (CFS_MAX_STATUS_SIZE from pmxcfs)
+    # drop older items until we satisfy this constraint
+    my $size = length(encode_json($data));
+    while ($size >= (32 * 1024)) { # TODO: update to 128 KiB in PVE 8.x
+       pop @$data;
+       $size = length(encode_json($data));
+    }
+
+    eval { $ipcc_update_status->("tasklist", $data) };
+    warn $@ if $@;
+}
+
+my $tasklistcache = {};
+
+sub get_tasklist {
+    my ($nodename) = @_;
+
+    my $kvstore = $versions->{kvstore} || {};
+
+    my $nodelist = get_nodelist();
+
+    my $res = [];
+    foreach my $node (@$nodelist) {
+       next if $nodename && ($nodename ne $node);
+       eval {
+           my $ver = exists $kvstore->{$node} ? $kvstore->{$node}->{tasklist} : undef;
+           my $cache = $tasklistcache->{$node};
+           if (!$cache || !$ver || !$cache->{version} || ($cache->{version} != $ver)) {
+               my $tasks = [];
+               if (my $raw = $ipcc_get_status->("tasklist", $node)) {
+                   my $json_str = unpack("Z*", $raw);
+                   $tasks = decode_json($json_str);
+               }
+               push @$res, @$tasks;
+               $tasklistcache->{$node} = {
+                   data => $tasks,
+                   version => $ver,
+               };
+           } elsif ($cache && $cache->{data}) {
+               push @$res, $cache->{data}->@*;
+           }
+       };
+       my $err = $@;
+       syslog('err', $err) if $err;
+    }
+
+    return $res;
+}
+
+sub broadcast_rrd {
+    my ($rrdid, $data) = @_;
+
+    eval {
+       &$ipcc_update_status("rrd/$rrdid", $data);
+    };
+    my $err = $@;
+
+    warn $err if $err;
+}
+
+my $last_rrd_dump = 0;
+my $last_rrd_data = "";
+
+sub rrd_dump {
+
+    my $ctime = time();
+
+    my $diff = $ctime - $last_rrd_dump;
+    if ($diff < 2) {
+       return $last_rrd_data;
+    }
+
+    my $raw;
+    eval {
+       $raw = &$ipcc_send_rec(CFS_IPC_GET_RRD_DUMP);
+    };
+    my $err = $@;
+
+    if ($err) {
+       warn $err;
+       return {};
+    }
+
+    my $res = {};
+
+    if ($raw) {
+       while ($raw =~ s/^(.*)\n//) {
+           my ($key, @ela) = split(/:/, $1);
+           next if !$key;
+           next if !(scalar(@ela) > 1);
+           $res->{$key} = [ map { $_ eq 'U' ? undef : $_ } @ela ];
+       }
+    }
+
+    $last_rrd_dump = $ctime;
+    $last_rrd_data = $res;
+
+    return $res;
+}
+
+
+# a fast way to read files (avoid fuse overhead)
+sub get_config {
+    my ($path) = @_;
+
+    return &$ipcc_get_config($path);
+}
+
+sub get_cluster_log {
+    my ($user, $max) = @_;
+
+    return &$ipcc_get_cluster_log($user, $max);
+}
+
+sub verify_token {
+    my ($userid, $token) = @_;
+
+    return &$ipcc_verify_token("$userid $token");
+}
+
+my $file_info = {};
+
+sub cfs_register_file {
+    my ($filename, $parser, $writer) = @_;
+
+    $observed->{$filename} || die "unknown file '$filename'";
+
+    die "file '$filename' already registered" if $file_info->{$filename};
+
+    $file_info->{$filename} = {
+       parser => $parser,
+       writer => $writer,
+    };
+}
+
+my $ccache_read = sub {
+    my ($filename, $parser, $version) = @_;
+
+    $ccache->{$filename} = {} if !$ccache->{$filename};
+
+    my $ci = $ccache->{$filename};
+
+    if (!$ci->{version} || !$version || $ci->{version} != $version) {
+       # we always call the parser, even when the file does not exist
+       # (in that case $data is undef)
+       my $data = get_config($filename);
+       $ci->{data} = &$parser("/etc/pve/$filename", $data);
+       $ci->{version} = $version;
+    }
+
+    my $res = ref($ci->{data}) ? dclone($ci->{data}) : $ci->{data};
+
+    return $res;
+};
+
+sub cfs_file_version {
+    my ($filename) = @_;
+
+    my $version;
+    my $infotag;
+    if ($filename =~ m!^nodes/[^/]+/(openvz|lxc|qemu-server)/(\d+)\.conf$!) {
+       my ($type, $vmid) = ($1, $2);
+       if ($vmlist && $vmlist->{ids} && $vmlist->{ids}->{$vmid}) {
+           $version = $vmlist->{ids}->{$vmid}->{version};
+       }
+       $infotag = "/$type/";
+    } else {
+       $infotag = $filename;
+       $version = $versions->{$filename};
+    }
+
+    my $info = $file_info->{$infotag} ||
+       die "unknown file type '$filename'\n";
+
+    return wantarray ? ($version, $info) : $version;
+}
+
+sub cfs_read_file {
+    my ($filename) = @_;
+
+    my ($version, $info) = cfs_file_version($filename);
+    my $parser = $info->{parser};
+
+    return &$ccache_read($filename, $parser, $version);
+}
+
+sub cfs_write_file {
+    my ($filename, $data) = @_;
+
+    my ($version, $info) = cfs_file_version($filename);
+
+    my $writer = $info->{writer} || die "no writer defined";
+
+    my $fsname = "/etc/pve/$filename";
+
+    my $raw = &$writer($fsname, $data);
+
+    if (my $ci = $ccache->{$filename}) {
+       $ci->{version} = undef;
+    }
+
+    PVE::Tools::file_set_contents($fsname, $raw);
+}
+
+my $cfs_lock = sub {
+    my ($lockid, $timeout, $code, @param) = @_;
+
+    my $prev_alarm = alarm(0); # suspend outer alarm early
+
+    my $res;
+    my $got_lock = 0;
+
+    # this timeout is for acquire the lock
+    $timeout = 10 if !$timeout;
+
+    my $filename = "$lockdir/$lockid";
+
+    my $is_code_err = 0;
+    eval {
+
+       mkdir $lockdir;
+
+       if (! -d $lockdir) {
+           die "pve cluster filesystem not online.\n";
+       }
+
+       my $timeout_err = sub { die "got lock request timeout\n"; };
+       local $SIG{ALRM} = $timeout_err;
+
+       while (1) {
+           alarm ($timeout);
+           $got_lock = mkdir($filename);
+           $timeout = alarm(0) - 1; # we'll sleep for 1s, see down below
+
+           last if $got_lock;
+
+           $timeout_err->() if $timeout <= 0;
+
+           print STDERR "trying to acquire cfs lock '$lockid' ...\n";
+           utime (0, 0, $filename); # cfs unlock request
+           sleep(1);
+       }
+
+       # fixed command timeout: cfs locks have a timeout of 120
+       # using 60 gives us another 60 seconds to abort the task
+       local $SIG{ALRM} = sub { die "'$lockid'-locked command timed out - aborting\n"; };
+       alarm(60);
+
+       cfs_update(); # make sure we read latest versions inside code()
+
+       $is_code_err = 1; # allows to differ between locking and actual-work errors
+
+       $res = &$code(@param);
+
+       alarm(0);
+    };
+
+    my $err = $@;
+
+    $err = "no quorum!\n" if !$got_lock && !check_cfs_quorum(1);
+
+    rmdir $filename if $got_lock; # if we held the lock always unlock again
+
+    alarm($prev_alarm);
+
+    if ($err) {
+       if (ref($err) eq 'PVE::Exception' || $is_code_err) {
+           # re-raise defined exceptions
+           $@ = $err;
+       } else {
+           # add lock info for plain errors comming from the locking itself
+           $@ = "cfs-lock '$lockid' error: $err";
+       }
+        return undef;
+    }
+
+    $@ = undef;
+
+    return $res;
+};
+
+sub cfs_lock_file {
+    my ($filename, $timeout, $code, @param) = @_;
+
+    my $info = $observed->{$filename} || die "unknown file '$filename'";
+
+    my $lockid = "file-$filename";
+    $lockid =~ s/[.\/]/_/g;
+
+    &$cfs_lock($lockid, $timeout, $code, @param);
+}
+
+sub cfs_lock_storage {
+    my ($storeid, $timeout, $code, @param) = @_;
+
+    my $lockid = "storage-$storeid";
+
+    &$cfs_lock($lockid, $timeout, $code, @param);
+}
+
+sub cfs_lock_domain {
+    my ($domainname, $timeout, $code, @param) = @_;
+
+    my $lockid = "domain-$domainname";
+
+    &$cfs_lock($lockid, $timeout, $code, @param);
+}
+
+sub cfs_lock_acme {
+    my ($account, $timeout, $code, @param) = @_;
+
+    my $lockid = "acme-$account";
+
+    &$cfs_lock($lockid, $timeout, $code, @param);
+}
+
+sub cfs_lock_authkey {
+    my ($timeout, $code, @param) = @_;
+
+    $cfs_lock->('authkey', $timeout, $code, @param);
+}
+
+sub cfs_lock_firewall {
+    my ($scope, $timeout, $code, @param) = @_;
+
+    my $lockid = "firewall-$scope";
+
+    $cfs_lock->($lockid, $timeout, $code, @param);
+}
+
+my $log_levels = {
+    "emerg" => 0,
+    "alert" => 1,
+    "crit" => 2,
+    "critical" => 2,
+    "err" => 3,
+    "error" => 3,
+    "warn" => 4,
+    "warning" => 4,
+    "notice" => 5,
+    "info" => 6,
+    "debug" => 7,
+};
+
+sub log_msg {
+   my ($priority, $ident, $msg) = @_;
+
+   if (my $tmp = $log_levels->{$priority}) {
+       $priority = $tmp;
+   }
+
+   die "need numeric log priority" if $priority !~ /^\d+$/;
+
+   my $tag = PVE::SafeSyslog::tag();
+
+   $msg = "empty message" if !$msg;
+
+   $ident = "" if !$ident;
+   $ident = encode("ascii", $ident,
+                  sub { sprintf "\\u%04x", shift });
+
+   my $ascii = encode("ascii", $msg, sub { sprintf "\\u%04x", shift });
+
+   if ($ident) {
+       syslog($priority, "<%s> %s", $ident, $ascii);
+   } else {
+       syslog($priority, "%s", $ascii);
+   }
+
+   eval { &$ipcc_log($priority, $ident, $tag, $ascii); };
+
+   syslog("err", "writing cluster log failed: $@") if $@;
+}
+
+sub check_vmid_unused {
+    my ($vmid, $noerr) = @_;
+
+    my $vmlist = get_vmlist();
+
+    my $d = $vmlist->{ids}->{$vmid};
+    return 1 if !defined($d);
+
+    return undef if $noerr;
+
+    my $vmtypestr =  $d->{type} eq 'qemu' ? 'VM' : 'CT';
+    die "$vmtypestr $vmid already exists on node '$d->{node}'\n";
+}
+
+sub check_node_exists {
+    my ($nodename, $noerr) = @_;
+
+    my $nodelist = $clinfo->{nodelist};
+    return 1 if $nodelist && $nodelist->{$nodename};
+
+    return undef if $noerr;
+
+    die "no such cluster node '$nodename'\n";
+}
+
+# this is also used to get the IP of the local node
+sub remote_node_ip {
+    my ($nodename, $noerr) = @_;
+
+    my $nodelist = $clinfo->{nodelist};
+    if ($nodelist && $nodelist->{$nodename}) {
+       if (my $ip = $nodelist->{$nodename}->{ip}) {
+           return $ip if !wantarray;
+           my $family = $nodelist->{$nodename}->{address_family};
+           if (!$family) {
+               $nodelist->{$nodename}->{address_family} =
+                   $family =
+                   PVE::Tools::get_host_address_family($ip);
+           }
+           return wantarray ? ($ip, $family) : $ip;
+       }
+    }
+
+    # fallback: try to get IP by other means
+    return PVE::Network::get_ip_from_hostname($nodename, $noerr);
+}
+
+sub get_node_fingerprint {
+    my ($node) = @_;
+
+    my $cert_path = "/etc/pve/nodes/$node/pve-ssl.pem";
+    my $custom_cert_path = "/etc/pve/nodes/$node/pveproxy-ssl.pem";
+
+    $cert_path = $custom_cert_path if -f $custom_cert_path;
+
+    return PVE::Certificate::get_certificate_fingerprint($cert_path);
+}
+
+# bash completion helpers
+
+sub complete_next_vmid {
+
+    my $vmlist = get_vmlist() || {};
+    my $idlist = $vmlist->{ids} || {};
+
+    for (my $i = 100; $i < 10000; $i++) {
+       return [$i] if !defined($idlist->{$i});
+    }
+
+    return [];
+}
+
+sub complete_vmid {
+
+    my $vmlist = get_vmlist();
+    my $ids = $vmlist->{ids} || {};
+
+    return [ keys %$ids ];
+}
+
+sub complete_local_vmid {
+
+    my $vmlist = get_vmlist();
+    my $ids = $vmlist->{ids} || {};
+
+    my $nodename = PVE::INotify::nodename();
+
+    my $res = [];
+    foreach my $vmid (keys %$ids) {
+       my $d = $ids->{$vmid};
+       next if !$d->{node} || $d->{node} ne $nodename;
+       push @$res, $vmid;
+    }
+
+    return $res;
+}
+
+sub complete_migration_target {
+
+    my $res = [];
+
+    my $nodename = PVE::INotify::nodename();
+
+    my $nodelist = get_nodelist();
+    foreach my $node (@$nodelist) {
+       next if $node eq $nodename;
+       push @$res, $node;
+    }
+
+    return $res;
+}
+
+
+# NOTE: filesystem must be offline here, no DB changes allowed
+sub cfs_backup_database {
+    mkdir $dbbackupdir;
+
+    my $ctime = time();
+    my $backup_fn = "$dbbackupdir/config-$ctime.sql.gz";
+
+    print "backup old database to '$backup_fn'\n";
+
+    my $cmd = [ ['sqlite3', $dbfile, '.dump'], ['gzip', '-', \ ">${backup_fn}"] ];
+    run_command($cmd, 'errmsg' => "cannot backup old database\n");
+
+    my $maxfiles = 10; # purge older backup
+    my $backups = [ sort { $b cmp $a } <$dbbackupdir/config-*.sql.gz> ];
+
+    if ((my $count = scalar(@$backups)) > $maxfiles) {
+       foreach my $f (@$backups[$maxfiles..$count-1]) {
+           next if $f !~ m/^(\S+)$/; # untaint
+           print "delete old backup '$1'\n";
+           unlink $1;
+       }
+    }
+
+    return $dbfile;
+}
+
+1;
diff --git a/src/PVE/Cluster/IPCConst.pm.awk b/src/PVE/Cluster/IPCConst.pm.awk
new file mode 100644 (file)
index 0000000..3c622ec
--- /dev/null
@@ -0,0 +1,21 @@
+BEGIN {
+    print "package PVE::Cluster::IPCConst;"
+    print "use strict; use warnings;"
+    print
+    print "use base 'Exporter';"
+    print
+    print "my %IPC_OPS;"
+    print "BEGIN {"
+    print "  %IPC_OPS = ("
+}
+
+/^#define CFS_IPC/ {
+    print "    " $2, "=>", $3 ","
+}
+
+END {
+    print "  );"
+    print "}"
+    print "use constant \\%IPC_OPS;"
+    print "our @EXPORT = keys(%IPC_OPS);"
+}
diff --git a/src/PVE/Cluster/Makefile b/src/PVE/Cluster/Makefile
new file mode 100644 (file)
index 0000000..494d86c
--- /dev/null
@@ -0,0 +1,17 @@
+PVEDIR=${DESTDIR}/usr/share/perl5/PVE
+
+SOURCES=IPCConst.pm Setup.pm
+
+.PHONY: install
+install: ${SOURCES}
+       install -d ${PVEDIR}/Cluster
+       for f in ${SOURCES}; do install -m 0664 $$f ${PVEDIR}/Cluster/$$f; done
+
+IPCConst.pm: ../../pmxcfs/cfs-ipc-ops.h
+       awk -f IPCConst.pm.awk $< > $@.tmp
+       mv $@.tmp $@
+IPCConst.pm: IPCConst.pm.awk
+
+.PHONY: clean
+clean:
+       rm -f IPCConst.pm IPCConst.pm.tmp
diff --git a/src/PVE/Cluster/Setup.pm b/src/PVE/Cluster/Setup.pm
new file mode 100644 (file)
index 0000000..108817e
--- /dev/null
@@ -0,0 +1,826 @@
+package PVE::Cluster::Setup;
+
+use strict;
+use warnings;
+
+use Digest::HMAC_SHA1;
+use Digest::SHA;
+use IO::File;
+use MIME::Base64;
+use Net::IP;
+use UUID;
+use POSIX qw(EEXIST);
+
+use PVE::APIClient::LWP;
+use PVE::Cluster;
+use PVE::Corosync;
+use PVE::INotify;
+use PVE::JSONSchema;
+use PVE::Network;
+use PVE::Tools;
+use PVE::Certificate;
+
+# relevant for joining or getting joined checks, after that versions in cluster can differ
+use constant JOIN_API_VERSION => 1;
+# (APIVER-this) is oldest version a new node in addnode can have and still be accepted
+use constant JOIN_API_AGE_AS_CLUSTER => 1;
+# (APIVER-this) is oldest version a cluster node can have to still try joining
+use constant JOIN_API_AGE_AS_JOINEE => 1;
+
+sub assert_we_can_join_cluster_version {
+    my ($version) = @_;
+    my $min_version = JOIN_API_VERSION - JOIN_API_AGE_AS_JOINEE;
+    return if $version >= $min_version;
+    die "error: incompatible join API version on cluster ($version), local node"
+       ." has ". JOIN_API_VERSION ." and supports >= $min_version. Make sure"
+       ."all cluster nodes are up-to-date.\n";
+}
+
+sub assert_node_can_join_our_version {
+    my ($version) = @_;
+    my $min_version = JOIN_API_VERSION - JOIN_API_AGE_AS_CLUSTER;
+    return if $version >= $min_version;
+    die "error: unsupported old API version on joining node ($version), cluster"
+       ." node has ". JOIN_API_VERSION ." and supports >= $min_version. Please"
+       ." upgrade node before joining\n";
+}
+
+my $pmxcfs_base_dir = PVE::Cluster::base_dir();
+my $pmxcfs_auth_dir = PVE::Cluster::auth_dir();
+
+# only write output if something fails
+sub run_silent_cmd {
+    my ($cmd) = @_;
+
+    my $outbuf = '';
+    my $record = sub { $outbuf .= shift . "\n"; };
+
+    eval { PVE::Tools::run_command($cmd, outfunc => $record, errfunc => $record) };
+
+    if (my $err = $@) {
+       print STDERR $outbuf;
+       die $err;
+    }
+}
+
+# Corosync related files
+my $localclusterdir = "/etc/corosync";
+my $localclusterconf = "$localclusterdir/corosync.conf";
+my $authfile = "$localclusterdir/authkey";
+my $clusterconf = "$pmxcfs_base_dir/corosync.conf";
+
+# CA/certificate related files
+my $pveca_key_fn = "$pmxcfs_auth_dir/pve-root-ca.key";
+my $pveca_srl_fn = "$pmxcfs_auth_dir/pve-root-ca.srl";
+my $pveca_cert_fn = "$pmxcfs_base_dir/pve-root-ca.pem";
+# this is just a secret accessable by the web browser
+# and is used for CSRF prevention
+my $pvewww_key_fn = "$pmxcfs_base_dir/pve-www.key";
+
+# ssh related files
+my $ssh_rsa_id_priv = "/root/.ssh/id_rsa";
+my $ssh_rsa_id = "/root/.ssh/id_rsa.pub";
+my $ssh_host_rsa_id = "/etc/ssh/ssh_host_rsa_key.pub";
+my $sshglobalknownhosts = "/etc/ssh/ssh_known_hosts";
+my $sshknownhosts = "$pmxcfs_auth_dir/known_hosts";
+my $sshauthkeys = "$pmxcfs_auth_dir/authorized_keys";
+my $sshd_config_fn = "/etc/ssh/sshd_config";
+my $rootsshauthkeys = "/root/.ssh/authorized_keys";
+my $rootsshauthkeysbackup = "${rootsshauthkeys}.org";
+my $rootsshconfig = "/root/.ssh/config";
+
+# ssh related utility functions
+
+sub ssh_merge_keys {
+    # remove duplicate keys in $sshauthkeys
+    # ssh-copy-id simply add keys, so the file can grow to large
+
+    my $data = '';
+    if (-f $sshauthkeys) {
+       $data = PVE::Tools::file_get_contents($sshauthkeys, 128*1024);
+       chomp($data);
+    }
+
+    my $found_backup;
+    if (-f $rootsshauthkeysbackup) {
+       $data .= "\n";
+       $data .= PVE::Tools::file_get_contents($rootsshauthkeysbackup, 128*1024);
+       chomp($data);
+       $found_backup = 1;
+    }
+
+    # always add ourself
+    if (-f $ssh_rsa_id) {
+       my $pub = PVE::Tools::file_get_contents($ssh_rsa_id);
+       chomp($pub);
+       $data .= "\n$pub\n";
+    }
+
+    my $newdata = "";
+    my $vhash = {};
+    my @lines = split(/\n/, $data);
+    foreach my $line (@lines) {
+       if ($line !~ /^#/ && $line =~ m/(^|\s)ssh-(rsa|dsa)\s+(\S+)\s+\S+$/) {
+            next if $vhash->{$3}++;
+       }
+       $newdata .= "$line\n";
+    }
+
+    PVE::Tools::file_set_contents($sshauthkeys, $newdata, 0600);
+
+    if ($found_backup && -l $rootsshauthkeys) {
+       # everything went well, so we can remove the backup
+       unlink $rootsshauthkeysbackup;
+    }
+}
+
+sub setup_sshd_config {
+    my () = @_;
+
+    my $conf = PVE::Tools::file_get_contents($sshd_config_fn);
+
+    return if $conf =~ m/^PermitRootLogin\s+yes\s*$/m;
+
+    if ($conf !~ s/^#?PermitRootLogin.*$/PermitRootLogin yes/m) {
+       chomp $conf;
+       $conf .= "\nPermitRootLogin yes\n";
+    }
+
+    PVE::Tools::file_set_contents($sshd_config_fn, $conf);
+
+    PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'sshd']);
+}
+
+sub setup_rootsshconfig {
+
+    # create ssh key if it does not exist
+    if (! -f $ssh_rsa_id) {
+       mkdir '/root/.ssh/';
+       system ("echo|ssh-keygen -t rsa -N '' -b 2048 -f ${ssh_rsa_id_priv}");
+    }
+
+    # create ssh config if it does not exist
+    if (! -f $rootsshconfig) {
+        mkdir '/root/.ssh';
+        if (my $fh = IO::File->new($rootsshconfig, O_CREAT|O_WRONLY|O_EXCL, 0640)) {
+            # this is the default ciphers list from Debian's OpenSSH package (OpenSSH_7.4p1 Debian-10, OpenSSL 1.0.2k  26 Jan 2017)
+           # changed order to put AES before Chacha20 (most hardware has AESNI)
+            print $fh "Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm\@openssh.com,aes256-gcm\@openssh.com,chacha20-poly1305\@openssh.com\n";
+            close($fh);
+        }
+    }
+}
+
+sub setup_ssh_keys {
+
+    mkdir $pmxcfs_auth_dir;
+
+    my $import_ok;
+
+    if (! -f $sshauthkeys) {
+       my $old;
+       if (-f $rootsshauthkeys) {
+           $old = PVE::Tools::file_get_contents($rootsshauthkeys, 128*1024);
+       }
+       if (my $fh = IO::File->new ($sshauthkeys, O_CREAT|O_WRONLY|O_EXCL, 0400)) {
+           PVE::Tools::safe_print($sshauthkeys, $fh, $old) if $old;
+           close($fh);
+           $import_ok = 1;
+       }
+    }
+
+    warn "can't create shared ssh key database '$sshauthkeys'\n"
+       if ! -f $sshauthkeys;
+
+    if (-f $rootsshauthkeys && ! -l $rootsshauthkeys) {
+       if (!rename($rootsshauthkeys , $rootsshauthkeysbackup)) {
+           warn "rename $rootsshauthkeys failed - $!\n";
+       }
+    }
+
+    if (! -l $rootsshauthkeys) {
+       symlink $sshauthkeys, $rootsshauthkeys;
+    }
+
+    if (! -l $rootsshauthkeys) {
+       warn "can't create symlink for ssh keys '$rootsshauthkeys' -> '$sshauthkeys'\n";
+    } else {
+       unlink $rootsshauthkeysbackup if $import_ok;
+    }
+}
+
+sub ssh_unmerge_known_hosts {
+    return if ! -l $sshglobalknownhosts;
+
+    my $old = '';
+    $old = PVE::Tools::file_get_contents($sshknownhosts, 128*1024)
+       if -f $sshknownhosts;
+
+    PVE::Tools::file_set_contents($sshglobalknownhosts, $old);
+}
+
+sub ssh_merge_known_hosts {
+    my ($nodename, $ip_address, $createLink) = @_;
+
+    die "no node name specified" if !$nodename;
+    die "no ip address specified" if !$ip_address;
+
+    # ssh lowercases hostnames (aliases) before comparision, so we need too
+    $nodename = lc($nodename);
+    $ip_address = lc($ip_address);
+
+    mkdir $pmxcfs_auth_dir;
+
+    if (! -f $sshknownhosts) {
+       if (my $fh = IO::File->new($sshknownhosts, O_CREAT|O_WRONLY|O_EXCL, 0600)) {
+           close($fh);
+       }
+    }
+
+    my $old = PVE::Tools::file_get_contents($sshknownhosts, 128*1024);
+
+    my $new = '';
+
+    if ((! -l $sshglobalknownhosts) && (-f $sshglobalknownhosts)) {
+       $new = PVE::Tools::file_get_contents($sshglobalknownhosts, 128*1024);
+    }
+
+    my $hostkey = PVE::Tools::file_get_contents($ssh_host_rsa_id);
+    # Note: file sometimes containe emty lines at start, so we use multiline match
+    die "can't parse $ssh_host_rsa_id" if $hostkey !~ m/^(ssh-rsa\s\S+)(\s.*)?$/m;
+    $hostkey = $1;
+
+    my $data = '';
+    my $vhash = {};
+
+    my $found_nodename;
+    my $found_local_ip;
+
+    my $merge_line = sub {
+       my ($line, $all) = @_;
+
+       return if $line =~ m/^\s*$/; # skip empty lines
+       return if $line =~ m/^#/; # skip comments
+
+       if ($line =~ m/^(\S+)\s(ssh-rsa\s\S+)(\s.*)?$/) {
+           my $key = $1;
+           my $rsakey = $2;
+           if (!$vhash->{$key}) {
+               $vhash->{$key} = 1;
+               if ($key =~ m/\|1\|([^\|\s]+)\|([^\|\s]+)$/) {
+                   my $salt = decode_base64($1);
+                   my $digest = $2;
+                   my $hmac = Digest::HMAC_SHA1->new($salt);
+                   $hmac->add($nodename);
+                   my $hd = $hmac->b64digest . '=';
+                   if ($digest eq $hd) {
+                       if ($rsakey eq $hostkey) {
+                           $found_nodename = 1;
+                           $data .= $line;
+                       }
+                       return;
+                   }
+                   $hmac = Digest::HMAC_SHA1->new($salt);
+                   $hmac->add($ip_address);
+                   $hd = $hmac->b64digest . '=';
+                   if ($digest eq $hd) {
+                       if ($rsakey eq $hostkey) {
+                           $found_local_ip = 1;
+                           $data .= $line;
+                       }
+                       return;
+                   }
+               } else {
+                   $key = lc($key); # avoid duplicate entries, ssh compares lowercased
+                   if ($key eq $ip_address) {
+                       $found_local_ip = 1 if $rsakey eq $hostkey;
+                   } elsif ($key eq $nodename) {
+                       $found_nodename = 1 if $rsakey eq $hostkey;
+                   }
+               }
+               $data .= $line;
+           }
+       } elsif ($all) {
+           $data .= $line;
+       }
+    };
+
+    while ($old && $old =~ s/^((.*?)(\n|$))//) {
+       my $line = "$2\n";
+       &$merge_line($line, 1);
+    }
+
+    while ($new && $new =~ s/^((.*?)(\n|$))//) {
+       my $line = "$2\n";
+       &$merge_line($line);
+    }
+
+    # add our own key if not already there
+    $data .= "$nodename $hostkey\n" if !$found_nodename;
+    $data .= "$ip_address $hostkey\n" if !$found_local_ip;
+
+    PVE::Tools::file_set_contents($sshknownhosts, $data);
+
+    return if !$createLink;
+
+    unlink $sshglobalknownhosts;
+    symlink $sshknownhosts, $sshglobalknownhosts;
+
+    warn "can't create symlink for ssh known hosts '$sshglobalknownhosts' -> '$sshknownhosts'\n"
+       if ! -l $sshglobalknownhosts;
+
+}
+
+# directory and file creation
+
+sub gen_local_dirs {
+    my ($nodename) = @_;
+
+    PVE::Cluster::check_cfs_is_mounted();
+
+    my @required_dirs = (
+       "$pmxcfs_base_dir/priv",
+       "$pmxcfs_base_dir/nodes",
+       "$pmxcfs_base_dir/nodes/$nodename",
+       "$pmxcfs_base_dir/nodes/$nodename/lxc",
+       "$pmxcfs_base_dir/nodes/$nodename/qemu-server",
+       "$pmxcfs_base_dir/nodes/$nodename/openvz",
+       "$pmxcfs_base_dir/nodes/$nodename/priv");
+
+    foreach my $dir (@required_dirs) {
+       if (! -d $dir) {
+           mkdir($dir) || $! == EEXIST || die "unable to create directory '$dir' - $!\n";
+       }
+    }
+}
+
+sub gen_auth_key {
+    my $authprivkeyfn = "$pmxcfs_auth_dir/authkey.key";
+    my $authpubkeyfn = "$pmxcfs_base_dir/authkey.pub";
+
+    return if -f "$authprivkeyfn";
+
+    PVE::Cluster::check_cfs_is_mounted();
+
+    PVE::Cluster::cfs_lock_authkey(undef, sub {
+       mkdir $pmxcfs_auth_dir || $! == EEXIST || die "unable to create dir '$pmxcfs_auth_dir' - $!\n";
+
+       run_silent_cmd(['openssl', 'genrsa', '-out', $authprivkeyfn, '2048']);
+
+       run_silent_cmd(['openssl', 'rsa', '-in', $authprivkeyfn, '-pubout', '-out', $authpubkeyfn]);
+    });
+
+    die "$@\n" if $@;
+}
+
+sub gen_pveca_key {
+
+    return if -f $pveca_key_fn;
+
+    eval {
+       run_silent_cmd(['openssl', 'genrsa', '-out', $pveca_key_fn, '4096']);
+    };
+
+    die "unable to generate pve ca key:\n$@" if $@;
+}
+
+sub gen_pveca_cert {
+
+    if (-f $pveca_key_fn && -f $pveca_cert_fn) {
+       return 0;
+    }
+
+    gen_pveca_key();
+
+    # we try to generate an unique 'subject' to avoid browser problems
+    # (reused serial numbers, ..)
+    my $uuid;
+    UUID::generate($uuid);
+    my $uuid_str;
+    UUID::unparse($uuid, $uuid_str);
+
+    eval {
+       # wrap openssl with faketime to prevent bug #904
+       run_silent_cmd(['faketime', 'yesterday', 'openssl', 'req', '-batch',
+                       '-days', '3650', '-new', '-x509', '-nodes', '-key',
+                       $pveca_key_fn, '-out', $pveca_cert_fn, '-subj',
+                       "/CN=Proxmox Virtual Environment/OU=$uuid_str/O=PVE Cluster Manager CA/"]);
+    };
+
+    die "generating pve root certificate failed:\n$@" if $@;
+
+    return 1;
+}
+
+sub gen_pve_ssl_key {
+    my ($nodename) = @_;
+
+    die "no node name specified" if !$nodename;
+
+    my $pvessl_key_fn = "$pmxcfs_base_dir/nodes/$nodename/pve-ssl.key";
+
+    return if -f $pvessl_key_fn;
+
+    eval {
+       run_silent_cmd(['openssl', 'genrsa', '-out', $pvessl_key_fn, '2048']);
+    };
+
+    die "unable to generate pve ssl key for node '$nodename':\n$@" if $@;
+}
+
+sub gen_pve_www_key {
+
+    return if -f $pvewww_key_fn;
+
+    eval {
+       run_silent_cmd(['openssl', 'genrsa', '-out', $pvewww_key_fn, '2048']);
+    };
+
+    die "unable to generate pve www key:\n$@" if $@;
+}
+
+sub update_serial {
+    my ($serial) = @_;
+
+    PVE::Tools::file_set_contents($pveca_srl_fn, $serial);
+}
+
+sub gen_pve_ssl_cert {
+    my ($force, $nodename, $ip) = @_;
+
+    die "no node name specified" if !$nodename;
+    die "no IP specified" if !$ip;
+
+    my $pvessl_cert_fn = "$pmxcfs_base_dir/nodes/$nodename/pve-ssl.pem";
+
+    return if !$force && -f $pvessl_cert_fn;
+
+    my $names = "IP:127.0.0.1,IP:::1,DNS:localhost";
+
+    my $rc = PVE::INotify::read_file('resolvconf');
+
+    $names .= ",IP:$ip";
+
+    $names .= ",DNS:$nodename";
+
+    my $fqdn = $nodename;
+    if ($rc && $rc->{search}) {
+       $fqdn .= ".$rc->{search}";
+       $names .= ",DNS:$fqdn";
+    }
+
+    my $sslconf = <<__EOD;
+RANDFILE = /root/.rnd
+extensions = v3_req
+
+[ req ]
+default_bits = 2048
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+prompt = no
+string_mask = nombstr
+
+[ req_distinguished_name ]
+organizationalUnitName = PVE Cluster Node
+organizationName = Proxmox Virtual Environment
+commonName = $fqdn
+
+[ v3_req ]
+basicConstraints = CA:FALSE
+extendedKeyUsage = serverAuth
+subjectAltName = $names
+__EOD
+
+    my $cfgfn = "/tmp/pvesslconf-$$.tmp";
+    my $fh = IO::File->new ($cfgfn, "w");
+    print $fh $sslconf;
+    close ($fh);
+
+    my $reqfn = "/tmp/pvecertreq-$$.tmp";
+    unlink $reqfn;
+
+    my $pvessl_key_fn = "$pmxcfs_base_dir/nodes/$nodename/pve-ssl.key";
+    eval {
+       run_silent_cmd([
+           'openssl', 'req', '-batch', '-new', '-config', $cfgfn, '-key', $pvessl_key_fn, '-out', $reqfn
+       ]);
+    };
+
+    if (my $err = $@) {
+       unlink $reqfn;
+       unlink $cfgfn;
+       die "unable to generate pve certificate request:\n$err";
+    }
+
+    update_serial("0000000000000000") if ! -f $pveca_srl_fn;
+
+    # get ca expiry
+    my $cainfo = PVE::Certificate::get_certificate_info($pveca_cert_fn);
+    my $daysleft = int(($cainfo->{notafter} - time())/(24*60*60));
+
+    if ($daysleft < 14) {
+       die "CA expires in less than 2 weeks, unable to generate certificate.\n";
+    }
+
+    # let the certificate expire a little sooner that the ca, so subtract 2 days
+    $daysleft -= 2;
+
+    # we want the certificates to only last 2 years, since some browsers
+    # do not accept certificates with very long expiry time
+    if ($daysleft >= 2*365) {
+       $daysleft = 2*365;
+    }
+
+    eval {
+       run_silent_cmd([
+           'faketime', 'yesterday', # NOTE: wrap openssl with faketime to prevent bug #904
+           'openssl', 'x509', '-req', '-in', $reqfn, '-days', $daysleft, '-out', $pvessl_cert_fn,
+           '-CAkey', $pveca_key_fn, '-CA', $pveca_cert_fn, '-CAserial', $pveca_srl_fn, '-extfile', $cfgfn
+       ]);
+    };
+
+    if (my $err = $@) {
+       unlink $reqfn;
+       unlink $cfgfn;
+       die "unable to generate pve ssl certificate:\n$err";
+    }
+
+    unlink $cfgfn;
+    unlink $reqfn;
+}
+
+sub gen_pve_node_files {
+    my ($nodename, $ip, $opt_force) = @_;
+
+    gen_local_dirs($nodename);
+
+    gen_auth_key();
+
+    # make sure we have a (cluster wide) secret
+    # for CSRFR prevention
+    gen_pve_www_key();
+
+    # make sure we have a (per node) private key
+    gen_pve_ssl_key($nodename);
+
+    # make sure we have a CA
+    my $force = gen_pveca_cert();
+
+    $force = 1 if $opt_force;
+
+    gen_pve_ssl_cert($force, $nodename, $ip);
+}
+
+my $vzdump_cron_dummy = <<__EOD;
+# cluster wide vzdump cron schedule
+# Atomatically generated file - do not edit
+
+PATH="/usr/sbin:/usr/bin:/sbin:/bin"
+
+__EOD
+
+sub gen_pve_vzdump_symlink {
+
+    my $filename = "/etc/pve/vzdump.cron";
+
+    my $link_fn = "/etc/cron.d/vzdump";
+
+    if ((-f $filename) && (! -l $link_fn)) {
+       rename($link_fn, "/root/etc_cron_vzdump.org"); # make backup if file exists
+       symlink($filename, $link_fn);
+    }
+}
+
+sub gen_pve_vzdump_files {
+
+    my $filename = "/etc/pve/vzdump.cron";
+
+    PVE::Tools::file_set_contents($filename, $vzdump_cron_dummy)
+       if ! -f $filename;
+
+    gen_pve_vzdump_symlink();
+};
+
+# join helpers
+
+sub assert_joinable {
+    my ($local_addr, $links, $force) = @_;
+
+    my $errors = '';
+    my $error = sub { $errors .= "* $_[0]\n"; };
+
+    if (-f $authfile) {
+       $error->("authentication key '$authfile' already exists");
+    }
+
+    if (-f $clusterconf)  {
+       $error->("cluster config '$clusterconf' already exists");
+    }
+
+    my $vmlist = PVE::Cluster::get_vmlist();
+    if ($vmlist && $vmlist->{ids} && scalar(keys %{$vmlist->{ids}})) {
+       $error->("this host already contains virtual guests");
+    }
+
+    if (PVE::Tools::run_command(['corosync-quorumtool', '-l'], noerr => 1, quiet => 1) == 0) {
+       $error->("corosync is already running, is this node already in a cluster?!");
+    }
+
+    # check if corosync ring IPs are configured on the current nodes interfaces
+    my $check_ip = sub {
+       my $ip = shift // return;
+       my $logid = shift;
+       if (!PVE::JSONSchema::pve_verify_ip($ip, 1)) {
+           my $host = $ip;
+           eval { $ip = PVE::Network::get_ip_from_hostname($host); };
+           if ($@) {
+               $error->("$logid: cannot use '$host': $@\n") ;
+               return;
+           }
+       }
+
+       my $cidr = (Net::IP::ip_is_ipv6($ip)) ? "$ip/128" : "$ip/32";
+       my $configured_ips = PVE::Network::get_local_ip_from_cidr($cidr);
+
+       $error->("$logid: cannot use IP '$ip', not found on local node!\n")
+           if scalar(@$configured_ips) < 1;
+    };
+
+    $check_ip->($local_addr, 'local node address');
+
+    foreach my $link (keys %$links) {
+       $check_ip->($links->{$link}->{address}, "link$link");
+    }
+
+    if ($errors) {
+       warn "detected the following error(s):\n$errors";
+       die "Check if node may join a cluster failed!\n" if !$force;
+       warn "\nWARNING : detected error but forced to continue!\n\n";
+    }
+}
+
+sub join {
+    my ($param) = @_;
+
+    my $nodename = PVE::INotify::nodename();
+    my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
+
+    my $links = PVE::Corosync::extract_corosync_link_args($param);
+
+    # check if we can join with the given parameters and current node state
+    assert_joinable($local_ip_address, $links, $param->{force});
+
+    setup_sshd_config();
+    setup_rootsshconfig();
+    setup_ssh_keys();
+
+    # make sure known_hosts is on local filesystem
+    ssh_unmerge_known_hosts();
+
+    my $host = $param->{hostname};
+    my $conn_args = {
+       username => 'root@pam',
+       password => $param->{password},
+       cookie_name => 'PVEAuthCookie',
+       protocol => 'https',
+       host => $host,
+       port => 8006,
+    };
+
+    if (my $fp = $param->{fingerprint}) {
+       $conn_args->{cached_fingerprints} = { uc($fp) => 1 };
+    } else {
+       # API schema ensures that we can only get here from CLI handler
+       $conn_args->{manual_verification} = 1;
+    }
+
+    print "Establishing API connection with host '$host'\n";
+
+    my $conn = PVE::APIClient::LWP->new(%$conn_args);
+    $conn->login();
+
+    # login raises an exception on failure, so if we get here we're good
+    print "Login succeeded.\n";
+
+    print "check cluster join API version\n";
+    my $apiver = eval { $conn->get("/cluster/config/apiversion") } // 0;
+    assert_we_can_join_cluster_version($apiver);
+
+    my $args = {};
+    $args->{force} = $param->{force} if defined($param->{force});
+    $args->{nodeid} = $param->{nodeid} if $param->{nodeid};
+    $args->{votes} = $param->{votes} if defined($param->{votes});
+    foreach my $link (keys %$links) {
+       $args->{"link$link"} = PVE::Corosync::print_corosync_link($links->{$link});
+    }
+
+    # this will be used as fallback if no links are specified
+    if (!%$links) {
+       $args->{link0} = $local_ip_address if $apiver == 0;
+       $args->{new_node_ip} = $local_ip_address if $apiver >= 1;
+
+       print "No cluster network links passed explicitly, fallback to local node"
+           . " IP '$local_ip_address'\n";
+    }
+
+    if ($apiver >= 1) {
+       $args->{apiversion} = JOIN_API_VERSION;
+    }
+
+    print "Request addition of this node\n";
+    my $res = eval { $conn->post("/cluster/config/nodes/$nodename", $args); };
+    if (my $err = $@) {
+       if (ref($err) && $err->isa('PVE::APIClient::Exception')) {
+           # we received additional info about the error, show the user
+           chomp $err->{msg};
+           warn "An error occurred on the cluster node: $err->{msg}\n";
+           foreach my $key (sort keys %{$err->{errors}}) {
+               my $symbol = ($key =~ m/^warning/) ? '*' : '!';
+               warn "$symbol $err->{errors}->{$key}\n";
+           }
+
+           die "Cluster join aborted!\n";
+       }
+
+       die $@;
+    }
+
+    if (defined($res->{warnings})) {
+       foreach my $warn (@{$res->{warnings}}) {
+           warn "cluster: $warn\n";
+       }
+    }
+
+    print "Join request OK, finishing setup locally\n";
+
+    # added successfuly - now prepare local node
+    finish_join($nodename, $res->{corosync_conf}, $res->{corosync_authkey});
+}
+
+sub finish_join {
+    my ($nodename, $corosync_conf, $corosync_authkey) = @_;
+
+    mkdir "$localclusterdir";
+    PVE::Tools::file_set_contents($authfile, $corosync_authkey);
+    PVE::Tools::file_set_contents($localclusterconf, $corosync_conf);
+
+    print "stopping pve-cluster service\n";
+    my $cmd = ['systemctl', 'stop', 'pve-cluster'];
+    PVE::Tools::run_command($cmd, errmsg => "can't stop pve-cluster service");
+
+    my $dbfile = PVE::Cluster::cfs_backup_database();
+    unlink $dbfile;
+
+    $cmd = ['systemctl', 'start', 'corosync', 'pve-cluster'];
+    PVE::Tools::run_command($cmd, errmsg => "starting pve-cluster failed");
+
+    # wait for quorum
+    my $printqmsg = 1;
+    while (!PVE::Cluster::check_cfs_quorum(1)) {
+       if ($printqmsg) {
+           print "waiting for quorum...";
+           STDOUT->flush();
+           $printqmsg = 0;
+       }
+       sleep(1);
+    }
+    print "OK\n" if !$printqmsg;
+
+    updatecerts_and_ssh(1);
+
+    print "generated new node certificate, restart pveproxy and pvedaemon services\n";
+    PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pvedaemon', 'pveproxy']);
+
+    print "successfully added node '$nodename' to cluster.\n";
+}
+
+sub updatecerts_and_ssh {
+    my ($force_new_cert, $silent) = @_;
+
+    my $p = sub { print "$_[0]\n" if !$silent };
+
+    setup_rootsshconfig();
+
+    gen_pve_vzdump_symlink();
+
+    if (!PVE::Cluster::check_cfs_quorum(1)) {
+       return undef if $silent;
+       die "no quorum - unable to update files\n";
+    }
+
+    setup_ssh_keys();
+
+    my $nodename = PVE::INotify::nodename();
+    my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
+
+    $p->("(re)generate node files");
+    $p->("generate new node certificate") if $force_new_cert;
+    gen_pve_node_files($nodename, $local_ip_address, $force_new_cert);
+
+    $p->("merge authorized SSH keys and known hosts");
+    ssh_merge_keys();
+    ssh_merge_known_hosts($nodename, $local_ip_address, 1);
+    gen_pve_vzdump_files();
+}
+
+1;
diff --git a/src/PVE/Corosync.pm b/src/PVE/Corosync.pm
new file mode 100644 (file)
index 0000000..2eb38b8
--- /dev/null
@@ -0,0 +1,528 @@
+package PVE::Corosync;
+
+use strict;
+use warnings;
+
+use Clone 'clone';
+use Digest::SHA;
+use Net::IP qw(ip_is_ipv6);
+use Scalar::Util qw(weaken);
+use Socket qw(AF_INET AF_INET6 inet_ntop);
+
+use PVE::Cluster;
+use PVE::JSONSchema;
+use PVE::Tools;
+use PVE::Tools qw($IPV4RE $IPV6RE);
+
+my $basedir = "/etc/pve";
+our $link_addr_re = qw/^(ring|link)(\d+)_addr$/;
+
+my $conf_array_sections = {
+    node => 1,
+    interface => 1,
+};
+
+my $corosync_link_format = {
+    address => {
+       default_key => 1,
+       type => 'string', format => 'address',
+       format_description => 'IP',
+       description => "Hostname (or IP) of this corosync link address.",
+    },
+    priority => {
+       optional => 1,
+       type => 'integer',
+       minimum => 0,
+       maximum => 255,
+       default => 0,
+       description => "The priority for the link when knet is used in 'passive'"
+                    . " mode (default). Lower value means higher priority. Only"
+                    . " valid for cluster create, ignored on node add.",
+    },
+};
+my $corosync_link_desc = {
+    type => 'string', format => $corosync_link_format,
+    description => "Address and priority information of a single corosync link."
+                . " (up to 8 links supported; link0..link7)",
+    optional => 1,
+};
+PVE::JSONSchema::register_standard_option("corosync-link", $corosync_link_desc);
+
+sub parse_corosync_link {
+    my ($value) = @_;
+
+    return undef if !defined($value);
+
+    return PVE::JSONSchema::parse_property_string($corosync_link_format, $value);
+}
+
+sub print_corosync_link {
+    my ($link) = @_;
+
+    return undef if !defined($link);
+
+    return PVE::JSONSchema::print_property_string($link, $corosync_link_format);
+}
+
+use constant MAX_LINK_INDEX => 7;
+
+sub add_corosync_link_properties {
+    my ($prop) = @_;
+
+    for my $lnum (0..MAX_LINK_INDEX) {
+       $prop->{"link$lnum"} = PVE::JSONSchema::get_standard_option("corosync-link");
+    }
+
+    return $prop;
+}
+
+sub extract_corosync_link_args {
+    my ($args) = @_;
+
+    my $links = {};
+    for my $lnum (0..MAX_LINK_INDEX) {
+       $links->{$lnum} = parse_corosync_link($args->{"link$lnum"})
+           if $args->{"link$lnum"};
+    }
+
+    return $links;
+}
+
+# a very simply parser ...
+sub parse_conf {
+    my ($filename, $raw) = @_;
+
+    return {} if !$raw;
+
+    my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : '');
+
+    $raw =~ s/#.*$//mg;
+    $raw =~ s/\r?\n/ /g;
+    $raw =~ s/\s+/ /g;
+    $raw =~ s/^\s+//;
+    $raw =~ s/\s*$//;
+
+    my @tokens = split(/\s/, $raw);
+
+    my $conf = { 'main' => {} };
+
+    my $stack = [];
+    my $section = $conf->{main};
+
+    while (defined(my $token = shift @tokens)) {
+       my $nexttok = $tokens[0];
+
+       if ($nexttok && ($nexttok eq '{')) {
+           shift @tokens; # skip '{'
+           my $new_section = {};
+           if ($conf_array_sections->{$token}) {
+               $section->{$token} = [] if !defined($section->{$token});
+               push @{$section->{$token}}, $new_section;
+           } elsif (!defined($section->{$token})) {
+               $section->{$token} = $new_section;
+           } else {
+               die "section '$token' already exists and not marked as array!\n";
+           }
+           push @$stack, $section;
+           $section = $new_section;
+           next;
+       }
+
+       if ($token eq '}') {
+           $section = pop @$stack;
+           die "parse error - uncexpected '}'\n" if !$section;
+           next;
+       }
+
+       my $key = $token;
+       die "missing ':' after key '$key'\n" if ! ($key =~ s/:$//);
+
+       die "parse error - no value for '$key'\n" if !defined($nexttok);
+       my $value = shift @tokens;
+
+       $section->{$key} = $value;
+    }
+
+    # make working with the config way easier
+    my ($totem, $nodelist) = $conf->{main}->@{"totem", "nodelist"};
+
+    $nodelist->{node} = {
+       map {
+           $_->{name} // $_->{ring0_addr} => $_
+       } @{$nodelist->{node}}
+    };
+    $totem->{interface} = {
+       map {
+           $_->{linknumber} // $_->{ringnumber} => $_
+       } @{$totem->{interface}}
+    };
+
+    $conf->{digest} = $digest;
+
+    return $conf;
+}
+
+sub write_conf {
+    my ($filename, $conf) = @_;
+
+    my $c = clone($conf->{main}) // die "no main section";
+
+    # retransform back for easier dumping
+    my $hash_to_array = sub {
+       my ($hash) = @_;
+       return [ $hash->@{sort keys %$hash} ];
+    };
+
+    $c->{nodelist}->{node} = &$hash_to_array($c->{nodelist}->{node});
+    $c->{totem}->{interface} = &$hash_to_array($c->{totem}->{interface});
+
+    my $dump_section_weak;
+    $dump_section_weak = sub {
+       my ($section, $prefix) = @_;
+
+       my $raw = '';
+
+       foreach my $k (sort keys %$section) {
+           my $v = $section->{$k};
+           if (ref($v) eq 'HASH') {
+               $raw .= $prefix . "$k {\n";
+               $raw .= $dump_section_weak->($v, "$prefix  ");
+               $raw .=  $prefix . "}\n";
+               $raw .= "\n" if !$prefix; # add extra newline at 1st level only
+           } elsif (ref($v) eq 'ARRAY') {
+               foreach my $child (@$v) {
+                   $raw .= $prefix . "$k {\n";
+                   $raw .= $dump_section_weak->($child, "$prefix  ");
+                   $raw .=  $prefix . "}\n";
+               }
+           } elsif (!ref($v)) {
+               die "got undefined value for key '$k'!\n" if !defined($v);
+               $raw .= $prefix . "$k: $v\n";
+           } else {
+               die "unexpected reference in config hash: $k => ". ref($v) ."\n";
+           }
+       }
+
+       return $raw;
+    };
+    my $dump_section = $dump_section_weak;
+    weaken($dump_section_weak);
+
+    my $raw = $dump_section->($c, '');
+
+    return $raw;
+}
+
+# read only - use atomic_write_conf method to write
+PVE::Cluster::cfs_register_file('corosync.conf', \&parse_conf);
+# this is read/write
+PVE::Cluster::cfs_register_file('corosync.conf.new', \&parse_conf,
+                               \&write_conf);
+
+sub check_conf_exists {
+    my ($noerr) = @_;
+
+    my $exists = -f "$basedir/corosync.conf";
+
+    die "Error: Corosync config '$basedir/corosync.conf' does not exist - is this node part of a cluster?\n"
+       if !$noerr && !$exists;
+
+    return $exists;
+}
+
+sub update_nodelist {
+    my ($conf, $nodelist) = @_;
+
+    $conf->{main}->{nodelist}->{node} = $nodelist;
+
+    atomic_write_conf($conf);
+}
+
+sub nodelist {
+    my ($conf) = @_;
+    return clone($conf->{main}->{nodelist}->{node});
+}
+
+sub totem_config {
+    my ($conf) = @_;
+    return clone($conf->{main}->{totem});
+}
+
+# caller must hold corosync.conf cfs lock if used in read-modify-write cycle
+sub atomic_write_conf {
+    my ($conf, $no_increase_version) = @_;
+
+    if (!$no_increase_version) {
+       die "invalid corosync config: unable to read config version\n"
+           if !defined($conf->{main}->{totem}->{config_version});
+       $conf->{main}->{totem}->{config_version}++;
+    }
+
+    PVE::Cluster::cfs_write_file("corosync.conf.new", $conf);
+
+    rename("/etc/pve/corosync.conf.new", "/etc/pve/corosync.conf")
+       || die "activating corosync.conf.new failed - $!\n";
+}
+
+# for creating a new cluster with the current node
+# params are those from the API/CLI cluster create call
+sub create_conf {
+    my ($nodename, $param) = @_;
+
+    my $clustername = $param->{clustername};
+    my $nodeid = $param->{nodeid} || 1;
+    my $votes = $param->{votes} || 1;
+
+    my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
+
+    my $links = extract_corosync_link_args($param);
+
+    # if no links given, fall back to local IP as link0
+    $links->{0} = { address => $local_ip_address }
+       if !%$links;
+
+    my $conf = {
+       totem => {
+           version => 2, # protocol version
+           secauth => 'on',
+           cluster_name => $clustername,
+           config_version => 0,
+           ip_version => 'ipv4-6',
+           link_mode => 'passive',
+           interface => {},
+       },
+       nodelist => {
+           node => {
+               $nodename => {
+                   name => $nodename,
+                   nodeid => $nodeid,
+                   quorum_votes => $votes,
+               },
+           },
+       },
+       quorum => {
+           provider => 'corosync_votequorum',
+       },
+       logging => {
+           to_syslog => 'yes',
+           debug => 'off',
+       },
+    };
+    my $totem = $conf->{totem};
+    my $node = $conf->{nodelist}->{node}->{$nodename};
+
+    foreach my $lnum (keys %$links) {
+       my $link = $links->{$lnum};
+
+       $totem->{interface}->{$lnum} = { linknumber => $lnum };
+
+       my $prio = $link->{priority};
+       $totem->{interface}->{$lnum}->{knet_link_priority} = $prio if $prio;
+
+       $node->{"ring${lnum}_addr"} = $link->{address};
+    }
+
+    return { main => $conf };
+}
+
+# returns (\@errors, \@warnings) to the caller, does *not* 'die' or 'warn'
+# verification was successful if \@errors is empty
+sub verify_conf {
+    my ($conf) = @_;
+
+    my @errors = ();
+    my @warnings = ();
+
+    my $nodelist = nodelist($conf);
+    if (!$nodelist) {
+       push @errors, "no nodes found";
+       return (\@errors, \@warnings);
+    }
+
+    my $totem = $conf->{main}->{totem};
+    if (!$totem) {
+       push @errors, "no totem found";
+       return (\@errors, \@warnings);
+    }
+
+    if ((!defined($totem->{secauth}) || $totem->{secauth} ne 'on') &&
+       (!defined($totem->{crypto_cipher}) || $totem->{crypto_cipher} eq 'none')) {
+       push @warnings, "warning: authentication/encryption is not explicitly enabled"
+           . " (secauth / crypto_cipher / crypto_hash)";
+    }
+
+    my $interfaces = $totem->{interface};
+
+    my $verify_link_ip = sub {
+       my ($key, $link, $node) = @_;
+       my ($resolved_ip, undef) = resolve_hostname_like_corosync($link, $conf);
+       if (!defined($resolved_ip)) {
+           push @warnings, "warning: unable to resolve $key '$link' for node '$node'"
+               . " to an IP address according to Corosync's resolve strategy -"
+               . " cluster could fail on restart!";
+       } elsif ($resolved_ip ne $link) {
+           push @warnings, "warning: $key '$link' for node '$node' resolves to"
+               . " '$resolved_ip' - consider replacing it with the currently"
+               . " resolved IP address for stability";
+       }
+    };
+
+    # sort for output order stability
+    my @node_names = sort keys %$nodelist;
+
+    my $node_links = {};
+    foreach my $node (@node_names) {
+       my $options = $nodelist->{$node};
+       foreach my $opt (keys %$options) {
+           my ($linktype, $linkid) = parse_link_entry($opt);
+           next if !defined($linktype);
+           $node_links->{$node}->{$linkid} = {
+               name => "${linktype}${linkid}_addr",
+               addr => $options->{$opt},
+           };
+       }
+    }
+
+    if (%$interfaces) {
+       # if interfaces are defined, *all* links must have a matching interface
+       # definition, and vice versa
+       for my $link (0..MAX_LINK_INDEX) {
+           my $have_interface = defined($interfaces->{$link});
+           foreach my $node (@node_names) {
+               my $linkdef = $node_links->{$node}->{$link};
+               if (defined($linkdef)) {
+                   $verify_link_ip->($linkdef->{name}, $linkdef->{addr}, $node);
+                   if (!$have_interface) {
+                       push @errors, "node '$node' has '$linkdef->{name}', but"
+                           . " there is no interface number $link configured";
+                   }
+               } else {
+                   if ($have_interface) {
+                       push @errors, "node '$node' is missing address for"
+                           . "interface number $link";
+                   }
+               }
+           }
+       }
+    } else {
+       # without interfaces, only check that links are consistent among nodes
+       for my $link (0..MAX_LINK_INDEX) {
+           my $nodes_with_link = {};
+           foreach my $node (@node_names) {
+               my $linkdef = $node_links->{$node}->{$link};
+               if (defined($linkdef)) {
+                   $verify_link_ip->($linkdef->{name}, $linkdef->{addr}, $node);
+                   $nodes_with_link->{$node} = 1;
+               }
+           }
+
+           if (%$nodes_with_link) {
+               foreach my $node (@node_names) {
+                   if (!defined($nodes_with_link->{$node})) {
+                       push @errors, "node '$node' is missing link $link,"
+                           . " which is configured on other nodes";
+                   }
+               }
+           }
+       }
+    }
+
+    return (\@errors, \@warnings);
+}
+
+# returns ($linktype, $linkid) with $linktype being 'ring' for now, and possibly
+# 'link' with upcoming corosync versions
+sub parse_link_entry {
+    my ($opt) = @_;
+    return (undef, undef) if $opt !~ $link_addr_re;
+    return ($1, $2);
+}
+
+sub for_all_corosync_addresses {
+    my ($corosync_conf, $ip_version, $func) = @_;
+
+    my $nodelist = nodelist($corosync_conf);
+    return if !defined($nodelist);
+
+    # iterate sorted to make rules deterministic (for change detection)
+    foreach my $node_name (sort keys %$nodelist) {
+       my $node_config = $nodelist->{$node_name};
+       foreach my $node_key (sort keys %$node_config) {
+           if ($node_key =~ $link_addr_re) {
+               my $node_address = $node_config->{$node_key};
+
+               my($ip, $version) = resolve_hostname_like_corosync($node_address, $corosync_conf);
+               next if !defined($ip);
+               next if defined($version) && defined($ip_version) && $version != $ip_version;
+
+               $func->($node_name, $ip, $version, $node_key);
+           }
+       }
+    }
+}
+
+# NOTE: Corosync actually only resolves on startup or config change, but we
+# currently do not have an easy way to synchronize our behaviour to that.
+sub resolve_hostname_like_corosync {
+    my ($hostname, $corosync_conf) = @_;
+
+    my $corosync_strategy = $corosync_conf->{main}->{totem}->{ip_version};
+    $corosync_strategy = lc ($corosync_strategy // "ipv6-4");
+
+    my $match_ip_and_version = sub {
+       my ($addr) = @_;
+
+       return undef if !defined($addr);
+
+       if ($addr =~ m/^$IPV4RE$/) {
+           return ($addr, 4);
+       } elsif ($addr =~ m/^$IPV6RE$/) {
+           return ($addr, 6);
+       }
+
+       return undef;
+    };
+
+    my ($resolved_ip, $ip_version) = $match_ip_and_version->($hostname);
+
+    return ($resolved_ip, $ip_version) if defined($resolved_ip);
+
+    my $resolved_ip4;
+    my $resolved_ip6;
+
+    my @resolved_raw;
+    eval { @resolved_raw = PVE::Tools::getaddrinfo_all($hostname); };
+
+    return undef if ($@ || !@resolved_raw);
+
+    foreach my $socket_info (@resolved_raw) {
+       next if !$socket_info->{addr};
+
+       my ($family, undef, $host) = PVE::Tools::unpack_sockaddr_in46($socket_info->{addr});
+
+       if ($family == AF_INET && !defined($resolved_ip4)) {
+           $resolved_ip4 = inet_ntop(AF_INET, $host);
+       } elsif ($family == AF_INET6 && !defined($resolved_ip6)) {
+           $resolved_ip6 = inet_ntop(AF_INET6, $host);
+       }
+
+       last if defined($resolved_ip4) && defined($resolved_ip6);
+    }
+
+    # corosync_strategy specifies the order in which IP addresses are resolved
+    # by corosync. We need to match that order, to ensure we create firewall
+    # rules for the correct address family.
+    if ($corosync_strategy eq "ipv4") {
+       $resolved_ip = $resolved_ip4;
+    } elsif ($corosync_strategy eq "ipv6") {
+       $resolved_ip = $resolved_ip6;
+    } elsif ($corosync_strategy eq "ipv6-4") {
+       $resolved_ip = $resolved_ip6 // $resolved_ip4;
+    } elsif ($corosync_strategy eq "ipv4-6") {
+       $resolved_ip = $resolved_ip4 // $resolved_ip6;
+    }
+
+    return $match_ip_and_version->($resolved_ip);
+}
+
+1;
diff --git a/src/PVE/DataCenterConfig.pm b/src/PVE/DataCenterConfig.pm
new file mode 100644 (file)
index 0000000..5f12450
--- /dev/null
@@ -0,0 +1,553 @@
+package PVE::DataCenterConfig;
+
+use strict;
+use warnings;
+
+use PVE::JSONSchema qw(parse_property_string);
+use PVE::Tools;
+use PVE::Cluster;
+
+my $crs_format = {
+    ha => {
+       type => 'string',
+       enum => ['basic', 'static'],
+       optional => 1,
+       default => 'basic',
+       description => "Use this resource scheduler mode for HA.",
+       verbose_description => "Configures how the HA manager should select nodes to start or ".
+           "recover services. With 'basic', only the number of services is used, with 'static', ".
+           "static CPU and memory configuration of services is considered.",
+    },
+    'ha-rebalance-on-start' => {
+       type => 'boolean',
+       optional => 1,
+       default => 0,
+       description => "Set to use CRS for selecting a suited node when a HA services request-state"
+           ." changes from stop to start.",
+    }
+};
+
+my $migration_format = {
+    type => {
+       default_key => 1,
+       type => 'string',
+       enum => ['secure', 'insecure'],
+       description => "Migration traffic is encrypted using an SSH tunnel by " .
+         "default. On secure, completely private networks this can be " .
+         "disabled to increase performance.",
+       default => 'secure',
+    },
+    network => {
+       optional => 1,
+       type => 'string', format => 'CIDR',
+       format_description => 'CIDR',
+       description => "CIDR of the (sub) network that is used for migration."
+    },
+};
+
+my $notification_format = {
+    'package-updates' => {
+       type => 'string',
+       enum => ['auto', 'always', 'never'],
+       description => "Control when the daily update job should send out notification mails.",
+       verbose_description => "Control how often the daily update job should send out notification mails:\n"
+           ."* 'auto' daily for systems with a valid subscription, as those are assumed to be "
+           ." production-ready and thus should know about pending updates.\n"
+           ."* 'always' every update, if there are new pending updates.\n"
+           ."* 'never' never send a notification for new pending updates.\n",
+       default => 'auto',
+    },
+};
+
+my $ha_format = {
+    shutdown_policy => {
+       type => 'string',
+       enum => ['freeze', 'failover', 'conditional', 'migrate'],
+       description => "The policy for HA services on node shutdown. 'freeze' disables ".
+           "auto-recovery, 'failover' ensures recovery, 'conditional' recovers on ".
+           "poweroff and freezes on reboot. 'migrate' will migrate running services ".
+           "to other nodes, if possible. With 'freeze' or 'failover', HA Services will ".
+           "always get stopped first on shutdown.",
+       verbose_description => "Describes the policy for handling HA services on poweroff ".
+           "or reboot of a node. Freeze will always freeze services which are still located ".
+           "on the node on shutdown, those services won't be recovered by the HA manager. ".
+           "Failover will not mark the services as frozen and thus the services will get ".
+           "recovered to other nodes, if the shutdown node does not come up again quickly ".
+           "(< 1min). 'conditional' chooses automatically depending on the type of shutdown, ".
+           "i.e., on a reboot the service will be frozen but on a poweroff the service will ".
+           "stay as is, and thus get recovered after about 2 minutes. ".
+           "Migrate will try to move all running services to another node when a reboot or ".
+           "shutdown was triggered. The poweroff process will only continue once no running services ".
+           "are located on the node anymore. If the node comes up again, the service will ".
+           "be moved back to the previously powered-off node, at least if no other migration, ".
+           "reloaction or recovery took place.",
+       default => 'conditional',
+    }
+};
+
+my $next_id_format = {
+    lower => {
+       type => 'integer',
+       description => "Lower, inclusive boundary for free next-id API range.",
+       min => 100,
+       max => 1000 * 1000 * 1000 - 1,
+       default => 100,
+       optional => 1,
+    },
+    upper => {
+       type => 'integer',
+       description => "Upper, exclusive boundary for free next-id API range.",
+       min => 100,
+       max => 1000 * 1000 * 1000,
+       default => 1000 * 1000, # lower than the maximum on purpose
+       optional => 1,
+    },
+};
+
+my $u2f_format = {
+    appid => {
+       type => 'string',
+       description => "U2F AppId URL override. Defaults to the origin.",
+       format_description => 'APPID',
+       optional => 1,
+    },
+    origin => {
+       type => 'string',
+       description => "U2F Origin override. Mostly useful for single nodes with a single URL.",
+       format_description => 'URL',
+       optional => 1,
+    },
+};
+
+my $webauthn_format = {
+    rp => {
+       type => 'string',
+       description =>
+           'Relying party name. Any text identifier.'
+           .' Changing this *may* break existing credentials.',
+       format_description => 'RELYING_PARTY',
+       optional => 1,
+    },
+    origin => {
+       type => 'string',
+       description =>
+           'Site origin. Must be a `https://` URL (or `http://localhost`).'
+           .' Should contain the address users type in their browsers to access'
+           .' the web interface.'
+           .' Changing this *may* break existing credentials.',
+       format_description => 'URL',
+       optional => 1,
+    },
+    id => {
+       type => 'string',
+       description =>
+           'Relying party ID. Must be the domain name without protocol, port or location.'
+           .' Changing this *will* break existing credentials.',
+       format_description => 'DOMAINNAME',
+       optional => 1,
+    },
+    'allow-subdomains' => {
+       type => 'boolean',
+       description => 'Whether to allow the origin to be a subdomain, rather than the exact URL.',
+       optional => 1,
+       default => 1,
+    },
+};
+
+PVE::JSONSchema::register_format('mac-prefix', \&pve_verify_mac_prefix);
+sub pve_verify_mac_prefix {
+    my ($mac_prefix, $noerr) = @_;
+
+    if ($mac_prefix !~ m/^[a-f0-9][02468ace](?::[a-f0-9]{2}){0,2}:?$/i) {
+       return undef if $noerr;
+       die "value is not a valid unicast MAC address prefix\n";
+    }
+    return $mac_prefix;
+}
+
+my $COLOR_RE = '[0-9a-fA-F]{6}';
+my $TAG_COLOR_OVERRIDE_RE = "(?:${PVE::JSONSchema::PVE_TAG_RE}:${COLOR_RE}(?:\:${COLOR_RE})?)";
+
+my $tag_style_format = {
+    'shape' => {
+       optional => 1,
+       type => 'string',
+       enum => ['full', 'circle', 'dense', 'none'],
+       default => 'circle',
+       description => "Tag shape for the web ui tree. 'full' draws the full tag. "
+           ."'circle' draws only a circle with the background color. "
+           ."'dense' only draws a small rectancle (useful when many tags are assigned to each guest)."
+           ."'none' disables showing the tags.",
+    },
+    'color-map' => {
+       optional => 1,
+       type => 'string',
+       pattern => "${TAG_COLOR_OVERRIDE_RE}(?:\;$TAG_COLOR_OVERRIDE_RE)*",
+       typetext => '<tag>:<hex-color>[:<hex-color-for-text>][;<tag>=...]',
+       description => "Manual color mapping for tags (semicolon separated).",
+    },
+    ordering => {
+       optional => 1,
+       type => 'string',
+       enum => ['config', 'alphabetical'],
+       default => 'alphabetical',
+       description => 'Controls the sorting of the tags in the web-interface and the API update.',
+    },
+    'case-sensitive' => {
+       type => 'boolean',
+       description => 'Controls if filtering for unique tags on update should check case-sensitive.',
+       optional => 1,
+       default => 0,
+    },
+};
+
+my $user_tag_privs_format = {
+    'user-allow' => {
+       optional => 1,
+       type => 'string',
+       enum => ['none', 'list', 'existing', 'free'],
+       default => 'free',
+       description => "Controls tag usage for users without `Sys.Modify` on `/` by either "
+           ."allowing `none`, a `list`, already `existing` or anything (`free`).",
+       verbose_description => "Controls which tags can be set or deleted on resources a user "
+           ."controls (such as guests). Users with the `Sys.Modify` privilege on `/` are always "
+           ." unrestricted. "
+           ."* 'none' no tags are usable. "
+           ."* 'list' tags from 'user-allow-list' are usable. "
+           ."* 'existing' like list, but already existing tags of resources are also usable."
+           ."* 'free' no tag restrictions.",
+    },
+    'user-allow-list' => {
+       optional => 1,
+       type => 'string',
+       pattern => "${PVE::JSONSchema::PVE_TAG_RE}(?:\;${PVE::JSONSchema::PVE_TAG_RE})*",
+       typetext => "<tag>[;<tag>...]",
+       description => "List of tags users are allowed to set and delete (semicolon separated) "
+           ."for 'user-allow' values 'list' and 'existing'.",
+    },
+};
+
+my $datacenter_schema = {
+    type => "object",
+    additionalProperties => 0,
+    properties => {
+       crs => {
+           optional => 1,
+           type => 'string', format => $crs_format,
+           description => "Cluster resource scheduling settings.",
+       },
+       keyboard => {
+           optional => 1,
+           type => 'string',
+           description => "Default keybord layout for vnc server.",
+           enum => PVE::Tools::kvmkeymaplist(),
+       },
+       language => {
+           optional => 1,
+           type => 'string',
+           description => "Default GUI language.",
+           enum => [
+               'ca',
+               'da',
+               'de',
+               'en',
+               'es',
+               'eu',
+               'fa',
+               'fr',
+               'he',
+               'it',
+               'ja',
+               'nb',
+               'nn',
+               'pl',
+               'pt_BR',
+               'ru',
+               'sl',
+               'sv',
+               'tr',
+               'zh_CN',
+               'zh_TW',
+           ],
+       },
+       http_proxy => {
+           optional => 1,
+           type => 'string',
+           description => "Specify external http proxy which is used for downloads (example: 'http://username:password\@host:port/')",
+           pattern => "http://.*",
+       },
+       # FIXME: remove with 8.0 (add check to pve7to8!), merged into "migration" since 4.3
+       migration_unsecure => {
+           optional => 1,
+           type => 'boolean',
+           description => "Migration is secure using SSH tunnel by default. " .
+             "For secure private networks you can disable it to speed up " .
+             "migration. Deprecated, use the 'migration' property instead!",
+       },
+       'next-id' => {
+           optional => 1,
+           type => 'string',
+           format => $next_id_format,
+           description => "Control the range for the free VMID auto-selection pool.",
+       },
+       migration => {
+           optional => 1,
+           type => 'string', format => $migration_format,
+           description => "For cluster wide migration settings.",
+       },
+       console => {
+           optional => 1,
+           type => 'string',
+           description => "Select the default Console viewer. You can either use the builtin java"
+               ." applet (VNC; deprecated and maps to html5), an external virt-viewer comtatible application (SPICE), an HTML5 based vnc viewer (noVNC), or an HTML5 based console client (xtermjs). If the selected viewer is not available (e.g. SPICE not activated for the VM), the fallback is noVNC.",
+           # FIXME: remove 'applet' with 8.0 (add pve7to8 check!)
+           enum => ['applet', 'vv', 'html5', 'xtermjs'],
+       },
+       email_from => {
+           optional => 1,
+           type => 'string',
+           format => 'email-opt',
+           description => "Specify email address to send notification from (default is root@\$hostname)",
+       },
+       max_workers => {
+           optional => 1,
+           type => 'integer',
+           minimum => 1,
+           description => "Defines how many workers (per node) are maximal started ".
+             " on actions like 'stopall VMs' or task from the ha-manager.",
+       },
+       fencing => {
+           optional => 1,
+           type => 'string',
+           default => 'watchdog',
+           enum => [ 'watchdog', 'hardware', 'both' ],
+           description => "Set the fencing mode of the HA cluster. Hardware mode " .
+             "needs a valid configuration of fence devices in /etc/pve/ha/fence.cfg." .
+             " With both all two modes are used." .
+             "\n\nWARNING: 'hardware' and 'both' are EXPERIMENTAL & WIP",
+       },
+       ha => {
+           optional => 1,
+           type => 'string', format => $ha_format,
+           description => "Cluster wide HA settings.",
+       },
+       mac_prefix => {
+           optional => 1,
+           type => 'string',
+           format => 'mac-prefix',
+           description => 'Prefix for autogenerated MAC addresses.',
+       },
+       notify => {
+           optional => 1,
+           type => 'string', format => $notification_format,
+           description => "Cluster-wide notification settings.",
+       },
+       bwlimit => PVE::JSONSchema::get_standard_option('bwlimit'),
+       u2f => {
+           optional => 1,
+           type => 'string',
+           format => $u2f_format,
+           description => 'u2f',
+       },
+       webauthn => {
+           optional => 1,
+           type => 'string',
+           format => $webauthn_format,
+           description => 'webauthn configuration',
+       },
+       description => {
+           type => 'string',
+           description => "Datacenter description. Shown in the web-interface datacenter notes panel."
+               ." This is saved as comment inside the configuration file.",
+           maxLength => 64 * 1024,
+           optional => 1,
+       },
+       'tag-style' => {
+           optional => 1,
+           type => 'string',
+           description => "Tag style options.",
+           format => $tag_style_format,
+       },
+       'user-tag-access' => {
+           optional => 1,
+           type => 'string',
+           description => "Privilege options for user-settable tags",
+           format => $user_tag_privs_format,
+       },
+       'registered-tags' => {
+           optional => 1,
+           type => 'string',
+           description => "A list of tags that require a `Sys.Modify` on '/' to set and delete. "
+               ."Tags set here that are also in 'user-tag-access' also require `Sys.Modify`.",
+           pattern => "(?:${PVE::JSONSchema::PVE_TAG_RE};)*${PVE::JSONSchema::PVE_TAG_RE}",
+           typetext => "<tag>[;<tag>...]",
+       },
+    },
+};
+
+# make schema accessible from outside (for documentation)
+sub get_datacenter_schema { return $datacenter_schema };
+
+sub parse_datacenter_config {
+    my ($filename, $raw) = @_;
+
+    $raw = '' if !defined($raw);
+
+    # description may be comment or key-value pair (or both)
+    my $comment = '';
+    for my $line (split(/\n/, $raw)) {
+       if ($line =~ /^\#(.*)$/) {
+           $comment .= PVE::Tools::decode_text($1) . "\n";
+       }
+    }
+
+    # parse_config ignores lines with # => use $raw
+    my $res = PVE::JSONSchema::parse_config($datacenter_schema, $filename, $raw);
+
+    $res->{description} = $comment;
+
+    if (my $crs = $res->{crs}) {
+       $res->{crs} = parse_property_string($crs_format, $crs);
+    }
+
+    if (my $migration = $res->{migration}) {
+       $res->{migration} = parse_property_string($migration_format, $migration);
+    }
+
+    if (my $next_id = $res->{'next-id'}) {
+       $res->{'next-id'} = parse_property_string($next_id_format, $next_id);
+    }
+
+    if (my $ha = $res->{ha}) {
+       $res->{ha} = parse_property_string($ha_format, $ha);
+    }
+    if (my $notify = $res->{notify}) {
+       $res->{notify} = parse_property_string($notification_format, $notify);
+    }
+
+    if (my $u2f = $res->{u2f}) {
+       $res->{u2f} = parse_property_string($u2f_format, $u2f);
+    }
+
+    if (my $webauthn = $res->{webauthn}) {
+       $res->{webauthn} = parse_property_string($webauthn_format, $webauthn);
+    }
+
+    if (my $tag_style = $res->{'tag-style'}) {
+       $res->{'tag-style'} = parse_property_string($tag_style_format, $tag_style);
+    }
+
+    if (my $user_tag_privs = $res->{'user-tag-access'}) {
+       $res->{'user-tag-access'} =
+           parse_property_string($user_tag_privs_format, $user_tag_privs);
+
+       if (my $user_tags = $res->{'user-tag-access'}->{'user-allow-list'}) {
+           $res->{'user-tag-access'}->{'user-allow-list'} = [split(';', $user_tags)];
+       }
+    }
+
+    if (my $admin_tags = $res->{'registered-tags'}) {
+       $res->{'registered-tags'} = [split(';', $admin_tags)];
+    }
+
+    # for backwards compatibility only, new migration property has precedence
+    if (defined($res->{migration_unsecure})) {
+       if (defined($res->{migration}->{type})) {
+           warn "deprecated setting 'migration_unsecure' and new 'migration: type' " .
+             "set at same time! Ignore 'migration_unsecure'\n";
+       } else {
+           $res->{migration}->{type} = ($res->{migration_unsecure}) ? 'insecure' : 'secure';
+       }
+    }
+
+    # for backwards compatibility only, applet maps to html5
+    if (defined($res->{console}) && $res->{console} eq 'applet') {
+       $res->{console} = 'html5';
+    }
+
+    return $res;
+}
+
+sub write_datacenter_config {
+    my ($filename, $cfg) = @_;
+
+    # map deprecated setting to new one
+    if (defined($cfg->{migration_unsecure}) && !defined($cfg->{migration})) {
+       my $migration_unsecure = delete $cfg->{migration_unsecure};
+       $cfg->{migration}->{type} = ($migration_unsecure) ? 'insecure' : 'secure';
+    }
+
+    # map deprecated applet setting to html5
+    if (defined($cfg->{console}) && $cfg->{console} eq 'applet') {
+       $cfg->{console} = 'html5';
+    }
+
+    if (ref(my $crs = $cfg->{crs})) {
+       $cfg->{crs} = PVE::JSONSchema::print_property_string($crs, $crs_format);
+    }
+
+    if (ref(my $migration = $cfg->{migration})) {
+       $cfg->{migration} = PVE::JSONSchema::print_property_string($migration, $migration_format);
+    }
+
+    if (defined(my $next_id = $cfg->{'next-id'})) {
+        $next_id = parse_property_string($next_id_format, $next_id) if !ref($next_id);
+
+       my $lower = int($next_id->{lower} // $next_id_format->{lower}->{default});
+       my $upper = int($next_id->{upper} // $next_id_format->{upper}->{default});
+
+       die "lower ($lower) <= upper ($upper) boundary rule broken\n" if $lower > $upper;
+
+       $cfg->{'next-id'} = PVE::JSONSchema::print_property_string($next_id, $next_id_format);
+    }
+
+    if (ref(my $ha = $cfg->{ha})) {
+       $cfg->{ha} = PVE::JSONSchema::print_property_string($ha, $ha_format);
+    }
+    if (ref(my $notify = $cfg->{notify})) {
+       $cfg->{notify} = PVE::JSONSchema::print_property_string($notify, $notification_format);
+    }
+
+    if (ref(my $u2f = $cfg->{u2f})) {
+       $cfg->{u2f} = PVE::JSONSchema::print_property_string($u2f, $u2f_format);
+    }
+
+    if (ref(my $webauthn = $cfg->{webauthn})) {
+       $cfg->{webauthn} = PVE::JSONSchema::print_property_string($webauthn, $webauthn_format);
+    }
+
+    if (ref(my $tag_style = $cfg->{'tag-style'})) {
+       $cfg->{'tag-style'} = PVE::JSONSchema::print_property_string($tag_style, $tag_style_format);
+    }
+
+    if (ref(my $user_tag_privs = $cfg->{'user-tag-access'})) {
+       if (my $user_tags = $user_tag_privs->{'user-allow-list'}) {
+           $user_tag_privs->{'user-allow-list'} = join(';', sort $user_tags->@*);
+       }
+       $cfg->{'user-tag-access'} =
+           PVE::JSONSchema::print_property_string($user_tag_privs, $user_tag_privs_format);
+    }
+
+    if (ref(my $admin_tags = $cfg->{'registered-tags'})) {
+       $cfg->{'registered-tags'} = join(';', sort $admin_tags->@*);
+    }
+
+    my $comment = '';
+    # add description as comment to top of file
+    my $description = $cfg->{description} || '';
+    foreach my $line (split(/\n/, $description)) {
+       $comment .= '#' .  PVE::Tools::encode_text($line) . "\n";
+    }
+    delete $cfg->{description}; # add only as comment, no additional key-value pair
+    my $dump = PVE::JSONSchema::dump_config($datacenter_schema, $filename, $cfg);
+
+    return $comment . "\n" . $dump;
+}
+
+PVE::Cluster::cfs_register_file(
+    'datacenter.cfg',
+    \&parse_datacenter_config,
+    \&write_datacenter_config,
+);
+
+1;
diff --git a/src/PVE/IPCC.pm b/src/PVE/IPCC.pm
new file mode 100644 (file)
index 0000000..529ebf5
--- /dev/null
@@ -0,0 +1,65 @@
+package PVE::IPCC;
+
+use 5.010001;
+use strict;
+use warnings;
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+
+# Items to export into callers namespace by default. Note: do not export
+# names by default without a very good reason. Use EXPORT_OK instead.
+# Do not simply export all your public functions/methods/constants.
+
+# This allows declaration      use PVE::IPCC ':all';
+# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
+# will save memory.
+our %EXPORT_TAGS = ( 'all' => [ qw(
+       
+) ] );
+
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
+
+our @EXPORT = qw(
+       
+);
+
+our $VERSION = '1.0';
+
+require XSLoader;
+XSLoader::load('PVE::IPCC', $VERSION);
+
+# Preloaded methods go here.
+
+1;
+__END__
+
+=head1 NAME
+
+PVE::IPCC - Perl extension to access the PVE IPC Server
+
+=head1 SYNOPSIS
+
+  use PVE::IPCC;
+  
+  my $res = PVE::IPCC::ipcc_send_rec(1, "hello");
+  my $res = PVE::IPCC::sendfd($socketfd, $fd, $opt_data);
+
+=head1 DESCRIPTION
+
+Send/receive RAW data packets from the PVE IPC Server.
+
+Pass file descriptor over unix domain sockets (used to pass
+file destriptors to qemu fdset). This is use in PVE::QMPClient.
+
+=head2 EXPORT
+
+None by default.
+
+=head1 AUTHOR
+
+Dietmar Maurer, E<lt>dietmar@proxmox.com<gt>
+
+=cut
diff --git a/src/PVE/IPCC.xs b/src/PVE/IPCC.xs
new file mode 100644 (file)
index 0000000..4661df0
--- /dev/null
@@ -0,0 +1,226 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+#include "ppport.h"
+
+/* sendfd: BSD style file descriptor passing over unix domain sockets
+ *  Richard Stevens: Unix Network Programming, Prentice Hall, 1990;
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+#ifndef SCM_RIGHTS
+#error "SCM_RIGHTS undefined"
+#endif 
+
+/* interface to pmxcfs (libqb) */
+#include <sys/syslog.h>
+#include <qb/qbdefs.h>
+#include <qb/qbutil.h>
+#include <qb/qblog.h>
+#include <qb/qbipcc.h>
+
+#define RESTART_FLAG_FILE "/run/pve-cluster/cfs-restart-flag"
+#define RESTART_GRACE_PERIOD 10
+
+#define PCS_SOCKET_NAME "pve2"
+
+#define PCS_SERVICE1 1
+#define MAX_MSG_SIZE (8192*128)
+
+static qb_ipcc_connection_t *conn;
+static pid_t conn_pid;
+
+static char ipcbuffer[MAX_MSG_SIZE];
+
+static qb_ipcc_connection_t *init_connection() {
+
+       static qb_ipcc_connection_t *connection = NULL;
+       struct timespec retry_timeout, now;
+       int cfs_restart_flag_fd = -1;
+
+       // check if pmxcfs is currently restarting
+       if ((cfs_restart_flag_fd = open(RESTART_FLAG_FILE, 0)) > 0) {
+               clock_gettime(CLOCK_MONOTONIC, &retry_timeout);
+               retry_timeout.tv_sec += RESTART_GRACE_PERIOD;
+       }
+
+       qb_log_init("IPCC.xs", LOG_USER, LOG_EMERG);
+       qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE);
+
+retry_connection:
+       connection = qb_ipcc_connect(PCS_SOCKET_NAME, MAX_MSG_SIZE);
+
+       if (!connection) {
+               if (cfs_restart_flag_fd >= 0) {
+                       // cfs restarting and hopefully back soon, poll
+                       clock_gettime(CLOCK_MONOTONIC, &now);
+
+                       if (now.tv_sec < retry_timeout.tv_sec ||
+                          (now.tv_sec == retry_timeout.tv_sec &&
+                           now.tv_nsec < retry_timeout.tv_nsec)) {
+
+                               usleep(100 * 1000);
+                               goto retry_connection;
+
+                       } else {
+                               // timeout: cleanup flag file if still the same
+                               struct stat s;
+                               fstat(cfs_restart_flag_fd, &s);
+                               if (s.st_nlink > 0)
+                                       unlink(RESTART_FLAG_FILE);
+                       }
+               }
+       }
+
+       if (cfs_restart_flag_fd >= 0) close(cfs_restart_flag_fd);
+
+       return connection;
+}
+
+
+MODULE = PVE::IPCC             PACKAGE = PVE::IPCC             
+
+SV *
+ipcc_send_rec(msgid, data=NULL)
+I32 msgid;
+SV * data;
+PROTOTYPE: $;$
+CODE:
+{
+       uint8_t retried_cache_connection = 0;
+       pid_t cpid = getpid();
+
+       /* Each process needs its own ipcc connection,
+        * else the shared memory buffer gets corrupted.
+        */ 
+       if (conn && conn_pid != cpid) {
+               conn = NULL;
+       }
+
+       if (conn == NULL) {
+recache_connection:
+               conn = init_connection();
+
+               if (!conn)
+                       XSRETURN_UNDEF;
+
+               conn_pid = cpid;
+       }
+
+       size_t len = 0;
+       char *dataptr = NULL;
+       if (data && SvPOK(data))
+               dataptr = SvPV(data, len);
+
+       int iov_len = 2;
+       struct iovec iov[iov_len];
+
+       struct qb_ipc_request_header req_header;
+
+       req_header.id = msgid;
+       req_header.size = sizeof(req_header) + len;
+
+       iov[0].iov_base = (char *)&req_header;
+       iov[0].iov_len = sizeof(req_header);
+       iov[1].iov_base = dataptr;
+       iov[1].iov_len = len;
+
+       int32_t ms_timeout = -1; // fixme:     
+       int res = qb_ipcc_sendv_recv(conn, iov, iov_len, ipcbuffer, sizeof(ipcbuffer), ms_timeout);
+       if (res < 0) {
+               qb_ipcc_disconnect(conn);
+               conn = NULL;
+               // requests during cfs restart and the first thereafter will fail, retry
+               if (!retried_cache_connection) {
+                       retried_cache_connection = 1;
+                       goto recache_connection;
+               }
+               errno = -res;
+               XSRETURN_UNDEF;
+       }
+
+       struct qb_ipc_response_header *res_header;
+
+       res_header = (struct qb_ipc_response_header *)ipcbuffer;
+       int dsize = res_header->size - sizeof(struct qb_ipc_response_header);
+
+       if (res_header->error < 0) {
+               errno = -res_header->error;
+               XSRETURN_UNDEF;
+       } else {
+               errno = 0;
+               if (dsize > 0) {
+                       RETVAL = newSVpv(ipcbuffer + sizeof(struct qb_ipc_response_header), dsize);
+               } else {
+                       XSRETURN_UNDEF;
+               }
+       }
+}
+OUTPUT: RETVAL
+
+# helper to pass SCM ACCESS RIGHTS
+
+int
+sendfd(sock_fd, send_me_fd, data=NULL)
+int sock_fd
+int send_me_fd
+SV * data;
+CODE:
+{
+       int ret = 0;
+       struct iovec  iov[1];
+       struct msghdr msg;
+       memset(&msg, 0, sizeof(msg));
+
+       size_t len = 0;
+       char *dataptr = NULL;
+       if (data && SvPOK(data))
+               dataptr = SvPV(data, len);
+       
+       iov[0].iov_base = dataptr;
+       iov[0].iov_len = len;   
+       msg.msg_iov = iov;
+       msg.msg_iovlen = 1;
+       msg.msg_name = 0;
+       msg.msg_namelen = 0;
+
+       char control[CMSG_SPACE(sizeof(int))];
+       memset(control, 0, sizeof(control));
+
+       msg.msg_control = control;
+       msg.msg_controllen = sizeof(control);
+       msg.msg_flags = 0;
+               
+       struct cmsghdr* h = CMSG_FIRSTHDR(&msg);
+       h->cmsg_len = CMSG_LEN(sizeof(int));
+       h->cmsg_level= SOL_SOCKET;
+       h->cmsg_type = SCM_RIGHTS;
+       *((int*)CMSG_DATA(h)) = send_me_fd;
+
+       int repeat;
+       do {
+               repeat = 0;
+               ret = sendmsg(sock_fd, &msg, 0);
+               if (ret < 0) {
+                       if (errno == EINTR) {
+                               repeat = 1;
+                       } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                               repeat = 1;
+                               usleep(1000);
+                       }
+               }
+       } while (repeat);
+       
+       RETVAL = ret;
+}
+OUTPUT: RETVAL
diff --git a/src/PVE/Makefile b/src/PVE/Makefile
new file mode 100644 (file)
index 0000000..8ea5383
--- /dev/null
@@ -0,0 +1,66 @@
+DESTDIR=
+PERL5DIR=${DESTDIR}/usr/share/perl5
+PVEDIR=${PERL5DIR}/PVE
+MAN=${DESTDIR}/usr/share/man
+BASHCOMPLETION=${DESTDIR}/usr/share/bash-completion/completions
+ZSHCOMPLETION=${DESTDIR}/usr/share/zsh/vendor-completions
+
+PERL_VENDORARCH=$(shell perl -MConfig -e 'print $$Config{vendorarch};')
+PVE_VENDORARCH=${DESTDIR}/${PERL_VENDORARCH}/auto/PVE/IPCC
+
+PERL_DOC_INC_DIRS:=..
+
+SUBDIRS=Cluster CLI API2
+SOURCES=IPCC.pm Cluster.pm Corosync.pm RRD.pm DataCenterConfig.pm SSHInfo.pm
+
+all:
+
+.PHONY: install
+install: pvecm ${SOURCES} IPCC.so pvecm.1 pvecm.bash-completion pvecm.zsh-completion datacenter.cfg.5
+       install -D -m 0755 pvecm ${DESTDIR}/usr/bin/pvecm
+       install -d ${PVEDIR}
+       for f in ${SOURCES}; do install -m 0664 $$f ${PVEDIR}/$$f; done
+       install -D IPCC.so ${PVE_VENDORARCH}/IPCC.so
+       install -D pvecm.1 ${MAN}/man1/pvecm.1
+       install -D datacenter.cfg.5 ${MAN}/man5/datacenter.cfg.5
+       install -m 0644 -D pvecm.bash-completion ${BASHCOMPLETION}/pvecm
+       install -m 0644 -D pvecm.zsh-completion ${ZSHCOMPLETION}/_pvecm
+       for d in ${SUBDIRS}; do $(MAKE) -C $$d install; done
+
+%.bash-completion:
+       perl ${PERL_DOC_INC} -T -e "use PVE::CLI::$*; PVE::CLI::$*->generate_bash_completions();" >$@.tmp
+       mv $@.tmp $@
+
+%.zsh-completion:
+       perl ${PERL_DOC_INC} -T -e "use PVE::CLI::$*; PVE::CLI::$*->generate_zsh_completions();" >$@.tmp
+       mv $@.tmp $@
+
+Cluster/IPCConst.pm:
+       $(MAKE) -C Cluster IPCConst.pm
+
+check: IPCC.so Cluster/IPCConst.pm
+       perl ${PERL_DOC_INC} -T -e "use PVE::CLI::pvecm; PVE::CLI::pvecm->verify_api();"
+
+CC=gcc
+CFLAGS += -fPIC -Wl,-z,relro -Wall -Werror -Wno-strict-aliasing -g -O2 -shared
+CFLAGS += $(shell pkg-config --cflags libqb)
+CFLAGS += $(shell perl -MExtUtils::Embed -e perl_inc)
+LDFLAGS = $(shell pkg-config --libs libqb)
+
+.c.o:
+       $(CC) $(CFLAGS) -c -o $@ $<
+
+IPCC.c: IPCC.xs ppport.h
+       xsubpp -noversioncheck IPCC.xs > IPCC.xsc
+       mv IPCC.xsc IPCC.c
+
+IPCC.so: IPCC.o
+       $(CC) ${CFLAGS} -shared -o $@ $<  ${LDFLAGS}
+
+-include /usr/share/pve-doc-generator/pve-doc-generator.mk
+
+.PHONY: clean
+clean:
+       $(MAKE) cleanup-docgen
+       rm -f IPCC.so IPCC.o IPCC.c pvecm.bash-completion pvecm.zsh-completion
+       for d in ${SUBDIRS}; do $(MAKE) -C $$d clean; done
diff --git a/src/PVE/RRD.pm b/src/PVE/RRD.pm
new file mode 100644 (file)
index 0000000..5d4abc9
--- /dev/null
@@ -0,0 +1,134 @@
+package PVE::RRD;
+
+use strict; use warnings;
+
+use RRDs;
+
+use PVE::Tools;
+
+sub create_rrd_data {
+    my ($rrdname, $timeframe, $cf) = @_;
+
+    my $rrddir = "/var/lib/rrdcached/db";
+
+    my $rrd = "$rrddir/$rrdname";
+
+    my $setup = {
+       hour =>  [ 60, 70 ],
+       day  =>  [ 60*30, 70 ],
+       week =>  [ 60*180, 70 ],
+       month => [ 60*720, 70 ],
+       year =>  [ 60*10080, 70 ],
+    };
+
+    my ($reso, $count) = @{$setup->{$timeframe}};
+    my $ctime  = $reso*int(time()/$reso);
+    my $req_start = $ctime - $reso*$count;
+
+    $cf = "AVERAGE" if !$cf;
+
+    my @args = (
+       "-s" => $req_start,
+       "-e" => $ctime - 1,
+       "-r" => $reso,
+       );
+
+    my $socket = "/var/run/rrdcached.sock";
+    push @args, "--daemon" => "unix:$socket" if -S $socket;
+
+    my ($start, $step, $names, $data) = RRDs::fetch($rrd, $cf, @args);
+
+    my $err = RRDs::error;
+    die "RRD error: $err\n" if $err;
+
+    die "got wrong time resolution ($step != $reso)\n"
+       if $step != $reso;
+
+    my $res = [];
+    my $fields = scalar(@$names);
+    for my $line (@$data) {
+       my $entry = { 'time' => $start };
+       $start += $step;
+       for (my $i = 0; $i < $fields; $i++) {
+           my $name = $names->[$i];
+           if (defined(my $val = $line->[$i])) {
+               $entry->{$name} = $val;
+           } else {
+               # leave empty fields undefined
+               # maybe make this configurable?
+           }
+       }
+       push @$res, $entry;
+    }
+
+    return $res;
+}
+
+sub create_rrd_graph {
+    my ($rrdname, $timeframe, $ds, $cf) = @_;
+
+    # Using RRD graph is clumsy - maybe it
+    # is better to simply fetch the data, and do all display
+    # related things with javascript (new extjs html5 graph library).
+
+    my $rrddir = "/var/lib/rrdcached/db";
+
+    my $rrd = "$rrddir/$rrdname";
+
+    my @ids = PVE::Tools::split_list($ds);
+
+    my $ds_txt = join('_', @ids);
+
+    my $filename = "${rrd}_${ds_txt}.png";
+
+    my $setup = {
+       hour =>  [ 60, 60 ],
+       day  =>  [ 60*30, 70 ],
+       week =>  [ 60*180, 70 ],
+       month => [ 60*720, 70 ],
+       year =>  [ 60*10080, 70 ],
+    };
+
+    my ($reso, $count) = @{$setup->{$timeframe}};
+
+    my @args = (
+       "--imgformat" => "PNG",
+       "--border" => 0,
+       "--height" => 200,
+       "--width" => 800,
+       "--start" => - $reso*$count,
+       "--end" => 'now' ,
+       "--lower-limit" => 0,
+       );
+
+    my $socket = "/var/run/rrdcached.sock";
+    push @args, "--daemon" => "unix:$socket" if -S $socket;
+
+    my @coldef = ('#00ddff', '#ff0000');
+
+    $cf = "AVERAGE" if !$cf;
+
+    my $i = 0;
+    foreach my $id (@ids) {
+       my $col = $coldef[$i++] || die "fixme: no color definition";
+       push @args, "DEF:${id}=$rrd:${id}:$cf";
+       my $dataid = $id;
+       if ($id eq 'cpu' || $id eq 'iowait') {
+           push @args, "CDEF:${id}_per=${id},100,*";
+           $dataid = "${id}_per";
+       }
+       push @args, "LINE2:${dataid}${col}:${id}";
+    }
+
+    push @args, '--full-size-mode';
+
+    # we do not really store data into the file
+    my $res = RRDs::graphv('-', @args);
+
+    my $err = RRDs::error;
+    die "RRD error: $err\n" if $err;
+
+    return { filename => $filename, image => $res->{image} };
+}
+
+1;
diff --git a/src/PVE/SSHInfo.pm b/src/PVE/SSHInfo.pm
new file mode 100644 (file)
index 0000000..c351148
--- /dev/null
@@ -0,0 +1,68 @@
+package PVE::SSHInfo;
+
+use strict;
+use warnings;
+
+use PVE::Cluster;
+use PVE::Tools;
+
+sub get_ssh_info {
+    my ($node, $network_cidr) = @_;
+
+    my $ip;
+    if (defined($network_cidr)) {
+       # Use mtunnel via to get the remote node's ip inside $network_cidr.
+       # This goes over the regular network (iow. uses get_ssh_info() with
+       # $network_cidr undefined.
+       # FIXME: Use the REST API client for this after creating an API entry
+       # for get_migration_ip.
+       my $default_remote = get_ssh_info($node, undef);
+       my $default_ssh = ssh_info_to_command($default_remote);
+       my $cmd =[@$default_ssh, 'pvecm', 'mtunnel',
+           '-migration_network', $network_cidr,
+           '-get_migration_ip'
+       ];
+       PVE::Tools::run_command($cmd, outfunc => sub {
+           my ($line) = @_;
+           chomp $line;
+           die "internal error: unexpected output from mtunnel\n"
+               if defined($ip);
+           if ($line =~ /^ip: '(.*)'$/) {
+               $ip = $1;
+           } else {
+               die "internal error: bad output from mtunnel\n"
+                   if defined($ip);
+           }
+       });
+       die "failed to get ip for node '$node' in network '$network_cidr'\n"
+           if !defined($ip);
+    } else {
+       $ip = PVE::Cluster::remote_node_ip($node);
+    }
+
+    return {
+       ip => $ip,
+       name => $node,
+       network => $network_cidr,
+    };
+}
+
+sub ssh_info_to_command_base {
+    my ($info, @extra_options) = @_;
+    return [
+       '/usr/bin/ssh',
+       '-e', 'none',
+       '-o', 'BatchMode=yes',
+       '-o', 'HostKeyAlias='.$info->{name},
+       @extra_options
+    ];
+}
+
+sub ssh_info_to_command {
+    my ($info, @extra_options) = @_;
+    my $cmd = ssh_info_to_command_base($info, @extra_options);
+    push @$cmd, "root\@$info->{ip}";
+    return $cmd;
+}
+
+1;
diff --git a/src/PVE/ppport.h b/src/PVE/ppport.h
new file mode 100644 (file)
index 0000000..8ec0d5f
--- /dev/null
@@ -0,0 +1,7063 @@
+#if 0
+<<'SKIP';
+#endif
+/*
+----------------------------------------------------------------------
+
+    ppport.h -- Perl/Pollution/Portability Version 3.19
+
+    Automatically created by Devel::PPPort running under perl 5.010001.
+
+    Do NOT edit this file directly! -- Edit PPPort_pm.PL and the
+    includes in parts/inc/ instead.
+
+    Use 'perldoc ppport.h' to view the documentation below.
+
+----------------------------------------------------------------------
+
+SKIP
+
+=pod
+
+=head1 NAME
+
+ppport.h - Perl/Pollution/Portability version 3.19
+
+=head1 SYNOPSIS
+
+  perl ppport.h [options] [source files]
+
+  Searches current directory for files if no [source files] are given
+
+  --help                      show short help
+
+  --version                   show version
+
+  --patch=file                write one patch file with changes
+  --copy=suffix               write changed copies with suffix
+  --diff=program              use diff program and options
+
+  --compat-version=version    provide compatibility with Perl version
+  --cplusplus                 accept C++ comments
+
+  --quiet                     don't output anything except fatal errors
+  --nodiag                    don't show diagnostics
+  --nohints                   don't show hints
+  --nochanges                 don't suggest changes
+  --nofilter                  don't filter input files
+
+  --strip                     strip all script and doc functionality from
+                              ppport.h
+
+  --list-provided             list provided API
+  --list-unsupported          list unsupported API
+  --api-info=name             show Perl API portability information
+
+=head1 COMPATIBILITY
+
+This version of F<ppport.h> is designed to support operation with Perl
+installations back to 5.003, and has been tested up to 5.10.0.
+
+=head1 OPTIONS
+
+=head2 --help
+
+Display a brief usage summary.
+
+=head2 --version
+
+Display the version of F<ppport.h>.
+
+=head2 --patch=I<file>
+
+If this option is given, a single patch file will be created if
+any changes are suggested. This requires a working diff program
+to be installed on your system.
+
+=head2 --copy=I<suffix>
+
+If this option is given, a copy of each file will be saved with
+the given suffix that contains the suggested changes. This does
+not require any external programs. Note that this does not
+automagially add a dot between the original filename and the
+suffix. If you want the dot, you have to include it in the option
+argument.
+
+If neither C<--patch> or C<--copy> are given, the default is to
+simply print the diffs for each file. This requires either
+C<Text::Diff> or a C<diff> program to be installed.
+
+=head2 --diff=I<program>
+
+Manually set the diff program and options to use. The default
+is to use C<Text::Diff>, when installed, and output unified
+context diffs.
+
+=head2 --compat-version=I<version>
+
+Tell F<ppport.h> to check for compatibility with the given
+Perl version. The default is to check for compatibility with Perl
+version 5.003. You can use this option to reduce the output
+of F<ppport.h> if you intend to be backward compatible only
+down to a certain Perl version.
+
+=head2 --cplusplus
+
+Usually, F<ppport.h> will detect C++ style comments and
+replace them with C style comments for portability reasons.
+Using this option instructs F<ppport.h> to leave C++
+comments untouched.
+
+=head2 --quiet
+
+Be quiet. Don't print anything except fatal errors.
+
+=head2 --nodiag
+
+Don't output any diagnostic messages. Only portability
+alerts will be printed.
+
+=head2 --nohints
+
+Don't output any hints. Hints often contain useful portability
+notes. Warnings will still be displayed.
+
+=head2 --nochanges
+
+Don't suggest any changes. Only give diagnostic output and hints
+unless these are also deactivated.
+
+=head2 --nofilter
+
+Don't filter the list of input files. By default, files not looking
+like source code (i.e. not *.xs, *.c, *.cc, *.cpp or *.h) are skipped.
+
+=head2 --strip
+
+Strip all script and documentation functionality from F<ppport.h>.
+This reduces the size of F<ppport.h> dramatically and may be useful
+if you want to include F<ppport.h> in smaller modules without
+increasing their distribution size too much.
+
+The stripped F<ppport.h> will have a C<--unstrip> option that allows
+you to undo the stripping, but only if an appropriate C<Devel::PPPort>
+module is installed.
+
+=head2 --list-provided
+
+Lists the API elements for which compatibility is provided by
+F<ppport.h>. Also lists if it must be explicitly requested,
+if it has dependencies, and if there are hints or warnings for it.
+
+=head2 --list-unsupported
+
+Lists the API elements that are known not to be supported by
+F<ppport.h> and below which version of Perl they probably
+won't be available or work.
+
+=head2 --api-info=I<name>
+
+Show portability information for API elements matching I<name>.
+If I<name> is surrounded by slashes, it is interpreted as a regular
+expression.
+
+=head1 DESCRIPTION
+
+In order for a Perl extension (XS) module to be as portable as possible
+across differing versions of Perl itself, certain steps need to be taken.
+
+=over 4
+
+=item *
+
+Including this header is the first major one. This alone will give you
+access to a large part of the Perl API that hasn't been available in
+earlier Perl releases. Use
+
+    perl ppport.h --list-provided
+
+to see which API elements are provided by ppport.h.
+
+=item *
+
+You should avoid using deprecated parts of the API. For example, using
+global Perl variables without the C<PL_> prefix is deprecated. Also,
+some API functions used to have a C<perl_> prefix. Using this form is
+also deprecated. You can safely use the supported API, as F<ppport.h>
+will provide wrappers for older Perl versions.
+
+=item *
+
+If you use one of a few functions or variables that were not present in
+earlier versions of Perl, and that can't be provided using a macro, you
+have to explicitly request support for these functions by adding one or
+more C<#define>s in your source code before the inclusion of F<ppport.h>.
+
+These functions or variables will be marked C<explicit> in the list shown
+by C<--list-provided>.
+
+Depending on whether you module has a single or multiple files that
+use such functions or variables, you want either C<static> or global
+variants.
+
+For a C<static> function or variable (used only in a single source
+file), use:
+
+    #define NEED_function
+    #define NEED_variable
+
+For a global function or variable (used in multiple source files),
+use:
+
+    #define NEED_function_GLOBAL
+    #define NEED_variable_GLOBAL
+
+Note that you mustn't have more than one global request for the
+same function or variable in your project.
+
+    Function / Variable       Static Request               Global Request
+    -----------------------------------------------------------------------------------------
+    PL_parser                 NEED_PL_parser               NEED_PL_parser_GLOBAL
+    PL_signals                NEED_PL_signals              NEED_PL_signals_GLOBAL
+    eval_pv()                 NEED_eval_pv                 NEED_eval_pv_GLOBAL
+    grok_bin()                NEED_grok_bin                NEED_grok_bin_GLOBAL
+    grok_hex()                NEED_grok_hex                NEED_grok_hex_GLOBAL
+    grok_number()             NEED_grok_number             NEED_grok_number_GLOBAL
+    grok_numeric_radix()      NEED_grok_numeric_radix      NEED_grok_numeric_radix_GLOBAL
+    grok_oct()                NEED_grok_oct                NEED_grok_oct_GLOBAL
+    load_module()             NEED_load_module             NEED_load_module_GLOBAL
+    my_snprintf()             NEED_my_snprintf             NEED_my_snprintf_GLOBAL
+    my_sprintf()              NEED_my_sprintf              NEED_my_sprintf_GLOBAL
+    my_strlcat()              NEED_my_strlcat              NEED_my_strlcat_GLOBAL
+    my_strlcpy()              NEED_my_strlcpy              NEED_my_strlcpy_GLOBAL
+    newCONSTSUB()             NEED_newCONSTSUB             NEED_newCONSTSUB_GLOBAL
+    newRV_noinc()             NEED_newRV_noinc             NEED_newRV_noinc_GLOBAL
+    newSV_type()              NEED_newSV_type              NEED_newSV_type_GLOBAL
+    newSVpvn_flags()          NEED_newSVpvn_flags          NEED_newSVpvn_flags_GLOBAL
+    newSVpvn_share()          NEED_newSVpvn_share          NEED_newSVpvn_share_GLOBAL
+    pv_display()              NEED_pv_display              NEED_pv_display_GLOBAL
+    pv_escape()               NEED_pv_escape               NEED_pv_escape_GLOBAL
+    pv_pretty()               NEED_pv_pretty               NEED_pv_pretty_GLOBAL
+    sv_2pv_flags()            NEED_sv_2pv_flags            NEED_sv_2pv_flags_GLOBAL
+    sv_2pvbyte()              NEED_sv_2pvbyte              NEED_sv_2pvbyte_GLOBAL
+    sv_catpvf_mg()            NEED_sv_catpvf_mg            NEED_sv_catpvf_mg_GLOBAL
+    sv_catpvf_mg_nocontext()  NEED_sv_catpvf_mg_nocontext  NEED_sv_catpvf_mg_nocontext_GLOBAL
+    sv_pvn_force_flags()      NEED_sv_pvn_force_flags      NEED_sv_pvn_force_flags_GLOBAL
+    sv_setpvf_mg()            NEED_sv_setpvf_mg            NEED_sv_setpvf_mg_GLOBAL
+    sv_setpvf_mg_nocontext()  NEED_sv_setpvf_mg_nocontext  NEED_sv_setpvf_mg_nocontext_GLOBAL
+    vload_module()            NEED_vload_module            NEED_vload_module_GLOBAL
+    vnewSVpvf()               NEED_vnewSVpvf               NEED_vnewSVpvf_GLOBAL
+    warner()                  NEED_warner                  NEED_warner_GLOBAL
+
+To avoid namespace conflicts, you can change the namespace of the
+explicitly exported functions / variables using the C<DPPP_NAMESPACE>
+macro. Just C<#define> the macro before including C<ppport.h>:
+
+    #define DPPP_NAMESPACE MyOwnNamespace_
+    #include "ppport.h"
+
+The default namespace is C<DPPP_>.
+
+=back
+
+The good thing is that most of the above can be checked by running
+F<ppport.h> on your source code. See the next section for
+details.
+
+=head1 EXAMPLES
+
+To verify whether F<ppport.h> is needed for your module, whether you
+should make any changes to your code, and whether any special defines
+should be used, F<ppport.h> can be run as a Perl script to check your
+source code. Simply say:
+
+    perl ppport.h
+
+The result will usually be a list of patches suggesting changes
+that should at least be acceptable, if not necessarily the most
+efficient solution, or a fix for all possible problems.
+
+If you know that your XS module uses features only available in
+newer Perl releases, if you're aware that it uses C++ comments,
+and if you want all suggestions as a single patch file, you could
+use something like this:
+
+    perl ppport.h --compat-version=5.6.0 --cplusplus --patch=test.diff
+
+If you only want your code to be scanned without any suggestions
+for changes, use:
+
+    perl ppport.h --nochanges
+
+You can specify a different C<diff> program or options, using
+the C<--diff> option:
+
+    perl ppport.h --diff='diff -C 10'
+
+This would output context diffs with 10 lines of context.
+
+If you want to create patched copies of your files instead, use:
+
+    perl ppport.h --copy=.new
+
+To display portability information for the C<newSVpvn> function,
+use:
+
+    perl ppport.h --api-info=newSVpvn
+
+Since the argument to C<--api-info> can be a regular expression,
+you can use
+
+    perl ppport.h --api-info=/_nomg$/
+
+to display portability information for all C<_nomg> functions or
+
+    perl ppport.h --api-info=/./
+
+to display information for all known API elements.
+
+=head1 BUGS
+
+If this version of F<ppport.h> is causing failure during
+the compilation of this module, please check if newer versions
+of either this module or C<Devel::PPPort> are available on CPAN
+before sending a bug report.
+
+If F<ppport.h> was generated using the latest version of
+C<Devel::PPPort> and is causing failure of this module, please
+file a bug report using the CPAN Request Tracker at L<http://rt.cpan.org/>.
+
+Please include the following information:
+
+=over 4
+
+=item 1.
+
+The complete output from running "perl -V"
+
+=item 2.
+
+This file.
+
+=item 3.
+
+The name and version of the module you were trying to build.
+
+=item 4.
+
+A full log of the build that failed.
+
+=item 5.
+
+Any other information that you think could be relevant.
+
+=back
+
+For the latest version of this code, please get the C<Devel::PPPort>
+module from CPAN.
+
+=head1 COPYRIGHT
+
+Version 3.x, Copyright (c) 2004-2009, Marcus Holland-Moritz.
+
+Version 2.x, Copyright (C) 2001, Paul Marquess.
+
+Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+See L<Devel::PPPort>.
+
+=cut
+
+use strict;
+
+# Disable broken TRIE-optimization
+BEGIN { eval '${^RE_TRIE_MAXBUF} = -1' if $] >= 5.009004 && $] <= 5.009005 }
+
+my $VERSION = 3.19;
+
+my %opt = (
+  quiet     => 0,
+  diag      => 1,
+  hints     => 1,
+  changes   => 1,
+  cplusplus => 0,
+  filter    => 1,
+  strip     => 0,
+  version   => 0,
+);
+
+my($ppport) = $0 =~ /([\w.]+)$/;
+my $LF = '(?:\r\n|[\r\n])';   # line feed
+my $HS = "[ \t]";             # horizontal whitespace
+
+# Never use C comments in this file!
+my $ccs  = '/'.'*';
+my $cce  = '*'.'/';
+my $rccs = quotemeta $ccs;
+my $rcce = quotemeta $cce;
+
+eval {
+  require Getopt::Long;
+  Getopt::Long::GetOptions(\%opt, qw(
+    help quiet diag! filter! hints! changes! cplusplus strip version
+    patch=s copy=s diff=s compat-version=s
+    list-provided list-unsupported api-info=s
+  )) or usage();
+};
+
+if ($@ and grep /^-/, @ARGV) {
+  usage() if "@ARGV" =~ /^--?h(?:elp)?$/;
+  die "Getopt::Long not found. Please don't use any options.\n";
+}
+
+if ($opt{version}) {
+  print "This is $0 $VERSION.\n";
+  exit 0;
+}
+
+usage() if $opt{help};
+strip() if $opt{strip};
+
+if (exists $opt{'compat-version'}) {
+  my($r,$v,$s) = eval { parse_version($opt{'compat-version'}) };
+  if ($@) {
+    die "Invalid version number format: '$opt{'compat-version'}'\n";
+  }
+  die "Only Perl 5 is supported\n" if $r != 5;
+  die "Invalid version number: $opt{'compat-version'}\n" if $v >= 1000 || $s >= 1000;
+  $opt{'compat-version'} = sprintf "%d.%03d%03d", $r, $v, $s;
+}
+else {
+  $opt{'compat-version'} = 5;
+}
+
+my %API = map { /^(\w+)\|([^|]*)\|([^|]*)\|(\w*)$/
+                ? ( $1 => {
+                      ($2                  ? ( base     => $2 ) : ()),
+                      ($3                  ? ( todo     => $3 ) : ()),
+                      (index($4, 'v') >= 0 ? ( varargs  => 1  ) : ()),
+                      (index($4, 'p') >= 0 ? ( provided => 1  ) : ()),
+                      (index($4, 'n') >= 0 ? ( nothxarg => 1  ) : ()),
+                    } )
+                : die "invalid spec: $_" } qw(
+AvFILLp|5.004050||p
+AvFILL|||
+CLASS|||n
+CPERLscope|5.005000||p
+CX_CURPAD_SAVE|||
+CX_CURPAD_SV|||
+CopFILEAV|5.006000||p
+CopFILEGV_set|5.006000||p
+CopFILEGV|5.006000||p
+CopFILESV|5.006000||p
+CopFILE_set|5.006000||p
+CopFILE|5.006000||p
+CopSTASHPV_set|5.006000||p
+CopSTASHPV|5.006000||p
+CopSTASH_eq|5.006000||p
+CopSTASH_set|5.006000||p
+CopSTASH|5.006000||p
+CopyD|5.009002||p
+Copy|||
+CvPADLIST|||
+CvSTASH|||
+CvWEAKOUTSIDE|||
+DEFSV_set|5.011000||p
+DEFSV|5.004050||p
+END_EXTERN_C|5.005000||p
+ENTER|||
+ERRSV|5.004050||p
+EXTEND|||
+EXTERN_C|5.005000||p
+F0convert|||n
+FREETMPS|||
+GIMME_V||5.004000|n
+GIMME|||n
+GROK_NUMERIC_RADIX|5.007002||p
+G_ARRAY|||
+G_DISCARD|||
+G_EVAL|||
+G_METHOD|5.006001||p
+G_NOARGS|||
+G_SCALAR|||
+G_VOID||5.004000|
+GetVars|||
+GvSVn|5.009003||p
+GvSV|||
+Gv_AMupdate|||
+HEf_SVKEY||5.004000|
+HeHASH||5.004000|
+HeKEY||5.004000|
+HeKLEN||5.004000|
+HePV||5.004000|
+HeSVKEY_force||5.004000|
+HeSVKEY_set||5.004000|
+HeSVKEY||5.004000|
+HeUTF8||5.011000|
+HeVAL||5.004000|
+HvNAMELEN_get|5.009003||p
+HvNAME_get|5.009003||p
+HvNAME|||
+INT2PTR|5.006000||p
+IN_LOCALE_COMPILETIME|5.007002||p
+IN_LOCALE_RUNTIME|5.007002||p
+IN_LOCALE|5.007002||p
+IN_PERL_COMPILETIME|5.008001||p
+IS_NUMBER_GREATER_THAN_UV_MAX|5.007002||p
+IS_NUMBER_INFINITY|5.007002||p
+IS_NUMBER_IN_UV|5.007002||p
+IS_NUMBER_NAN|5.007003||p
+IS_NUMBER_NEG|5.007002||p
+IS_NUMBER_NOT_INT|5.007002||p
+IVSIZE|5.006000||p
+IVTYPE|5.006000||p
+IVdf|5.006000||p
+LEAVE|||
+LVRET|||
+MARK|||
+MULTICALL||5.011000|
+MY_CXT_CLONE|5.009002||p
+MY_CXT_INIT|5.007003||p
+MY_CXT|5.007003||p
+MoveD|5.009002||p
+Move|||
+NOOP|5.005000||p
+NUM2PTR|5.006000||p
+NVTYPE|5.006000||p
+NVef|5.006001||p
+NVff|5.006001||p
+NVgf|5.006001||p
+Newxc|5.009003||p
+Newxz|5.009003||p
+Newx|5.009003||p
+Nullav|||
+Nullch|||
+Nullcv|||
+Nullhv|||
+Nullsv|||
+ORIGMARK|||
+PAD_BASE_SV|||
+PAD_CLONE_VARS|||
+PAD_COMPNAME_FLAGS|||
+PAD_COMPNAME_GEN_set|||
+PAD_COMPNAME_GEN|||
+PAD_COMPNAME_OURSTASH|||
+PAD_COMPNAME_PV|||
+PAD_COMPNAME_TYPE|||
+PAD_DUP|||
+PAD_RESTORE_LOCAL|||
+PAD_SAVE_LOCAL|||
+PAD_SAVE_SETNULLPAD|||
+PAD_SETSV|||
+PAD_SET_CUR_NOSAVE|||
+PAD_SET_CUR|||
+PAD_SVl|||
+PAD_SV|||
+PERLIO_FUNCS_CAST|5.009003||p
+PERLIO_FUNCS_DECL|5.009003||p
+PERL_ABS|5.008001||p
+PERL_BCDVERSION|5.011000||p
+PERL_GCC_BRACE_GROUPS_FORBIDDEN|5.008001||p
+PERL_HASH|5.004000||p
+PERL_INT_MAX|5.004000||p
+PERL_INT_MIN|5.004000||p
+PERL_LONG_MAX|5.004000||p
+PERL_LONG_MIN|5.004000||p
+PERL_MAGIC_arylen|5.007002||p
+PERL_MAGIC_backref|5.007002||p
+PERL_MAGIC_bm|5.007002||p
+PERL_MAGIC_collxfrm|5.007002||p
+PERL_MAGIC_dbfile|5.007002||p
+PERL_MAGIC_dbline|5.007002||p
+PERL_MAGIC_defelem|5.007002||p
+PERL_MAGIC_envelem|5.007002||p
+PERL_MAGIC_env|5.007002||p
+PERL_MAGIC_ext|5.007002||p
+PERL_MAGIC_fm|5.007002||p
+PERL_MAGIC_glob|5.011000||p
+PERL_MAGIC_isaelem|5.007002||p
+PERL_MAGIC_isa|5.007002||p
+PERL_MAGIC_mutex|5.011000||p
+PERL_MAGIC_nkeys|5.007002||p
+PERL_MAGIC_overload_elem|5.007002||p
+PERL_MAGIC_overload_table|5.007002||p
+PERL_MAGIC_overload|5.007002||p
+PERL_MAGIC_pos|5.007002||p
+PERL_MAGIC_qr|5.007002||p
+PERL_MAGIC_regdata|5.007002||p
+PERL_MAGIC_regdatum|5.007002||p
+PERL_MAGIC_regex_global|5.007002||p
+PERL_MAGIC_shared_scalar|5.007003||p
+PERL_MAGIC_shared|5.007003||p
+PERL_MAGIC_sigelem|5.007002||p
+PERL_MAGIC_sig|5.007002||p
+PERL_MAGIC_substr|5.007002||p
+PERL_MAGIC_sv|5.007002||p
+PERL_MAGIC_taint|5.007002||p
+PERL_MAGIC_tiedelem|5.007002||p
+PERL_MAGIC_tiedscalar|5.007002||p
+PERL_MAGIC_tied|5.007002||p
+PERL_MAGIC_utf8|5.008001||p
+PERL_MAGIC_uvar_elem|5.007003||p
+PERL_MAGIC_uvar|5.007002||p
+PERL_MAGIC_vec|5.007002||p
+PERL_MAGIC_vstring|5.008001||p
+PERL_PV_ESCAPE_ALL|5.009004||p
+PERL_PV_ESCAPE_FIRSTCHAR|5.009004||p
+PERL_PV_ESCAPE_NOBACKSLASH|5.009004||p
+PERL_PV_ESCAPE_NOCLEAR|5.009004||p
+PERL_PV_ESCAPE_QUOTE|5.009004||p
+PERL_PV_ESCAPE_RE|5.009005||p
+PERL_PV_ESCAPE_UNI_DETECT|5.009004||p
+PERL_PV_ESCAPE_UNI|5.009004||p
+PERL_PV_PRETTY_DUMP|5.009004||p
+PERL_PV_PRETTY_ELLIPSES|5.010000||p
+PERL_PV_PRETTY_LTGT|5.009004||p
+PERL_PV_PRETTY_NOCLEAR|5.010000||p
+PERL_PV_PRETTY_QUOTE|5.009004||p
+PERL_PV_PRETTY_REGPROP|5.009004||p
+PERL_QUAD_MAX|5.004000||p
+PERL_QUAD_MIN|5.004000||p
+PERL_REVISION|5.006000||p
+PERL_SCAN_ALLOW_UNDERSCORES|5.007003||p
+PERL_SCAN_DISALLOW_PREFIX|5.007003||p
+PERL_SCAN_GREATER_THAN_UV_MAX|5.007003||p
+PERL_SCAN_SILENT_ILLDIGIT|5.008001||p
+PERL_SHORT_MAX|5.004000||p
+PERL_SHORT_MIN|5.004000||p
+PERL_SIGNALS_UNSAFE_FLAG|5.008001||p
+PERL_SUBVERSION|5.006000||p
+PERL_SYS_INIT3||5.006000|
+PERL_SYS_INIT|||
+PERL_SYS_TERM||5.011000|
+PERL_UCHAR_MAX|5.004000||p
+PERL_UCHAR_MIN|5.004000||p
+PERL_UINT_MAX|5.004000||p
+PERL_UINT_MIN|5.004000||p
+PERL_ULONG_MAX|5.004000||p
+PERL_ULONG_MIN|5.004000||p
+PERL_UNUSED_ARG|5.009003||p
+PERL_UNUSED_CONTEXT|5.009004||p
+PERL_UNUSED_DECL|5.007002||p
+PERL_UNUSED_VAR|5.007002||p
+PERL_UQUAD_MAX|5.004000||p
+PERL_UQUAD_MIN|5.004000||p
+PERL_USE_GCC_BRACE_GROUPS|5.009004||p
+PERL_USHORT_MAX|5.004000||p
+PERL_USHORT_MIN|5.004000||p
+PERL_VERSION|5.006000||p
+PL_DBsignal|5.005000||p
+PL_DBsingle|||pn
+PL_DBsub|||pn
+PL_DBtrace|||pn
+PL_Sv|5.005000||p
+PL_bufend|5.011000||p
+PL_bufptr|5.011000||p
+PL_compiling|5.004050||p
+PL_copline|5.011000||p
+PL_curcop|5.004050||p
+PL_curstash|5.004050||p
+PL_debstash|5.004050||p
+PL_defgv|5.004050||p
+PL_diehook|5.004050||p
+PL_dirty|5.004050||p
+PL_dowarn|||pn
+PL_errgv|5.004050||p
+PL_error_count|5.011000||p
+PL_expect|5.011000||p
+PL_hexdigit|5.005000||p
+PL_hints|5.005000||p
+PL_in_my_stash|5.011000||p
+PL_in_my|5.011000||p
+PL_last_in_gv|||n
+PL_laststatval|5.005000||p
+PL_lex_state|5.011000||p
+PL_lex_stuff|5.011000||p
+PL_linestr|5.011000||p
+PL_modglobal||5.005000|n
+PL_na|5.004050||pn
+PL_no_modify|5.006000||p
+PL_ofsgv|||n
+PL_parser|5.009005||p
+PL_perl_destruct_level|5.004050||p
+PL_perldb|5.004050||p
+PL_ppaddr|5.006000||p
+PL_rsfp_filters|5.004050||p
+PL_rsfp|5.004050||p
+PL_rs|||n
+PL_signals|5.008001||p
+PL_stack_base|5.004050||p
+PL_stack_sp|5.004050||p
+PL_statcache|5.005000||p
+PL_stdingv|5.004050||p
+PL_sv_arenaroot|5.004050||p
+PL_sv_no|5.004050||pn
+PL_sv_undef|5.004050||pn
+PL_sv_yes|5.004050||pn
+PL_tainted|5.004050||p
+PL_tainting|5.004050||p
+PL_tokenbuf|5.011000||p
+POP_MULTICALL||5.011000|
+POPi|||n
+POPl|||n
+POPn|||n
+POPpbytex||5.007001|n
+POPpx||5.005030|n
+POPp|||n
+POPs|||n
+PTR2IV|5.006000||p
+PTR2NV|5.006000||p
+PTR2UV|5.006000||p
+PTR2nat|5.009003||p
+PTR2ul|5.007001||p
+PTRV|5.006000||p
+PUSHMARK|||
+PUSH_MULTICALL||5.011000|
+PUSHi|||
+PUSHmortal|5.009002||p
+PUSHn|||
+PUSHp|||
+PUSHs|||
+PUSHu|5.004000||p
+PUTBACK|||
+PerlIO_clearerr||5.007003|
+PerlIO_close||5.007003|
+PerlIO_context_layers||5.009004|
+PerlIO_eof||5.007003|
+PerlIO_error||5.007003|
+PerlIO_fileno||5.007003|
+PerlIO_fill||5.007003|
+PerlIO_flush||5.007003|
+PerlIO_get_base||5.007003|
+PerlIO_get_bufsiz||5.007003|
+PerlIO_get_cnt||5.007003|
+PerlIO_get_ptr||5.007003|
+PerlIO_read||5.007003|
+PerlIO_seek||5.007003|
+PerlIO_set_cnt||5.007003|
+PerlIO_set_ptrcnt||5.007003|
+PerlIO_setlinebuf||5.007003|
+PerlIO_stderr||5.007003|
+PerlIO_stdin||5.007003|
+PerlIO_stdout||5.007003|
+PerlIO_tell||5.007003|
+PerlIO_unread||5.007003|
+PerlIO_write||5.007003|
+Perl_signbit||5.009005|n
+PoisonFree|5.009004||p
+PoisonNew|5.009004||p
+PoisonWith|5.009004||p
+Poison|5.008000||p
+RETVAL|||n
+Renewc|||
+Renew|||
+SAVECLEARSV|||
+SAVECOMPPAD|||
+SAVEPADSV|||
+SAVETMPS|||
+SAVE_DEFSV|5.004050||p
+SPAGAIN|||
+SP|||
+START_EXTERN_C|5.005000||p
+START_MY_CXT|5.007003||p
+STMT_END|||p
+STMT_START|||p
+STR_WITH_LEN|5.009003||p
+ST|||
+SV_CONST_RETURN|5.009003||p
+SV_COW_DROP_PV|5.008001||p
+SV_COW_SHARED_HASH_KEYS|5.009005||p
+SV_GMAGIC|5.007002||p
+SV_HAS_TRAILING_NUL|5.009004||p
+SV_IMMEDIATE_UNREF|5.007001||p
+SV_MUTABLE_RETURN|5.009003||p
+SV_NOSTEAL|5.009002||p
+SV_SMAGIC|5.009003||p
+SV_UTF8_NO_ENCODING|5.008001||p
+SVfARG|5.009005||p
+SVf_UTF8|5.006000||p
+SVf|5.006000||p
+SVt_IV|||
+SVt_NV|||
+SVt_PVAV|||
+SVt_PVCV|||
+SVt_PVHV|||
+SVt_PVMG|||
+SVt_PV|||
+Safefree|||
+Slab_Alloc|||
+Slab_Free|||
+Slab_to_rw|||
+StructCopy|||
+SvCUR_set|||
+SvCUR|||
+SvEND|||
+SvGAMAGIC||5.006001|
+SvGETMAGIC|5.004050||p
+SvGROW|||
+SvIOK_UV||5.006000|
+SvIOK_notUV||5.006000|
+SvIOK_off|||
+SvIOK_only_UV||5.006000|
+SvIOK_only|||
+SvIOK_on|||
+SvIOKp|||
+SvIOK|||
+SvIVX|||
+SvIV_nomg|5.009001||p
+SvIV_set|||
+SvIVx|||
+SvIV|||
+SvIsCOW_shared_hash||5.008003|
+SvIsCOW||5.008003|
+SvLEN_set|||
+SvLEN|||
+SvLOCK||5.007003|
+SvMAGIC_set|5.009003||p
+SvNIOK_off|||
+SvNIOKp|||
+SvNIOK|||
+SvNOK_off|||
+SvNOK_only|||
+SvNOK_on|||
+SvNOKp|||
+SvNOK|||
+SvNVX|||
+SvNV_set|||
+SvNVx|||
+SvNV|||
+SvOK|||
+SvOOK_offset||5.011000|
+SvOOK|||
+SvPOK_off|||
+SvPOK_only_UTF8||5.006000|
+SvPOK_only|||
+SvPOK_on|||
+SvPOKp|||
+SvPOK|||
+SvPVX_const|5.009003||p
+SvPVX_mutable|5.009003||p
+SvPVX|||
+SvPV_const|5.009003||p
+SvPV_flags_const_nolen|5.009003||p
+SvPV_flags_const|5.009003||p
+SvPV_flags_mutable|5.009003||p
+SvPV_flags|5.007002||p
+SvPV_force_flags_mutable|5.009003||p
+SvPV_force_flags_nolen|5.009003||p
+SvPV_force_flags|5.007002||p
+SvPV_force_mutable|5.009003||p
+SvPV_force_nolen|5.009003||p
+SvPV_force_nomg_nolen|5.009003||p
+SvPV_force_nomg|5.007002||p
+SvPV_force|||p
+SvPV_mutable|5.009003||p
+SvPV_nolen_const|5.009003||p
+SvPV_nolen|5.006000||p
+SvPV_nomg_const_nolen|5.009003||p
+SvPV_nomg_const|5.009003||p
+SvPV_nomg|5.007002||p
+SvPV_renew|5.009003||p
+SvPV_set|||
+SvPVbyte_force||5.009002|
+SvPVbyte_nolen||5.006000|
+SvPVbytex_force||5.006000|
+SvPVbytex||5.006000|
+SvPVbyte|5.006000||p
+SvPVutf8_force||5.006000|
+SvPVutf8_nolen||5.006000|
+SvPVutf8x_force||5.006000|
+SvPVutf8x||5.006000|
+SvPVutf8||5.006000|
+SvPVx|||
+SvPV|||
+SvREFCNT_dec|||
+SvREFCNT_inc_NN|5.009004||p
+SvREFCNT_inc_simple_NN|5.009004||p
+SvREFCNT_inc_simple_void_NN|5.009004||p
+SvREFCNT_inc_simple_void|5.009004||p
+SvREFCNT_inc_simple|5.009004||p
+SvREFCNT_inc_void_NN|5.009004||p
+SvREFCNT_inc_void|5.009004||p
+SvREFCNT_inc|||p
+SvREFCNT|||
+SvROK_off|||
+SvROK_on|||
+SvROK|||
+SvRV_set|5.009003||p
+SvRV|||
+SvRXOK||5.009005|
+SvRX||5.009005|
+SvSETMAGIC|||
+SvSHARED_HASH|5.009003||p
+SvSHARE||5.007003|
+SvSTASH_set|5.009003||p
+SvSTASH|||
+SvSetMagicSV_nosteal||5.004000|
+SvSetMagicSV||5.004000|
+SvSetSV_nosteal||5.004000|
+SvSetSV|||
+SvTAINTED_off||5.004000|
+SvTAINTED_on||5.004000|
+SvTAINTED||5.004000|
+SvTAINT|||
+SvTRUE|||
+SvTYPE|||
+SvUNLOCK||5.007003|
+SvUOK|5.007001|5.006000|p
+SvUPGRADE|||
+SvUTF8_off||5.006000|
+SvUTF8_on||5.006000|
+SvUTF8||5.006000|
+SvUVXx|5.004000||p
+SvUVX|5.004000||p
+SvUV_nomg|5.009001||p
+SvUV_set|5.009003||p
+SvUVx|5.004000||p
+SvUV|5.004000||p
+SvVOK||5.008001|
+SvVSTRING_mg|5.009004||p
+THIS|||n
+UNDERBAR|5.009002||p
+UTF8_MAXBYTES|5.009002||p
+UVSIZE|5.006000||p
+UVTYPE|5.006000||p
+UVXf|5.007001||p
+UVof|5.006000||p
+UVuf|5.006000||p
+UVxf|5.006000||p
+WARN_ALL|5.006000||p
+WARN_AMBIGUOUS|5.006000||p
+WARN_ASSERTIONS|5.011000||p
+WARN_BAREWORD|5.006000||p
+WARN_CLOSED|5.006000||p
+WARN_CLOSURE|5.006000||p
+WARN_DEBUGGING|5.006000||p
+WARN_DEPRECATED|5.006000||p
+WARN_DIGIT|5.006000||p
+WARN_EXEC|5.006000||p
+WARN_EXITING|5.006000||p
+WARN_GLOB|5.006000||p
+WARN_INPLACE|5.006000||p
+WARN_INTERNAL|5.006000||p
+WARN_IO|5.006000||p
+WARN_LAYER|5.008000||p
+WARN_MALLOC|5.006000||p
+WARN_MISC|5.006000||p
+WARN_NEWLINE|5.006000||p
+WARN_NUMERIC|5.006000||p
+WARN_ONCE|5.006000||p
+WARN_OVERFLOW|5.006000||p
+WARN_PACK|5.006000||p
+WARN_PARENTHESIS|5.006000||p
+WARN_PIPE|5.006000||p
+WARN_PORTABLE|5.006000||p
+WARN_PRECEDENCE|5.006000||p
+WARN_PRINTF|5.006000||p
+WARN_PROTOTYPE|5.006000||p
+WARN_QW|5.006000||p
+WARN_RECURSION|5.006000||p
+WARN_REDEFINE|5.006000||p
+WARN_REGEXP|5.006000||p
+WARN_RESERVED|5.006000||p
+WARN_SEMICOLON|5.006000||p
+WARN_SEVERE|5.006000||p
+WARN_SIGNAL|5.006000||p
+WARN_SUBSTR|5.006000||p
+WARN_SYNTAX|5.006000||p
+WARN_TAINT|5.006000||p
+WARN_THREADS|5.008000||p
+WARN_UNINITIALIZED|5.006000||p
+WARN_UNOPENED|5.006000||p
+WARN_UNPACK|5.006000||p
+WARN_UNTIE|5.006000||p
+WARN_UTF8|5.006000||p
+WARN_VOID|5.006000||p
+XCPT_CATCH|5.009002||p
+XCPT_RETHROW|5.009002||p
+XCPT_TRY_END|5.009002||p
+XCPT_TRY_START|5.009002||p
+XPUSHi|||
+XPUSHmortal|5.009002||p
+XPUSHn|||
+XPUSHp|||
+XPUSHs|||
+XPUSHu|5.004000||p
+XSPROTO|5.010000||p
+XSRETURN_EMPTY|||
+XSRETURN_IV|||
+XSRETURN_NO|||
+XSRETURN_NV|||
+XSRETURN_PV|||
+XSRETURN_UNDEF|||
+XSRETURN_UV|5.008001||p
+XSRETURN_YES|||
+XSRETURN|||p
+XST_mIV|||
+XST_mNO|||
+XST_mNV|||
+XST_mPV|||
+XST_mUNDEF|||
+XST_mUV|5.008001||p
+XST_mYES|||
+XS_VERSION_BOOTCHECK|||
+XS_VERSION|||
+XSprePUSH|5.006000||p
+XS|||
+ZeroD|5.009002||p
+Zero|||
+_aMY_CXT|5.007003||p
+_pMY_CXT|5.007003||p
+aMY_CXT_|5.007003||p
+aMY_CXT|5.007003||p
+aTHXR_|5.011000||p
+aTHXR|5.011000||p
+aTHX_|5.006000||p
+aTHX|5.006000||p
+add_data|||n
+addmad|||
+allocmy|||
+amagic_call|||
+amagic_cmp_locale|||
+amagic_cmp|||
+amagic_i_ncmp|||
+amagic_ncmp|||
+any_dup|||
+ao|||
+append_elem|||
+append_list|||
+append_madprops|||
+apply_attrs_my|||
+apply_attrs_string||5.006001|
+apply_attrs|||
+apply|||
+atfork_lock||5.007003|n
+atfork_unlock||5.007003|n
+av_arylen_p||5.009003|
+av_clear|||
+av_create_and_push||5.009005|
+av_create_and_unshift_one||5.009005|
+av_delete||5.006000|
+av_exists||5.006000|
+av_extend|||
+av_fetch|||
+av_fill|||
+av_iter_p||5.011000|
+av_len|||
+av_make|||
+av_pop|||
+av_push|||
+av_reify|||
+av_shift|||
+av_store|||
+av_undef|||
+av_unshift|||
+ax|||n
+bad_type|||
+bind_match|||
+block_end|||
+block_gimme||5.004000|
+block_start|||
+boolSV|5.004000||p
+boot_core_PerlIO|||
+boot_core_UNIVERSAL|||
+boot_core_mro|||
+bytes_from_utf8||5.007001|
+bytes_to_uni|||n
+bytes_to_utf8||5.006001|
+call_argv|5.006000||p
+call_atexit||5.006000|
+call_list||5.004000|
+call_method|5.006000||p
+call_pv|5.006000||p
+call_sv|5.006000||p
+calloc||5.007002|n
+cando|||
+cast_i32||5.006000|
+cast_iv||5.006000|
+cast_ulong||5.006000|
+cast_uv||5.006000|
+check_type_and_open|||
+check_uni|||
+checkcomma|||
+checkposixcc|||
+ckWARN|5.006000||p
+ck_anoncode|||
+ck_bitop|||
+ck_concat|||
+ck_defined|||
+ck_delete|||
+ck_die|||
+ck_each|||
+ck_eof|||
+ck_eval|||
+ck_exec|||
+ck_exists|||
+ck_exit|||
+ck_ftst|||
+ck_fun|||
+ck_glob|||
+ck_grep|||
+ck_index|||
+ck_join|||
+ck_lfun|||
+ck_listiob|||
+ck_match|||
+ck_method|||
+ck_null|||
+ck_open|||
+ck_readline|||
+ck_repeat|||
+ck_require|||
+ck_return|||
+ck_rfun|||
+ck_rvconst|||
+ck_sassign|||
+ck_select|||
+ck_shift|||
+ck_sort|||
+ck_spair|||
+ck_split|||
+ck_subr|||
+ck_substr|||
+ck_svconst|||
+ck_trunc|||
+ck_unpack|||
+ckwarn_d||5.009003|
+ckwarn||5.009003|
+cl_and|||n
+cl_anything|||n
+cl_init_zero|||n
+cl_init|||n
+cl_is_anything|||n
+cl_or|||n
+clear_placeholders|||
+closest_cop|||
+convert|||
+cop_free|||
+cr_textfilter|||
+create_eval_scope|||
+croak_nocontext|||vn
+croak_xs_usage||5.011000|
+croak|||v
+csighandler||5.009003|n
+curmad|||
+custom_op_desc||5.007003|
+custom_op_name||5.007003|
+cv_ckproto_len|||
+cv_clone|||
+cv_const_sv||5.004000|
+cv_dump|||
+cv_undef|||
+cx_dump||5.005000|
+cx_dup|||
+cxinc|||
+dAXMARK|5.009003||p
+dAX|5.007002||p
+dITEMS|5.007002||p
+dMARK|||
+dMULTICALL||5.009003|
+dMY_CXT_SV|5.007003||p
+dMY_CXT|5.007003||p
+dNOOP|5.006000||p
+dORIGMARK|||
+dSP|||
+dTHR|5.004050||p
+dTHXR|5.011000||p
+dTHXa|5.006000||p
+dTHXoa|5.006000||p
+dTHX|5.006000||p
+dUNDERBAR|5.009002||p
+dVAR|5.009003||p
+dXCPT|5.009002||p
+dXSARGS|||
+dXSI32|||
+dXSTARG|5.006000||p
+deb_curcv|||
+deb_nocontext|||vn
+deb_stack_all|||
+deb_stack_n|||
+debop||5.005000|
+debprofdump||5.005000|
+debprof|||
+debstackptrs||5.007003|
+debstack||5.007003|
+debug_start_match|||
+deb||5.007003|v
+del_sv|||
+delete_eval_scope|||
+delimcpy||5.004000|
+deprecate_old|||
+deprecate|||
+despatch_signals||5.007001|
+destroy_matcher|||
+die_nocontext|||vn
+die_where|||
+die|||v
+dirp_dup|||
+div128|||
+djSP|||
+do_aexec5|||
+do_aexec|||
+do_aspawn|||
+do_binmode||5.004050|
+do_chomp|||
+do_chop|||
+do_close|||
+do_dump_pad|||
+do_eof|||
+do_exec3|||
+do_execfree|||
+do_exec|||
+do_gv_dump||5.006000|
+do_gvgv_dump||5.006000|
+do_hv_dump||5.006000|
+do_ipcctl|||
+do_ipcget|||
+do_join|||
+do_kv|||
+do_magic_dump||5.006000|
+do_msgrcv|||
+do_msgsnd|||
+do_oddball|||
+do_op_dump||5.006000|
+do_op_xmldump|||
+do_open9||5.006000|
+do_openn||5.007001|
+do_open||5.004000|
+do_pmop_dump||5.006000|
+do_pmop_xmldump|||
+do_print|||
+do_readline|||
+do_seek|||
+do_semop|||
+do_shmio|||
+do_smartmatch|||
+do_spawn_nowait|||
+do_spawn|||
+do_sprintf|||
+do_sv_dump||5.006000|
+do_sysseek|||
+do_tell|||
+do_trans_complex_utf8|||
+do_trans_complex|||
+do_trans_count_utf8|||
+do_trans_count|||
+do_trans_simple_utf8|||
+do_trans_simple|||
+do_trans|||
+do_vecget|||
+do_vecset|||
+do_vop|||
+docatch|||
+doeval|||
+dofile|||
+dofindlabel|||
+doform|||
+doing_taint||5.008001|n
+dooneliner|||
+doopen_pm|||
+doparseform|||
+dopoptoeval|||
+dopoptogiven|||
+dopoptolabel|||
+dopoptoloop|||
+dopoptosub_at|||
+dopoptowhen|||
+doref||5.009003|
+dounwind|||
+dowantarray|||
+dump_all||5.006000|
+dump_eval||5.006000|
+dump_exec_pos|||
+dump_fds|||
+dump_form||5.006000|
+dump_indent||5.006000|v
+dump_mstats|||
+dump_packsubs||5.006000|
+dump_sub||5.006000|
+dump_sv_child|||
+dump_trie_interim_list|||
+dump_trie_interim_table|||
+dump_trie|||
+dump_vindent||5.006000|
+dumpuntil|||
+dup_attrlist|||
+emulate_cop_io|||
+eval_pv|5.006000||p
+eval_sv|5.006000||p
+exec_failed|||
+expect_number|||
+fbm_compile||5.005000|
+fbm_instr||5.005000|
+feature_is_enabled|||
+fetch_cop_label||5.011000|
+filter_add|||
+filter_del|||
+filter_gets|||
+filter_read|||
+find_and_forget_pmops|||
+find_array_subscript|||
+find_beginning|||
+find_byclass|||
+find_hash_subscript|||
+find_in_my_stash|||
+find_runcv||5.008001|
+find_rundefsvoffset||5.009002|
+find_script|||
+find_uninit_var|||
+first_symbol|||n
+fold_constants|||
+forbid_setid|||
+force_ident|||
+force_list|||
+force_next|||
+force_version|||
+force_word|||
+forget_pmop|||
+form_nocontext|||vn
+form||5.004000|v
+fp_dup|||
+fprintf_nocontext|||vn
+free_global_struct|||
+free_tied_hv_pool|||
+free_tmps|||
+gen_constant_list|||
+get_arena|||
+get_aux_mg|||
+get_av|5.006000||p
+get_context||5.006000|n
+get_cvn_flags||5.009005|
+get_cv|5.006000||p
+get_db_sub|||
+get_debug_opts|||
+get_hash_seed|||
+get_hv|5.006000||p
+get_isa_hash|||
+get_mstats|||
+get_no_modify|||
+get_num|||
+get_op_descs||5.005000|
+get_op_names||5.005000|
+get_opargs|||
+get_ppaddr||5.006000|
+get_re_arg|||
+get_sv|5.006000||p
+get_vtbl||5.005030|
+getcwd_sv||5.007002|
+getenv_len|||
+glob_2number|||
+glob_assign_glob|||
+glob_assign_ref|||
+gp_dup|||
+gp_free|||
+gp_ref|||
+grok_bin|5.007003||p
+grok_hex|5.007003||p
+grok_number|5.007002||p
+grok_numeric_radix|5.007002||p
+grok_oct|5.007003||p
+group_end|||
+gv_AVadd|||
+gv_HVadd|||
+gv_IOadd|||
+gv_SVadd|||
+gv_autoload4||5.004000|
+gv_check|||
+gv_const_sv||5.009003|
+gv_dump||5.006000|
+gv_efullname3||5.004000|
+gv_efullname4||5.006001|
+gv_efullname|||
+gv_ename|||
+gv_fetchfile_flags||5.009005|
+gv_fetchfile|||
+gv_fetchmeth_autoload||5.007003|
+gv_fetchmethod_autoload||5.004000|
+gv_fetchmethod_flags||5.011000|
+gv_fetchmethod|||
+gv_fetchmeth|||
+gv_fetchpvn_flags|5.009002||p
+gv_fetchpvs|5.009004||p
+gv_fetchpv|||
+gv_fetchsv||5.009002|
+gv_fullname3||5.004000|
+gv_fullname4||5.006001|
+gv_fullname|||
+gv_get_super_pkg|||
+gv_handler||5.007001|
+gv_init_sv|||
+gv_init|||
+gv_name_set||5.009004|
+gv_stashpvn|5.004000||p
+gv_stashpvs|5.009003||p
+gv_stashpv|||
+gv_stashsv|||
+he_dup|||
+hek_dup|||
+hfreeentries|||
+hsplit|||
+hv_assert||5.011000|
+hv_auxinit|||n
+hv_backreferences_p|||
+hv_clear_placeholders||5.009001|
+hv_clear|||
+hv_common_key_len||5.010000|
+hv_common||5.010000|
+hv_copy_hints_hv|||
+hv_delayfree_ent||5.004000|
+hv_delete_common|||
+hv_delete_ent||5.004000|
+hv_delete|||
+hv_eiter_p||5.009003|
+hv_eiter_set||5.009003|
+hv_exists_ent||5.004000|
+hv_exists|||
+hv_fetch_ent||5.004000|
+hv_fetchs|5.009003||p
+hv_fetch|||
+hv_free_ent||5.004000|
+hv_iterinit|||
+hv_iterkeysv||5.004000|
+hv_iterkey|||
+hv_iternext_flags||5.008000|
+hv_iternextsv|||
+hv_iternext|||
+hv_iterval|||
+hv_kill_backrefs|||
+hv_ksplit||5.004000|
+hv_magic_check|||n
+hv_magic|||
+hv_name_set||5.009003|
+hv_notallowed|||
+hv_placeholders_get||5.009003|
+hv_placeholders_p||5.009003|
+hv_placeholders_set||5.009003|
+hv_riter_p||5.009003|
+hv_riter_set||5.009003|
+hv_scalar||5.009001|
+hv_store_ent||5.004000|
+hv_store_flags||5.008000|
+hv_stores|5.009004||p
+hv_store|||
+hv_undef|||
+ibcmp_locale||5.004000|
+ibcmp_utf8||5.007003|
+ibcmp|||
+incline|||
+incpush_if_exists|||
+incpush_use_sep|||
+incpush|||
+ingroup|||
+init_argv_symbols|||
+init_debugger|||
+init_global_struct|||
+init_i18nl10n||5.006000|
+init_i18nl14n||5.006000|
+init_ids|||
+init_interp|||
+init_main_stash|||
+init_perllib|||
+init_postdump_symbols|||
+init_predump_symbols|||
+init_stacks||5.005000|
+init_tm||5.007002|
+instr|||
+intro_my|||
+intuit_method|||
+intuit_more|||
+invert|||
+io_close|||
+isALNUMC|5.006000||p
+isALNUM|||
+isALPHA|||
+isASCII|5.006000||p
+isBLANK|5.006001||p
+isCNTRL|5.006000||p
+isDIGIT|||
+isGRAPH|5.006000||p
+isGV_with_GP|5.009004||p
+isLOWER|||
+isPRINT|5.004000||p
+isPSXSPC|5.006001||p
+isPUNCT|5.006000||p
+isSPACE|||
+isUPPER|||
+isXDIGIT|5.006000||p
+is_an_int|||
+is_gv_magical_sv|||
+is_handle_constructor|||n
+is_list_assignment|||
+is_lvalue_sub||5.007001|
+is_uni_alnum_lc||5.006000|
+is_uni_alnumc_lc||5.006000|
+is_uni_alnumc||5.006000|
+is_uni_alnum||5.006000|
+is_uni_alpha_lc||5.006000|
+is_uni_alpha||5.006000|
+is_uni_ascii_lc||5.006000|
+is_uni_ascii||5.006000|
+is_uni_cntrl_lc||5.006000|
+is_uni_cntrl||5.006000|
+is_uni_digit_lc||5.006000|
+is_uni_digit||5.006000|
+is_uni_graph_lc||5.006000|
+is_uni_graph||5.006000|
+is_uni_idfirst_lc||5.006000|
+is_uni_idfirst||5.006000|
+is_uni_lower_lc||5.006000|
+is_uni_lower||5.006000|
+is_uni_print_lc||5.006000|
+is_uni_print||5.006000|
+is_uni_punct_lc||5.006000|
+is_uni_punct||5.006000|
+is_uni_space_lc||5.006000|
+is_uni_space||5.006000|
+is_uni_upper_lc||5.006000|
+is_uni_upper||5.006000|
+is_uni_xdigit_lc||5.006000|
+is_uni_xdigit||5.006000|
+is_utf8_alnumc||5.006000|
+is_utf8_alnum||5.006000|
+is_utf8_alpha||5.006000|
+is_utf8_ascii||5.006000|
+is_utf8_char_slow|||n
+is_utf8_char||5.006000|
+is_utf8_cntrl||5.006000|
+is_utf8_common|||
+is_utf8_digit||5.006000|
+is_utf8_graph||5.006000|
+is_utf8_idcont||5.008000|
+is_utf8_idfirst||5.006000|
+is_utf8_lower||5.006000|
+is_utf8_mark||5.006000|
+is_utf8_print||5.006000|
+is_utf8_punct||5.006000|
+is_utf8_space||5.006000|
+is_utf8_string_loclen||5.009003|
+is_utf8_string_loc||5.008001|
+is_utf8_string||5.006001|
+is_utf8_upper||5.006000|
+is_utf8_xdigit||5.006000|
+isa_lookup|||
+items|||n
+ix|||n
+jmaybe|||
+join_exact|||
+keyword|||
+leave_scope|||
+lex_end|||
+lex_start|||
+linklist|||
+listkids|||
+list|||
+load_module_nocontext|||vn
+load_module|5.006000||pv
+localize|||
+looks_like_bool|||
+looks_like_number|||
+lop|||
+mPUSHi|5.009002||p
+mPUSHn|5.009002||p
+mPUSHp|5.009002||p
+mPUSHs|5.011000||p
+mPUSHu|5.009002||p
+mXPUSHi|5.009002||p
+mXPUSHn|5.009002||p
+mXPUSHp|5.009002||p
+mXPUSHs|5.011000||p
+mXPUSHu|5.009002||p
+mad_free|||
+madlex|||
+madparse|||
+magic_clear_all_env|||
+magic_clearenv|||
+magic_clearhint|||
+magic_clearisa|||
+magic_clearpack|||
+magic_clearsig|||
+magic_dump||5.006000|
+magic_existspack|||
+magic_freearylen_p|||
+magic_freeovrld|||
+magic_getarylen|||
+magic_getdefelem|||
+magic_getnkeys|||
+magic_getpack|||
+magic_getpos|||
+magic_getsig|||
+magic_getsubstr|||
+magic_gettaint|||
+magic_getuvar|||
+magic_getvec|||
+magic_get|||
+magic_killbackrefs|||
+magic_len|||
+magic_methcall|||
+magic_methpack|||
+magic_nextpack|||
+magic_regdata_cnt|||
+magic_regdatum_get|||
+magic_regdatum_set|||
+magic_scalarpack|||
+magic_set_all_env|||
+magic_setamagic|||
+magic_setarylen|||
+magic_setcollxfrm|||
+magic_setdbline|||
+magic_setdefelem|||
+magic_setenv|||
+magic_sethint|||
+magic_setisa|||
+magic_setmglob|||
+magic_setnkeys|||
+magic_setpack|||
+magic_setpos|||
+magic_setregexp|||
+magic_setsig|||
+magic_setsubstr|||
+magic_settaint|||
+magic_setutf8|||
+magic_setuvar|||
+magic_setvec|||
+magic_set|||
+magic_sizepack|||
+magic_wipepack|||
+make_matcher|||
+make_trie_failtable|||
+make_trie|||
+malloc_good_size|||n
+malloced_size|||n
+malloc||5.007002|n
+markstack_grow|||
+matcher_matches_sv|||
+measure_struct|||
+memEQ|5.004000||p
+memNE|5.004000||p
+mem_collxfrm|||
+mem_log_common|||n
+mess_alloc|||
+mess_nocontext|||vn
+mess||5.006000|v
+method_common|||
+mfree||5.007002|n
+mg_clear|||
+mg_copy|||
+mg_dup|||
+mg_find|||
+mg_free|||
+mg_get|||
+mg_length||5.005000|
+mg_localize|||
+mg_magical|||
+mg_set|||
+mg_size||5.005000|
+mini_mktime||5.007002|
+missingterm|||
+mode_from_discipline|||
+modkids|||
+mod|||
+more_bodies|||
+more_sv|||
+moreswitches|||
+mro_get_from_name||5.011000|
+mro_get_linear_isa_dfs|||
+mro_get_linear_isa||5.009005|
+mro_get_private_data||5.011000|
+mro_isa_changed_in|||
+mro_meta_dup|||
+mro_meta_init|||
+mro_method_changed_in||5.009005|
+mro_register||5.011000|
+mro_set_mro||5.011000|
+mro_set_private_data||5.011000|
+mul128|||
+mulexp10|||n
+my_atof2||5.007002|
+my_atof||5.006000|
+my_attrs|||
+my_bcopy|||n
+my_betoh16|||n
+my_betoh32|||n
+my_betoh64|||n
+my_betohi|||n
+my_betohl|||n
+my_betohs|||n
+my_bzero|||n
+my_chsize|||
+my_clearenv|||
+my_cxt_index|||
+my_cxt_init|||
+my_dirfd||5.009005|
+my_exit_jump|||
+my_exit|||
+my_failure_exit||5.004000|
+my_fflush_all||5.006000|
+my_fork||5.007003|n
+my_htobe16|||n
+my_htobe32|||n
+my_htobe64|||n
+my_htobei|||n
+my_htobel|||n
+my_htobes|||n
+my_htole16|||n
+my_htole32|||n
+my_htole64|||n
+my_htolei|||n
+my_htolel|||n
+my_htoles|||n
+my_htonl|||
+my_kid|||
+my_letoh16|||n
+my_letoh32|||n
+my_letoh64|||n
+my_letohi|||n
+my_letohl|||n
+my_letohs|||n
+my_lstat|||
+my_memcmp||5.004000|n
+my_memset|||n
+my_ntohl|||
+my_pclose||5.004000|
+my_popen_list||5.007001|
+my_popen||5.004000|
+my_setenv|||
+my_snprintf|5.009004||pvn
+my_socketpair||5.007003|n
+my_sprintf|5.009003||pvn
+my_stat|||
+my_strftime||5.007002|
+my_strlcat|5.009004||pn
+my_strlcpy|5.009004||pn
+my_swabn|||n
+my_swap|||
+my_unexec|||
+my_vsnprintf||5.009004|n
+need_utf8|||n
+newANONATTRSUB||5.006000|
+newANONHASH|||
+newANONLIST|||
+newANONSUB|||
+newASSIGNOP|||
+newATTRSUB||5.006000|
+newAVREF|||
+newAV|||
+newBINOP|||
+newCONDOP|||
+newCONSTSUB|5.004050||p
+newCVREF|||
+newDEFSVOP|||
+newFORM|||
+newFOROP|||
+newGIVENOP||5.009003|
+newGIVWHENOP|||
+newGP|||
+newGVOP|||
+newGVREF|||
+newGVgen|||
+newHVREF|||
+newHVhv||5.005000|
+newHV|||
+newIO|||
+newLISTOP|||
+newLOGOP|||
+newLOOPEX|||
+newLOOPOP|||
+newMADPROP|||
+newMADsv|||
+newMYSUB|||
+newNULLLIST|||
+newOP|||
+newPADOP|||
+newPMOP|||
+newPROG|||
+newPVOP|||
+newRANGE|||
+newRV_inc|5.004000||p
+newRV_noinc|5.004000||p
+newRV|||
+newSLICEOP|||
+newSTATEOP|||
+newSUB|||
+newSVOP|||
+newSVREF|||
+newSV_type|5.009005||p
+newSVhek||5.009003|
+newSViv|||
+newSVnv|||
+newSVpvf_nocontext|||vn
+newSVpvf||5.004000|v
+newSVpvn_flags|5.011000||p
+newSVpvn_share|5.007001||p
+newSVpvn_utf8|5.011000||p
+newSVpvn|5.004050||p
+newSVpvs_flags|5.011000||p
+newSVpvs_share||5.009003|
+newSVpvs|5.009003||p
+newSVpv|||
+newSVrv|||
+newSVsv|||
+newSVuv|5.006000||p
+newSV|||
+newTOKEN|||
+newUNOP|||
+newWHENOP||5.009003|
+newWHILEOP||5.009003|
+newXS_flags||5.009004|
+newXSproto||5.006000|
+newXS||5.006000|
+new_collate||5.006000|
+new_constant|||
+new_ctype||5.006000|
+new_he|||
+new_logop|||
+new_numeric||5.006000|
+new_stackinfo||5.005000|
+new_version||5.009000|
+new_warnings_bitfield|||
+next_symbol|||
+nextargv|||
+nextchar|||
+ninstr|||
+no_bareword_allowed|||
+no_fh_allowed|||
+no_op|||
+not_a_number|||
+nothreadhook||5.008000|
+nuke_stacks|||
+num_overflow|||n
+offer_nice_chunk|||
+oopsAV|||
+oopsHV|||
+op_clear|||
+op_const_sv|||
+op_dump||5.006000|
+op_free|||
+op_getmad_weak|||
+op_getmad|||
+op_null||5.007002|
+op_refcnt_dec|||
+op_refcnt_inc|||
+op_refcnt_lock||5.009002|
+op_refcnt_unlock||5.009002|
+op_xmldump|||
+open_script|||
+pMY_CXT_|5.007003||p
+pMY_CXT|5.007003||p
+pTHX_|5.006000||p
+pTHX|5.006000||p
+packWARN|5.007003||p
+pack_cat||5.007003|
+pack_rec|||
+package|||
+packlist||5.008001|
+pad_add_anon|||
+pad_add_name|||
+pad_alloc|||
+pad_block_start|||
+pad_check_dup|||
+pad_compname_type|||
+pad_findlex|||
+pad_findmy|||
+pad_fixup_inner_anons|||
+pad_free|||
+pad_leavemy|||
+pad_new|||
+pad_peg|||n
+pad_push|||
+pad_reset|||
+pad_setsv|||
+pad_sv||5.011000|
+pad_swipe|||
+pad_tidy|||
+pad_undef|||
+parse_body|||
+parse_unicode_opts|||
+parser_dup|||
+parser_free|||
+path_is_absolute|||n
+peep|||
+pending_Slabs_to_ro|||
+perl_alloc_using|||n
+perl_alloc|||n
+perl_clone_using|||n
+perl_clone|||n
+perl_construct|||n
+perl_destruct||5.007003|n
+perl_free|||n
+perl_parse||5.006000|n
+perl_run|||n
+pidgone|||
+pm_description|||
+pmflag|||
+pmop_dump||5.006000|
+pmop_xmldump|||
+pmruntime|||
+pmtrans|||
+pop_scope|||
+pregcomp||5.009005|
+pregexec|||
+pregfree2||5.011000|
+pregfree|||
+prepend_elem|||
+prepend_madprops|||
+printbuf|||
+printf_nocontext|||vn
+process_special_blocks|||
+ptr_table_clear||5.009005|
+ptr_table_fetch||5.009005|
+ptr_table_find|||n
+ptr_table_free||5.009005|
+ptr_table_new||5.009005|
+ptr_table_split||5.009005|
+ptr_table_store||5.009005|
+push_scope|||
+put_byte|||
+pv_display|5.006000||p
+pv_escape|5.009004||p
+pv_pretty|5.009004||p
+pv_uni_display||5.007003|
+qerror|||
+qsortsvu|||
+re_compile||5.009005|
+re_croak2|||
+re_dup_guts|||
+re_intuit_start||5.009005|
+re_intuit_string||5.006000|
+readpipe_override|||
+realloc||5.007002|n
+reentrant_free|||
+reentrant_init|||
+reentrant_retry|||vn
+reentrant_size|||
+ref_array_or_hash|||
+refcounted_he_chain_2hv|||
+refcounted_he_fetch|||
+refcounted_he_free|||
+refcounted_he_new_common|||
+refcounted_he_new|||
+refcounted_he_value|||
+refkids|||
+refto|||
+ref||5.011000|
+reg_check_named_buff_matched|||
+reg_named_buff_all||5.009005|
+reg_named_buff_exists||5.009005|
+reg_named_buff_fetch||5.009005|
+reg_named_buff_firstkey||5.009005|
+reg_named_buff_iter|||
+reg_named_buff_nextkey||5.009005|
+reg_named_buff_scalar||5.009005|
+reg_named_buff|||
+reg_namedseq|||
+reg_node|||
+reg_numbered_buff_fetch|||
+reg_numbered_buff_length|||
+reg_numbered_buff_store|||
+reg_qr_package|||
+reg_recode|||
+reg_scan_name|||
+reg_skipcomment|||
+reg_temp_copy|||
+reganode|||
+regatom|||
+regbranch|||
+regclass_swash||5.009004|
+regclass|||
+regcppop|||
+regcppush|||
+regcurly|||n
+regdump_extflags|||
+regdump||5.005000|
+regdupe_internal|||
+regexec_flags||5.005000|
+regfree_internal||5.009005|
+reghop3|||n
+reghop4|||n
+reghopmaybe3|||n
+reginclass|||
+reginitcolors||5.006000|
+reginsert|||
+regmatch|||
+regnext||5.005000|
+regpiece|||
+regpposixcc|||
+regprop|||
+regrepeat|||
+regtail_study|||
+regtail|||
+regtry|||
+reguni|||
+regwhite|||n
+reg|||
+repeatcpy|||
+report_evil_fh|||
+report_uninit|||
+require_pv||5.006000|
+require_tie_mod|||
+restore_magic|||
+rninstr|||
+rsignal_restore|||
+rsignal_save|||
+rsignal_state||5.004000|
+rsignal||5.004000|
+run_body|||
+run_user_filter|||
+runops_debug||5.005000|
+runops_standard||5.005000|
+rvpv_dup|||
+rxres_free|||
+rxres_restore|||
+rxres_save|||
+safesyscalloc||5.006000|n
+safesysfree||5.006000|n
+safesysmalloc||5.006000|n
+safesysrealloc||5.006000|n
+same_dirent|||
+save_I16||5.004000|
+save_I32|||
+save_I8||5.006000|
+save_adelete||5.011000|
+save_aelem||5.004050|
+save_alloc||5.006000|
+save_aptr|||
+save_ary|||
+save_bool||5.008001|
+save_clearsv|||
+save_delete|||
+save_destructor_x||5.006000|
+save_destructor||5.006000|
+save_freeop|||
+save_freepv|||
+save_freesv|||
+save_generic_pvref||5.006001|
+save_generic_svref||5.005030|
+save_gp||5.004000|
+save_hash|||
+save_hek_flags|||n
+save_helem_flags||5.011000|
+save_helem||5.004050|
+save_hints|||
+save_hptr|||
+save_int|||
+save_item|||
+save_iv||5.005000|
+save_lines|||
+save_list|||
+save_long|||
+save_magic|||
+save_mortalizesv||5.007001|
+save_nogv|||
+save_op|||
+save_padsv_and_mortalize||5.011000|
+save_pptr|||
+save_pushi32ptr|||
+save_pushptri32ptr|||
+save_pushptrptr|||
+save_pushptr||5.011000|
+save_re_context||5.006000|
+save_scalar_at|||
+save_scalar|||
+save_set_svflags||5.009000|
+save_shared_pvref||5.007003|
+save_sptr|||
+save_svref|||
+save_vptr||5.006000|
+savepvn|||
+savepvs||5.009003|
+savepv|||
+savesharedpvn||5.009005|
+savesharedpv||5.007003|
+savestack_grow_cnt||5.008001|
+savestack_grow|||
+savesvpv||5.009002|
+sawparens|||
+scalar_mod_type|||n
+scalarboolean|||
+scalarkids|||
+scalarseq|||
+scalarvoid|||
+scalar|||
+scan_bin||5.006000|
+scan_commit|||
+scan_const|||
+scan_formline|||
+scan_heredoc|||
+scan_hex|||
+scan_ident|||
+scan_inputsymbol|||
+scan_num||5.007001|
+scan_oct|||
+scan_pat|||
+scan_str|||
+scan_subst|||
+scan_trans|||
+scan_version||5.009001|
+scan_vstring||5.009005|
+scan_word|||
+scope|||
+screaminstr||5.005000|
+search_const|||
+seed||5.008001|
+sequence_num|||
+sequence_tail|||
+sequence|||
+set_context||5.006000|n
+set_numeric_local||5.006000|
+set_numeric_radix||5.006000|
+set_numeric_standard||5.006000|
+setdefout|||
+share_hek_flags|||
+share_hek||5.004000|
+si_dup|||
+sighandler|||n
+simplify_sort|||
+skipspace0|||
+skipspace1|||
+skipspace2|||
+skipspace|||
+softref2xv|||
+sortcv_stacked|||
+sortcv_xsub|||
+sortcv|||
+sortsv_flags||5.009003|
+sortsv||5.007003|
+space_join_names_mortal|||
+ss_dup|||
+stack_grow|||
+start_force|||
+start_glob|||
+start_subparse||5.004000|
+stashpv_hvname_match||5.011000|
+stdize_locale|||
+store_cop_label|||
+strEQ|||
+strGE|||
+strGT|||
+strLE|||
+strLT|||
+strNE|||
+str_to_version||5.006000|
+strip_return|||
+strnEQ|||
+strnNE|||
+study_chunk|||
+sub_crush_depth|||
+sublex_done|||
+sublex_push|||
+sublex_start|||
+sv_2bool|||
+sv_2cv|||
+sv_2io|||
+sv_2iuv_common|||
+sv_2iuv_non_preserve|||
+sv_2iv_flags||5.009001|
+sv_2iv|||
+sv_2mortal|||
+sv_2num|||
+sv_2nv|||
+sv_2pv_flags|5.007002||p
+sv_2pv_nolen|5.006000||p
+sv_2pvbyte_nolen|5.006000||p
+sv_2pvbyte|5.006000||p
+sv_2pvutf8_nolen||5.006000|
+sv_2pvutf8||5.006000|
+sv_2pv|||
+sv_2uv_flags||5.009001|
+sv_2uv|5.004000||p
+sv_add_arena|||
+sv_add_backref|||
+sv_backoff|||
+sv_bless|||
+sv_cat_decode||5.008001|
+sv_catpv_mg|5.004050||p
+sv_catpvf_mg_nocontext|||pvn
+sv_catpvf_mg|5.006000|5.004000|pv
+sv_catpvf_nocontext|||vn
+sv_catpvf||5.004000|v
+sv_catpvn_flags||5.007002|
+sv_catpvn_mg|5.004050||p
+sv_catpvn_nomg|5.007002||p
+sv_catpvn|||
+sv_catpvs|5.009003||p
+sv_catpv|||
+sv_catsv_flags||5.007002|
+sv_catsv_mg|5.004050||p
+sv_catsv_nomg|5.007002||p
+sv_catsv|||
+sv_catxmlpvn|||
+sv_catxmlsv|||
+sv_chop|||
+sv_clean_all|||
+sv_clean_objs|||
+sv_clear|||
+sv_cmp_locale||5.004000|
+sv_cmp|||
+sv_collxfrm|||
+sv_compile_2op||5.008001|
+sv_copypv||5.007003|
+sv_dec|||
+sv_del_backref|||
+sv_derived_from||5.004000|
+sv_destroyable||5.010000|
+sv_does||5.009004|
+sv_dump|||
+sv_dup_inc_multiple|||
+sv_dup|||
+sv_eq|||
+sv_exp_grow|||
+sv_force_normal_flags||5.007001|
+sv_force_normal||5.006000|
+sv_free2|||
+sv_free_arenas|||
+sv_free|||
+sv_gets||5.004000|
+sv_grow|||
+sv_i_ncmp|||
+sv_inc|||
+sv_insert_flags||5.011000|
+sv_insert|||
+sv_isa|||
+sv_isobject|||
+sv_iv||5.005000|
+sv_kill_backrefs|||
+sv_len_utf8||5.006000|
+sv_len|||
+sv_magic_portable|5.011000|5.004000|p
+sv_magicext||5.007003|
+sv_magic|||
+sv_mortalcopy|||
+sv_ncmp|||
+sv_newmortal|||
+sv_newref|||
+sv_nolocking||5.007003|
+sv_nosharing||5.007003|
+sv_nounlocking|||
+sv_nv||5.005000|
+sv_peek||5.005000|
+sv_pos_b2u_midway|||
+sv_pos_b2u||5.006000|
+sv_pos_u2b_cached|||
+sv_pos_u2b_forwards|||n
+sv_pos_u2b_midway|||n
+sv_pos_u2b||5.006000|
+sv_pvbyten_force||5.006000|
+sv_pvbyten||5.006000|
+sv_pvbyte||5.006000|
+sv_pvn_force_flags|5.007002||p
+sv_pvn_force|||
+sv_pvn_nomg|5.007003|5.005000|p
+sv_pvn||5.005000|
+sv_pvutf8n_force||5.006000|
+sv_pvutf8n||5.006000|
+sv_pvutf8||5.006000|
+sv_pv||5.006000|
+sv_recode_to_utf8||5.007003|
+sv_reftype|||
+sv_release_COW|||
+sv_replace|||
+sv_report_used|||
+sv_reset|||
+sv_rvweaken||5.006000|
+sv_setiv_mg|5.004050||p
+sv_setiv|||
+sv_setnv_mg|5.006000||p
+sv_setnv|||
+sv_setpv_mg|5.004050||p
+sv_setpvf_mg_nocontext|||pvn
+sv_setpvf_mg|5.006000|5.004000|pv
+sv_setpvf_nocontext|||vn
+sv_setpvf||5.004000|v
+sv_setpviv_mg||5.008001|
+sv_setpviv||5.008001|
+sv_setpvn_mg|5.004050||p
+sv_setpvn|||
+sv_setpvs|5.009004||p
+sv_setpv|||
+sv_setref_iv|||
+sv_setref_nv|||
+sv_setref_pvn|||
+sv_setref_pv|||
+sv_setref_uv||5.007001|
+sv_setsv_cow|||
+sv_setsv_flags||5.007002|
+sv_setsv_mg|5.004050||p
+sv_setsv_nomg|5.007002||p
+sv_setsv|||
+sv_setuv_mg|5.004050||p
+sv_setuv|5.004000||p
+sv_tainted||5.004000|
+sv_taint||5.004000|
+sv_true||5.005000|
+sv_unglob|||
+sv_uni_display||5.007003|
+sv_unmagic|||
+sv_unref_flags||5.007001|
+sv_unref|||
+sv_untaint||5.004000|
+sv_upgrade|||
+sv_usepvn_flags||5.009004|
+sv_usepvn_mg|5.004050||p
+sv_usepvn|||
+sv_utf8_decode||5.006000|
+sv_utf8_downgrade||5.006000|
+sv_utf8_encode||5.006000|
+sv_utf8_upgrade_flags_grow||5.011000|
+sv_utf8_upgrade_flags||5.007002|
+sv_utf8_upgrade_nomg||5.007002|
+sv_utf8_upgrade||5.007001|
+sv_uv|5.005000||p
+sv_vcatpvf_mg|5.006000|5.004000|p
+sv_vcatpvfn||5.004000|
+sv_vcatpvf|5.006000|5.004000|p
+sv_vsetpvf_mg|5.006000|5.004000|p
+sv_vsetpvfn||5.004000|
+sv_vsetpvf|5.006000|5.004000|p
+sv_xmlpeek|||
+svtype|||
+swallow_bom|||
+swap_match_buff|||
+swash_fetch||5.007002|
+swash_get|||
+swash_init||5.006000|
+sys_init3||5.010000|n
+sys_init||5.010000|n
+sys_intern_clear|||
+sys_intern_dup|||
+sys_intern_init|||
+sys_term||5.010000|n
+taint_env|||
+taint_proper|||
+tmps_grow||5.006000|
+toLOWER|||
+toUPPER|||
+to_byte_substr|||
+to_uni_fold||5.007003|
+to_uni_lower_lc||5.006000|
+to_uni_lower||5.007003|
+to_uni_title_lc||5.006000|
+to_uni_title||5.007003|
+to_uni_upper_lc||5.006000|
+to_uni_upper||5.007003|
+to_utf8_case||5.007003|
+to_utf8_fold||5.007003|
+to_utf8_lower||5.007003|
+to_utf8_substr|||
+to_utf8_title||5.007003|
+to_utf8_upper||5.007003|
+token_free|||
+token_getmad|||
+tokenize_use|||
+tokeq|||
+tokereport|||
+too_few_arguments|||
+too_many_arguments|||
+uiv_2buf|||n
+unlnk|||
+unpack_rec|||
+unpack_str||5.007003|
+unpackstring||5.008001|
+unshare_hek_or_pvn|||
+unshare_hek|||
+unsharepvn||5.004000|
+unwind_handler_stack|||
+update_debugger_info|||
+upg_version||5.009005|
+usage|||
+utf16_to_utf8_reversed||5.006001|
+utf16_to_utf8||5.006001|
+utf8_distance||5.006000|
+utf8_hop||5.006000|
+utf8_length||5.007001|
+utf8_mg_pos_cache_update|||
+utf8_to_bytes||5.006001|
+utf8_to_uvchr||5.007001|
+utf8_to_uvuni||5.007001|
+utf8n_to_uvchr|||
+utf8n_to_uvuni||5.007001|
+utilize|||
+uvchr_to_utf8_flags||5.007003|
+uvchr_to_utf8|||
+uvuni_to_utf8_flags||5.007003|
+uvuni_to_utf8||5.007001|
+validate_suid|||
+varname|||
+vcmp||5.009000|
+vcroak||5.006000|
+vdeb||5.007003|
+vdie_common|||
+vdie_croak_common|||
+vdie|||
+vform||5.006000|
+visit|||
+vivify_defelem|||
+vivify_ref|||
+vload_module|5.006000||p
+vmess||5.006000|
+vnewSVpvf|5.006000|5.004000|p
+vnormal||5.009002|
+vnumify||5.009000|
+vstringify||5.009000|
+vverify||5.009003|
+vwarner||5.006000|
+vwarn||5.006000|
+wait4pid|||
+warn_nocontext|||vn
+warner_nocontext|||vn
+warner|5.006000|5.004000|pv
+warn|||v
+watch|||
+whichsig|||
+write_no_mem|||
+write_to_stderr|||
+xmldump_all|||
+xmldump_attr|||
+xmldump_eval|||
+xmldump_form|||
+xmldump_indent|||v
+xmldump_packsubs|||
+xmldump_sub|||
+xmldump_vindent|||
+yyerror|||
+yylex|||
+yyparse|||
+yywarn|||
+);
+
+if (exists $opt{'list-unsupported'}) {
+  my $f;
+  for $f (sort { lc $a cmp lc $b } keys %API) {
+    next unless $API{$f}{todo};
+    print "$f ", '.'x(40-length($f)), " ", format_version($API{$f}{todo}), "\n";
+  }
+  exit 0;
+}
+
+# Scan for possible replacement candidates
+
+my(%replace, %need, %hints, %warnings, %depends);
+my $replace = 0;
+my($hint, $define, $function);
+
+sub find_api
+{
+  my $code = shift;
+  $code =~ s{
+    / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*)
+  | "[^"\\]*(?:\\.[^"\\]*)*"
+  | '[^'\\]*(?:\\.[^'\\]*)*' }{}egsx;
+  grep { exists $API{$_} } $code =~ /(\w+)/mg;
+}
+
+while (<DATA>) {
+  if ($hint) {
+    my $h = $hint->[0] eq 'Hint' ? \%hints : \%warnings;
+    if (m{^\s*\*\s(.*?)\s*$}) {
+      for (@{$hint->[1]}) {
+        $h->{$_} ||= '';  # suppress warning with older perls
+        $h->{$_} .= "$1\n";
+      }
+    }
+    else { undef $hint }
+  }
+
+  $hint = [$1, [split /,?\s+/, $2]]
+      if m{^\s*$rccs\s+(Hint|Warning):\s+(\w+(?:,?\s+\w+)*)\s*$};
+
+  if ($define) {
+    if ($define->[1] =~ /\\$/) {
+      $define->[1] .= $_;
+    }
+    else {
+      if (exists $API{$define->[0]} && $define->[1] !~ /^DPPP_\(/) {
+        my @n = find_api($define->[1]);
+        push @{$depends{$define->[0]}}, @n if @n
+      }
+      undef $define;
+    }
+  }
+
+  $define = [$1, $2] if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(.*)};
+
+  if ($function) {
+    if (/^}/) {
+      if (exists $API{$function->[0]}) {
+        my @n = find_api($function->[1]);
+        push @{$depends{$function->[0]}}, @n if @n
+      }
+      undef $function;
+    }
+    else {
+      $function->[1] .= $_;
+    }
+  }
+
+  $function = [$1, ''] if m{^DPPP_\(my_(\w+)\)};
+
+  $replace     = $1 if m{^\s*$rccs\s+Replace:\s+(\d+)\s+$rcce\s*$};
+  $replace{$2} = $1 if $replace and m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+)};
+  $replace{$2} = $1 if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+).*$rccs\s+Replace\s+$rcce};
+  $replace{$1} = $2 if m{^\s*$rccs\s+Replace (\w+) with (\w+)\s+$rcce\s*$};
+
+  if (m{^\s*$rccs\s+(\w+(\s*,\s*\w+)*)\s+depends\s+on\s+(\w+(\s*,\s*\w+)*)\s+$rcce\s*$}) {
+    my @deps = map { s/\s+//g; $_ } split /,/, $3;
+    my $d;
+    for $d (map { s/\s+//g; $_ } split /,/, $1) {
+      push @{$depends{$d}}, @deps;
+    }
+  }
+
+  $need{$1} = 1 if m{^#if\s+defined\(NEED_(\w+)(?:_GLOBAL)?\)};
+}
+
+for (values %depends) {
+  my %s;
+  $_ = [sort grep !$s{$_}++, @$_];
+}
+
+if (exists $opt{'api-info'}) {
+  my $f;
+  my $count = 0;
+  my $match = $opt{'api-info'} =~ m!^/(.*)/$! ? $1 : "^\Q$opt{'api-info'}\E\$";
+  for $f (sort { lc $a cmp lc $b } keys %API) {
+    next unless $f =~ /$match/;
+    print "\n=== $f ===\n\n";
+    my $info = 0;
+    if ($API{$f}{base} || $API{$f}{todo}) {
+      my $base = format_version($API{$f}{base} || $API{$f}{todo});
+      print "Supported at least starting from perl-$base.\n";
+      $info++;
+    }
+    if ($API{$f}{provided}) {
+      my $todo = $API{$f}{todo} ? format_version($API{$f}{todo}) : "5.003";
+      print "Support by $ppport provided back to perl-$todo.\n";
+      print "Support needs to be explicitly requested by NEED_$f.\n" if exists $need{$f};
+      print "Depends on: ", join(', ', @{$depends{$f}}), ".\n" if exists $depends{$f};
+      print "\n$hints{$f}" if exists $hints{$f};
+      print "\nWARNING:\n$warnings{$f}" if exists $warnings{$f};
+      $info++;
+    }
+    print "No portability information available.\n" unless $info;
+    $count++;
+  }
+  $count or print "Found no API matching '$opt{'api-info'}'.";
+  print "\n";
+  exit 0;
+}
+
+if (exists $opt{'list-provided'}) {
+  my $f;
+  for $f (sort { lc $a cmp lc $b } keys %API) {
+    next unless $API{$f}{provided};
+    my @flags;
+    push @flags, 'explicit' if exists $need{$f};
+    push @flags, 'depend'   if exists $depends{$f};
+    push @flags, 'hint'     if exists $hints{$f};
+    push @flags, 'warning'  if exists $warnings{$f};
+    my $flags = @flags ? '  ['.join(', ', @flags).']' : '';
+    print "$f$flags\n";
+  }
+  exit 0;
+}
+
+my @files;
+my @srcext = qw( .xs .c .h .cc .cpp -c.inc -xs.inc );
+my $srcext = join '|', map { quotemeta $_ } @srcext;
+
+if (@ARGV) {
+  my %seen;
+  for (@ARGV) {
+    if (-e) {
+      if (-f) {
+        push @files, $_ unless $seen{$_}++;
+      }
+      else { warn "'$_' is not a file.\n" }
+    }
+    else {
+      my @new = grep { -f } glob $_
+          or warn "'$_' does not exist.\n";
+      push @files, grep { !$seen{$_}++ } @new;
+    }
+  }
+}
+else {
+  eval {
+    require File::Find;
+    File::Find::find(sub {
+      $File::Find::name =~ /($srcext)$/i
+          and push @files, $File::Find::name;
+    }, '.');
+  };
+  if ($@) {
+    @files = map { glob "*$_" } @srcext;
+  }
+}
+
+if (!@ARGV || $opt{filter}) {
+  my(@in, @out);
+  my %xsc = map { /(.*)\.xs$/ ? ("$1.c" => 1, "$1.cc" => 1) : () } @files;
+  for (@files) {
+    my $out = exists $xsc{$_} || /\b\Q$ppport\E$/i || !/($srcext)$/i;
+    push @{ $out ? \@out : \@in }, $_;
+  }
+  if (@ARGV && @out) {
+    warning("Skipping the following files (use --nofilter to avoid this):\n| ", join "\n| ", @out);
+  }
+  @files = @in;
+}
+
+die "No input files given!\n" unless @files;
+
+my(%files, %global, %revreplace);
+%revreplace = reverse %replace;
+my $filename;
+my $patch_opened = 0;
+
+for $filename (@files) {
+  unless (open IN, "<$filename") {
+    warn "Unable to read from $filename: $!\n";
+    next;
+  }
+
+  info("Scanning $filename ...");
+
+  my $c = do { local $/; <IN> };
+  close IN;
+
+  my %file = (orig => $c, changes => 0);
+
+  # Temporarily remove C/XS comments and strings from the code
+  my @ccom;
+
+  $c =~ s{
+    ( ^$HS*\#$HS*include\b[^\r\n]+\b(?:\Q$ppport\E|XSUB\.h)\b[^\r\n]*
+    | ^$HS*\#$HS*(?:define|elif|if(?:def)?)\b[^\r\n]* )
+  | ( ^$HS*\#[^\r\n]*
+    | "[^"\\]*(?:\\.[^"\\]*)*"
+    | '[^'\\]*(?:\\.[^'\\]*)*'
+    | / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]* ) )
+  }{ defined $2 and push @ccom, $2;
+     defined $1 ? $1 : "$ccs$#ccom$cce" }mgsex;
+
+  $file{ccom} = \@ccom;
+  $file{code} = $c;
+  $file{has_inc_ppport} = $c =~ /^$HS*#$HS*include[^\r\n]+\b\Q$ppport\E\b/m;
+
+  my $func;
+
+  for $func (keys %API) {
+    my $match = $func;
+    $match .= "|$revreplace{$func}" if exists $revreplace{$func};
+    if ($c =~ /\b(?:Perl_)?($match)\b/) {
+      $file{uses_replace}{$1}++ if exists $revreplace{$func} && $1 eq $revreplace{$func};
+      $file{uses_Perl}{$func}++ if $c =~ /\bPerl_$func\b/;
+      if (exists $API{$func}{provided}) {
+        $file{uses_provided}{$func}++;
+        if (!exists $API{$func}{base} || $API{$func}{base} > $opt{'compat-version'}) {
+          $file{uses}{$func}++;
+          my @deps = rec_depend($func);
+          if (@deps) {
+            $file{uses_deps}{$func} = \@deps;
+            for (@deps) {
+              $file{uses}{$_} = 0 unless exists $file{uses}{$_};
+            }
+          }
+          for ($func, @deps) {
+            $file{needs}{$_} = 'static' if exists $need{$_};
+          }
+        }
+      }
+      if (exists $API{$func}{todo} && $API{$func}{todo} > $opt{'compat-version'}) {
+        if ($c =~ /\b$func\b/) {
+          $file{uses_todo}{$func}++;
+        }
+      }
+    }
+  }
+
+  while ($c =~ /^$HS*#$HS*define$HS+(NEED_(\w+?)(_GLOBAL)?)\b/mg) {
+    if (exists $need{$2}) {
+      $file{defined $3 ? 'needed_global' : 'needed_static'}{$2}++;
+    }
+    else { warning("Possibly wrong #define $1 in $filename") }
+  }
+
+  for (qw(uses needs uses_todo needed_global needed_static)) {
+    for $func (keys %{$file{$_}}) {
+      push @{$global{$_}{$func}}, $filename;
+    }
+  }
+
+  $files{$filename} = \%file;
+}
+
+# Globally resolve NEED_'s
+my $need;
+for $need (keys %{$global{needs}}) {
+  if (@{$global{needs}{$need}} > 1) {
+    my @targets = @{$global{needs}{$need}};
+    my @t = grep $files{$_}{needed_global}{$need}, @targets;
+    @targets = @t if @t;
+    @t = grep /\.xs$/i, @targets;
+    @targets = @t if @t;
+    my $target = shift @targets;
+    $files{$target}{needs}{$need} = 'global';
+    for (@{$global{needs}{$need}}) {
+      $files{$_}{needs}{$need} = 'extern' if $_ ne $target;
+    }
+  }
+}
+
+for $filename (@files) {
+  exists $files{$filename} or next;
+
+  info("=== Analyzing $filename ===");
+
+  my %file = %{$files{$filename}};
+  my $func;
+  my $c = $file{code};
+  my $warnings = 0;
+
+  for $func (sort keys %{$file{uses_Perl}}) {
+    if ($API{$func}{varargs}) {
+      unless ($API{$func}{nothxarg}) {
+        my $changes = ($c =~ s{\b(Perl_$func\s*\(\s*)(?!aTHX_?)(\)|[^\s)]*\))}
+                              { $1 . ($2 eq ')' ? 'aTHX' : 'aTHX_ ') . $2 }ge);
+        if ($changes) {
+          warning("Doesn't pass interpreter argument aTHX to Perl_$func");
+          $file{changes} += $changes;
+        }
+      }
+    }
+    else {
+      warning("Uses Perl_$func instead of $func");
+      $file{changes} += ($c =~ s{\bPerl_$func(\s*)\((\s*aTHX_?)?\s*}
+                                {$func$1(}g);
+    }
+  }
+
+  for $func (sort keys %{$file{uses_replace}}) {
+    warning("Uses $func instead of $replace{$func}");
+    $file{changes} += ($c =~ s/\b$func\b/$replace{$func}/g);
+  }
+
+  for $func (sort keys %{$file{uses_provided}}) {
+    if ($file{uses}{$func}) {
+      if (exists $file{uses_deps}{$func}) {
+        diag("Uses $func, which depends on ", join(', ', @{$file{uses_deps}{$func}}));
+      }
+      else {
+        diag("Uses $func");
+      }
+    }
+    $warnings += hint($func);
+  }
+
+  unless ($opt{quiet}) {
+    for $func (sort keys %{$file{uses_todo}}) {
+      print "*** WARNING: Uses $func, which may not be portable below perl ",
+            format_version($API{$func}{todo}), ", even with '$ppport'\n";
+      $warnings++;
+    }
+  }
+
+  for $func (sort keys %{$file{needed_static}}) {
+    my $message = '';
+    if (not exists $file{uses}{$func}) {
+      $message = "No need to define NEED_$func if $func is never used";
+    }
+    elsif (exists $file{needs}{$func} && $file{needs}{$func} ne 'static') {
+      $message = "No need to define NEED_$func when already needed globally";
+    }
+    if ($message) {
+      diag($message);
+      $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_$func\b.*$LF//mg);
+    }
+  }
+
+  for $func (sort keys %{$file{needed_global}}) {
+    my $message = '';
+    if (not exists $global{uses}{$func}) {
+      $message = "No need to define NEED_${func}_GLOBAL if $func is never used";
+    }
+    elsif (exists $file{needs}{$func}) {
+      if ($file{needs}{$func} eq 'extern') {
+        $message = "No need to define NEED_${func}_GLOBAL when already needed globally";
+      }
+      elsif ($file{needs}{$func} eq 'static') {
+        $message = "No need to define NEED_${func}_GLOBAL when only used in this file";
+      }
+    }
+    if ($message) {
+      diag($message);
+      $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_${func}_GLOBAL\b.*$LF//mg);
+    }
+  }
+
+  $file{needs_inc_ppport} = keys %{$file{uses}};
+
+  if ($file{needs_inc_ppport}) {
+    my $pp = '';
+
+    for $func (sort keys %{$file{needs}}) {
+      my $type = $file{needs}{$func};
+      next if $type eq 'extern';
+      my $suffix = $type eq 'global' ? '_GLOBAL' : '';
+      unless (exists $file{"needed_$type"}{$func}) {
+        if ($type eq 'global') {
+          diag("Files [@{$global{needs}{$func}}] need $func, adding global request");
+        }
+        else {
+          diag("File needs $func, adding static request");
+        }
+        $pp .= "#define NEED_$func$suffix\n";
+      }
+    }
+
+    if ($pp && ($c =~ s/^(?=$HS*#$HS*define$HS+NEED_\w+)/$pp/m)) {
+      $pp = '';
+      $file{changes}++;
+    }
+
+    unless ($file{has_inc_ppport}) {
+      diag("Needs to include '$ppport'");
+      $pp .= qq(#include "$ppport"\n)
+    }
+
+    if ($pp) {
+      $file{changes} += ($c =~ s/^($HS*#$HS*define$HS+NEED_\w+.*?)^/$1$pp/ms)
+                     || ($c =~ s/^(?=$HS*#$HS*include.*\Q$ppport\E)/$pp/m)
+                     || ($c =~ s/^($HS*#$HS*include.*XSUB.*\s*?)^/$1$pp/m)
+                     || ($c =~ s/^/$pp/);
+    }
+  }
+  else {
+    if ($file{has_inc_ppport}) {
+      diag("No need to include '$ppport'");
+      $file{changes} += ($c =~ s/^$HS*?#$HS*include.*\Q$ppport\E.*?$LF//m);
+    }
+  }
+
+  # put back in our C comments
+  my $ix;
+  my $cppc = 0;
+  my @ccom = @{$file{ccom}};
+  for $ix (0 .. $#ccom) {
+    if (!$opt{cplusplus} && $ccom[$ix] =~ s!^//!!) {
+      $cppc++;
+      $file{changes} += $c =~ s/$rccs$ix$rcce/$ccs$ccom[$ix] $cce/;
+    }
+    else {
+      $c =~ s/$rccs$ix$rcce/$ccom[$ix]/;
+    }
+  }
+
+  if ($cppc) {
+    my $s = $cppc != 1 ? 's' : '';
+    warning("Uses $cppc C++ style comment$s, which is not portable");
+  }
+
+  my $s = $warnings != 1 ? 's' : '';
+  my $warn = $warnings ? " ($warnings warning$s)" : '';
+  info("Analysis completed$warn");
+
+  if ($file{changes}) {
+    if (exists $opt{copy}) {
+      my $newfile = "$filename$opt{copy}";
+      if (-e $newfile) {
+        error("'$newfile' already exists, refusing to write copy of '$filename'");
+      }
+      else {
+        local *F;
+        if (open F, ">$newfile") {
+          info("Writing copy of '$filename' with changes to '$newfile'");
+          print F $c;
+          close F;
+        }
+        else {
+          error("Cannot open '$newfile' for writing: $!");
+        }
+      }
+    }
+    elsif (exists $opt{patch} || $opt{changes}) {
+      if (exists $opt{patch}) {
+        unless ($patch_opened) {
+          if (open PATCH, ">$opt{patch}") {
+            $patch_opened = 1;
+          }
+          else {
+            error("Cannot open '$opt{patch}' for writing: $!");
+            delete $opt{patch};
+            $opt{changes} = 1;
+            goto fallback;
+          }
+        }
+        mydiff(\*PATCH, $filename, $c);
+      }
+      else {
+fallback:
+        info("Suggested changes:");
+        mydiff(\*STDOUT, $filename, $c);
+      }
+    }
+    else {
+      my $s = $file{changes} == 1 ? '' : 's';
+      info("$file{changes} potentially required change$s detected");
+    }
+  }
+  else {
+    info("Looks good");
+  }
+}
+
+close PATCH if $patch_opened;
+
+exit 0;
+
+
+sub try_use { eval "use @_;"; return $@ eq '' }
+
+sub mydiff
+{
+  local *F = shift;
+  my($file, $str) = @_;
+  my $diff;
+
+  if (exists $opt{diff}) {
+    $diff = run_diff($opt{diff}, $file, $str);
+  }
+
+  if (!defined $diff and try_use('Text::Diff')) {
+    $diff = Text::Diff::diff($file, \$str, { STYLE => 'Unified' });
+    $diff = <<HEADER . $diff;
+--- $file
++++ $file.patched
+HEADER
+  }
+
+  if (!defined $diff) {
+    $diff = run_diff('diff -u', $file, $str);
+  }
+
+  if (!defined $diff) {
+    $diff = run_diff('diff', $file, $str);
+  }
+
+  if (!defined $diff) {
+    error("Cannot generate a diff. Please install Text::Diff or use --copy.");
+    return;
+  }
+
+  print F $diff;
+}
+
+sub run_diff
+{
+  my($prog, $file, $str) = @_;
+  my $tmp = 'dppptemp';
+  my $suf = 'aaa';
+  my $diff = '';
+  local *F;
+
+  while (-e "$tmp.$suf") { $suf++ }
+  $tmp = "$tmp.$suf";
+
+  if (open F, ">$tmp") {
+    print F $str;
+    close F;
+
+    if (open F, "$prog $file $tmp |") {
+      while (<F>) {
+        s/\Q$tmp\E/$file.patched/;
+        $diff .= $_;
+      }
+      close F;
+      unlink $tmp;
+      return $diff;
+    }
+
+    unlink $tmp;
+  }
+  else {
+    error("Cannot open '$tmp' for writing: $!");
+  }
+
+  return undef;
+}
+
+sub rec_depend
+{
+  my($func, $seen) = @_;
+  return () unless exists $depends{$func};
+  $seen = {%{$seen||{}}};
+  return () if $seen->{$func}++;
+  my %s;
+  grep !$s{$_}++, map { ($_, rec_depend($_, $seen)) } @{$depends{$func}};
+}
+
+sub parse_version
+{
+  my $ver = shift;
+
+  if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) {
+    return ($1, $2, $3);
+  }
+  elsif ($ver !~ /^\d+\.[\d_]+$/) {
+    die "cannot parse version '$ver'\n";
+  }
+
+  $ver =~ s/_//g;
+  $ver =~ s/$/000000/;
+
+  my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/;
+
+  $v = int $v;
+  $s = int $s;
+
+  if ($r < 5 || ($r == 5 && $v < 6)) {
+    if ($s % 10) {
+      die "cannot parse version '$ver'\n";
+    }
+  }
+
+  return ($r, $v, $s);
+}
+
+sub format_version
+{
+  my $ver = shift;
+
+  $ver =~ s/$/000000/;
+  my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/;
+
+  $v = int $v;
+  $s = int $s;
+
+  if ($r < 5 || ($r == 5 && $v < 6)) {
+    if ($s % 10) {
+      die "invalid version '$ver'\n";
+    }
+    $s /= 10;
+
+    $ver = sprintf "%d.%03d", $r, $v;
+    $s > 0 and $ver .= sprintf "_%02d", $s;
+
+    return $ver;
+  }
+
+  return sprintf "%d.%d.%d", $r, $v, $s;
+}
+
+sub info
+{
+  $opt{quiet} and return;
+  print @_, "\n";
+}
+
+sub diag
+{
+  $opt{quiet} and return;
+  $opt{diag} and print @_, "\n";
+}
+
+sub warning
+{
+  $opt{quiet} and return;
+  print "*** ", @_, "\n";
+}
+
+sub error
+{
+  print "*** ERROR: ", @_, "\n";
+}
+
+my %given_hints;
+my %given_warnings;
+sub hint
+{
+  $opt{quiet} and return;
+  my $func = shift;
+  my $rv = 0;
+  if (exists $warnings{$func} && !$given_warnings{$func}++) {
+    my $warn = $warnings{$func};
+    $warn =~ s!^!*** !mg;
+    print "*** WARNING: $func\n", $warn;
+    $rv++;
+  }
+  if ($opt{hints} && exists $hints{$func} && !$given_hints{$func}++) {
+    my $hint = $hints{$func};
+    $hint =~ s/^/   /mg;
+    print "   --- hint for $func ---\n", $hint;
+  }
+  $rv;
+}
+
+sub usage
+{
+  my($usage) = do { local(@ARGV,$/)=($0); <> } =~ /^=head\d$HS+SYNOPSIS\s*^(.*?)\s*^=/ms;
+  my %M = ( 'I' => '*' );
+  $usage =~ s/^\s*perl\s+\S+/$^X $0/;
+  $usage =~ s/([A-Z])<([^>]+)>/$M{$1}$2$M{$1}/g;
+
+  print <<ENDUSAGE;
+
+Usage: $usage
+
+See perldoc $0 for details.
+
+ENDUSAGE
+
+  exit 2;
+}
+
+sub strip
+{
+  my $self = do { local(@ARGV,$/)=($0); <> };
+  my($copy) = $self =~ /^=head\d\s+COPYRIGHT\s*^(.*?)^=\w+/ms;
+  $copy =~ s/^(?=\S+)/    /gms;
+  $self =~ s/^$HS+Do NOT edit.*?(?=^-)/$copy/ms;
+  $self =~ s/^SKIP.*(?=^__DATA__)/SKIP
+if (\@ARGV && \$ARGV[0] eq '--unstrip') {
+  eval { require Devel::PPPort };
+  \$@ and die "Cannot require Devel::PPPort, please install.\\n";
+  if (eval \$Devel::PPPort::VERSION < $VERSION) {
+    die "$0 was originally generated with Devel::PPPort $VERSION.\\n"
+      . "Your Devel::PPPort is only version \$Devel::PPPort::VERSION.\\n"
+      . "Please install a newer version, or --unstrip will not work.\\n";
+  }
+  Devel::PPPort::WriteFile(\$0);
+  exit 0;
+}
+print <<END;
+
+Sorry, but this is a stripped version of \$0.
+
+To be able to use its original script and doc functionality,
+please try to regenerate this file using:
+
+  \$^X \$0 --unstrip
+
+END
+/ms;
+  my($pl, $c) = $self =~ /(.*^__DATA__)(.*)/ms;
+  $c =~ s{
+    / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*)
+  | ( "[^"\\]*(?:\\.[^"\\]*)*"
+    | '[^'\\]*(?:\\.[^'\\]*)*' )
+  | ($HS+) }{ defined $2 ? ' ' : ($1 || '') }gsex;
+  $c =~ s!\s+$!!mg;
+  $c =~ s!^$LF!!mg;
+  $c =~ s!^\s*#\s*!#!mg;
+  $c =~ s!^\s+!!mg;
+
+  open OUT, ">$0" or die "cannot strip $0: $!\n";
+  print OUT "$pl$c\n";
+
+  exit 0;
+}
+
+__DATA__
+*/
+
+#ifndef _P_P_PORTABILITY_H_
+#define _P_P_PORTABILITY_H_
+
+#ifndef DPPP_NAMESPACE
+#  define DPPP_NAMESPACE DPPP_
+#endif
+
+#define DPPP_CAT2(x,y) CAT2(x,y)
+#define DPPP_(name) DPPP_CAT2(DPPP_NAMESPACE, name)
+
+#ifndef PERL_REVISION
+#  if !defined(__PATCHLEVEL_H_INCLUDED__) && !(defined(PATCHLEVEL) && defined(SUBVERSION))
+#    define PERL_PATCHLEVEL_H_IMPLICIT
+#    include <patchlevel.h>
+#  endif
+#  if !(defined(PERL_VERSION) || (defined(SUBVERSION) && defined(PATCHLEVEL)))
+#    include <could_not_find_Perl_patchlevel.h>
+#  endif
+#  ifndef PERL_REVISION
+#    define PERL_REVISION       (5)
+     /* Replace: 1 */
+#    define PERL_VERSION        PATCHLEVEL
+#    define PERL_SUBVERSION     SUBVERSION
+     /* Replace PERL_PATCHLEVEL with PERL_VERSION */
+     /* Replace: 0 */
+#  endif
+#endif
+
+#define _dpppDEC2BCD(dec) ((((dec)/100)<<8)|((((dec)%100)/10)<<4)|((dec)%10))
+#define PERL_BCDVERSION ((_dpppDEC2BCD(PERL_REVISION)<<24)|(_dpppDEC2BCD(PERL_VERSION)<<12)|_dpppDEC2BCD(PERL_SUBVERSION))
+
+/* It is very unlikely that anyone will try to use this with Perl 6
+   (or greater), but who knows.
+ */
+#if PERL_REVISION != 5
+#  error ppport.h only works with Perl version 5
+#endif /* PERL_REVISION != 5 */
+#ifndef dTHR
+#  define dTHR                           dNOOP
+#endif
+#ifndef dTHX
+#  define dTHX                           dNOOP
+#endif
+
+#ifndef dTHXa
+#  define dTHXa(x)                       dNOOP
+#endif
+#ifndef pTHX
+#  define pTHX                           void
+#endif
+
+#ifndef pTHX_
+#  define pTHX_
+#endif
+
+#ifndef aTHX
+#  define aTHX
+#endif
+
+#ifndef aTHX_
+#  define aTHX_
+#endif
+
+#if (PERL_BCDVERSION < 0x5006000)
+#  ifdef USE_THREADS
+#    define aTHXR  thr
+#    define aTHXR_ thr,
+#  else
+#    define aTHXR
+#    define aTHXR_
+#  endif
+#  define dTHXR  dTHR
+#else
+#  define aTHXR  aTHX
+#  define aTHXR_ aTHX_
+#  define dTHXR  dTHX
+#endif
+#ifndef dTHXoa
+#  define dTHXoa(x)                      dTHXa(x)
+#endif
+
+#ifdef I_LIMITS
+#  include <limits.h>
+#endif
+
+#ifndef PERL_UCHAR_MIN
+#  define PERL_UCHAR_MIN ((unsigned char)0)
+#endif
+
+#ifndef PERL_UCHAR_MAX
+#  ifdef UCHAR_MAX
+#    define PERL_UCHAR_MAX ((unsigned char)UCHAR_MAX)
+#  else
+#    ifdef MAXUCHAR
+#      define PERL_UCHAR_MAX ((unsigned char)MAXUCHAR)
+#    else
+#      define PERL_UCHAR_MAX ((unsigned char)~(unsigned)0)
+#    endif
+#  endif
+#endif
+
+#ifndef PERL_USHORT_MIN
+#  define PERL_USHORT_MIN ((unsigned short)0)
+#endif
+
+#ifndef PERL_USHORT_MAX
+#  ifdef USHORT_MAX
+#    define PERL_USHORT_MAX ((unsigned short)USHORT_MAX)
+#  else
+#    ifdef MAXUSHORT
+#      define PERL_USHORT_MAX ((unsigned short)MAXUSHORT)
+#    else
+#      ifdef USHRT_MAX
+#        define PERL_USHORT_MAX ((unsigned short)USHRT_MAX)
+#      else
+#        define PERL_USHORT_MAX ((unsigned short)~(unsigned)0)
+#      endif
+#    endif
+#  endif
+#endif
+
+#ifndef PERL_SHORT_MAX
+#  ifdef SHORT_MAX
+#    define PERL_SHORT_MAX ((short)SHORT_MAX)
+#  else
+#    ifdef MAXSHORT    /* Often used in <values.h> */
+#      define PERL_SHORT_MAX ((short)MAXSHORT)
+#    else
+#      ifdef SHRT_MAX
+#        define PERL_SHORT_MAX ((short)SHRT_MAX)
+#      else
+#        define PERL_SHORT_MAX ((short) (PERL_USHORT_MAX >> 1))
+#      endif
+#    endif
+#  endif
+#endif
+
+#ifndef PERL_SHORT_MIN
+#  ifdef SHORT_MIN
+#    define PERL_SHORT_MIN ((short)SHORT_MIN)
+#  else
+#    ifdef MINSHORT
+#      define PERL_SHORT_MIN ((short)MINSHORT)
+#    else
+#      ifdef SHRT_MIN
+#        define PERL_SHORT_MIN ((short)SHRT_MIN)
+#      else
+#        define PERL_SHORT_MIN (-PERL_SHORT_MAX - ((3 & -1) == 3))
+#      endif
+#    endif
+#  endif
+#endif
+
+#ifndef PERL_UINT_MAX
+#  ifdef UINT_MAX
+#    define PERL_UINT_MAX ((unsigned int)UINT_MAX)
+#  else
+#    ifdef MAXUINT
+#      define PERL_UINT_MAX ((unsigned int)MAXUINT)
+#    else
+#      define PERL_UINT_MAX (~(unsigned int)0)
+#    endif
+#  endif
+#endif
+
+#ifndef PERL_UINT_MIN
+#  define PERL_UINT_MIN ((unsigned int)0)
+#endif
+
+#ifndef PERL_INT_MAX
+#  ifdef INT_MAX
+#    define PERL_INT_MAX ((int)INT_MAX)
+#  else
+#    ifdef MAXINT    /* Often used in <values.h> */
+#      define PERL_INT_MAX ((int)MAXINT)
+#    else
+#      define PERL_INT_MAX ((int)(PERL_UINT_MAX >> 1))
+#    endif
+#  endif
+#endif
+
+#ifndef PERL_INT_MIN
+#  ifdef INT_MIN
+#    define PERL_INT_MIN ((int)INT_MIN)
+#  else
+#    ifdef MININT
+#      define PERL_INT_MIN ((int)MININT)
+#    else
+#      define PERL_INT_MIN (-PERL_INT_MAX - ((3 & -1) == 3))
+#    endif
+#  endif
+#endif
+
+#ifndef PERL_ULONG_MAX
+#  ifdef ULONG_MAX
+#    define PERL_ULONG_MAX ((unsigned long)ULONG_MAX)
+#  else
+#    ifdef MAXULONG
+#      define PERL_ULONG_MAX ((unsigned long)MAXULONG)
+#    else
+#      define PERL_ULONG_MAX (~(unsigned long)0)
+#    endif
+#  endif
+#endif
+
+#ifndef PERL_ULONG_MIN
+#  define PERL_ULONG_MIN ((unsigned long)0L)
+#endif
+
+#ifndef PERL_LONG_MAX
+#  ifdef LONG_MAX
+#    define PERL_LONG_MAX ((long)LONG_MAX)
+#  else
+#    ifdef MAXLONG
+#      define PERL_LONG_MAX ((long)MAXLONG)
+#    else
+#      define PERL_LONG_MAX ((long) (PERL_ULONG_MAX >> 1))
+#    endif
+#  endif
+#endif
+
+#ifndef PERL_LONG_MIN
+#  ifdef LONG_MIN
+#    define PERL_LONG_MIN ((long)LONG_MIN)
+#  else
+#    ifdef MINLONG
+#      define PERL_LONG_MIN ((long)MINLONG)
+#    else
+#      define PERL_LONG_MIN (-PERL_LONG_MAX - ((3 & -1) == 3))
+#    endif
+#  endif
+#endif
+
+#if defined(HAS_QUAD) && (defined(convex) || defined(uts))
+#  ifndef PERL_UQUAD_MAX
+#    ifdef ULONGLONG_MAX
+#      define PERL_UQUAD_MAX ((unsigned long long)ULONGLONG_MAX)
+#    else
+#      ifdef MAXULONGLONG
+#        define PERL_UQUAD_MAX ((unsigned long long)MAXULONGLONG)
+#      else
+#        define PERL_UQUAD_MAX (~(unsigned long long)0)
+#      endif
+#    endif
+#  endif
+
+#  ifndef PERL_UQUAD_MIN
+#    define PERL_UQUAD_MIN ((unsigned long long)0L)
+#  endif
+
+#  ifndef PERL_QUAD_MAX
+#    ifdef LONGLONG_MAX
+#      define PERL_QUAD_MAX ((long long)LONGLONG_MAX)
+#    else
+#      ifdef MAXLONGLONG
+#        define PERL_QUAD_MAX ((long long)MAXLONGLONG)
+#      else
+#        define PERL_QUAD_MAX ((long long) (PERL_UQUAD_MAX >> 1))
+#      endif
+#    endif
+#  endif
+
+#  ifndef PERL_QUAD_MIN
+#    ifdef LONGLONG_MIN
+#      define PERL_QUAD_MIN ((long long)LONGLONG_MIN)
+#    else
+#      ifdef MINLONGLONG
+#        define PERL_QUAD_MIN ((long long)MINLONGLONG)
+#      else
+#        define PERL_QUAD_MIN (-PERL_QUAD_MAX - ((3 & -1) == 3))
+#      endif
+#    endif
+#  endif
+#endif
+
+/* This is based on code from 5.003 perl.h */
+#ifdef HAS_QUAD
+#  ifdef cray
+#ifndef IVTYPE
+#  define IVTYPE                         int
+#endif
+
+#ifndef IV_MIN
+#  define IV_MIN                         PERL_INT_MIN
+#endif
+
+#ifndef IV_MAX
+#  define IV_MAX                         PERL_INT_MAX
+#endif
+
+#ifndef UV_MIN
+#  define UV_MIN                         PERL_UINT_MIN
+#endif
+
+#ifndef UV_MAX
+#  define UV_MAX                         PERL_UINT_MAX
+#endif
+
+#    ifdef INTSIZE
+#ifndef IVSIZE
+#  define IVSIZE                         INTSIZE
+#endif
+
+#    endif
+#  else
+#    if defined(convex) || defined(uts)
+#ifndef IVTYPE
+#  define IVTYPE                         long long
+#endif
+
+#ifndef IV_MIN
+#  define IV_MIN                         PERL_QUAD_MIN
+#endif
+
+#ifndef IV_MAX
+#  define IV_MAX                         PERL_QUAD_MAX
+#endif
+
+#ifndef UV_MIN
+#  define UV_MIN                         PERL_UQUAD_MIN
+#endif
+
+#ifndef UV_MAX
+#  define UV_MAX                         PERL_UQUAD_MAX
+#endif
+
+#      ifdef LONGLONGSIZE
+#ifndef IVSIZE
+#  define IVSIZE                         LONGLONGSIZE
+#endif
+
+#      endif
+#    else
+#ifndef IVTYPE
+#  define IVTYPE                         long
+#endif
+
+#ifndef IV_MIN
+#  define IV_MIN                         PERL_LONG_MIN
+#endif
+
+#ifndef IV_MAX
+#  define IV_MAX                         PERL_LONG_MAX
+#endif
+
+#ifndef UV_MIN
+#  define UV_MIN                         PERL_ULONG_MIN
+#endif
+
+#ifndef UV_MAX
+#  define UV_MAX                         PERL_ULONG_MAX
+#endif
+
+#      ifdef LONGSIZE
+#ifndef IVSIZE
+#  define IVSIZE                         LONGSIZE
+#endif
+
+#      endif
+#    endif
+#  endif
+#ifndef IVSIZE
+#  define IVSIZE                         8
+#endif
+
+#ifndef PERL_QUAD_MIN
+#  define PERL_QUAD_MIN                  IV_MIN
+#endif
+
+#ifndef PERL_QUAD_MAX
+#  define PERL_QUAD_MAX                  IV_MAX
+#endif
+
+#ifndef PERL_UQUAD_MIN
+#  define PERL_UQUAD_MIN                 UV_MIN
+#endif
+
+#ifndef PERL_UQUAD_MAX
+#  define PERL_UQUAD_MAX                 UV_MAX
+#endif
+
+#else
+#ifndef IVTYPE
+#  define IVTYPE                         long
+#endif
+
+#ifndef IV_MIN
+#  define IV_MIN                         PERL_LONG_MIN
+#endif
+
+#ifndef IV_MAX
+#  define IV_MAX                         PERL_LONG_MAX
+#endif
+
+#ifndef UV_MIN
+#  define UV_MIN                         PERL_ULONG_MIN
+#endif
+
+#ifndef UV_MAX
+#  define UV_MAX                         PERL_ULONG_MAX
+#endif
+
+#endif
+
+#ifndef IVSIZE
+#  ifdef LONGSIZE
+#    define IVSIZE LONGSIZE
+#  else
+#    define IVSIZE 4 /* A bold guess, but the best we can make. */
+#  endif
+#endif
+#ifndef UVTYPE
+#  define UVTYPE                         unsigned IVTYPE
+#endif
+
+#ifndef UVSIZE
+#  define UVSIZE                         IVSIZE
+#endif
+#ifndef sv_setuv
+#  define sv_setuv(sv, uv)               \
+               STMT_START {                         \
+                 UV TeMpUv = uv;                    \
+                 if (TeMpUv <= IV_MAX)              \
+                   sv_setiv(sv, TeMpUv);            \
+                 else                               \
+                   sv_setnv(sv, (double)TeMpUv);    \
+               } STMT_END
+#endif
+#ifndef newSVuv
+#  define newSVuv(uv)                    ((uv) <= IV_MAX ? newSViv((IV)uv) : newSVnv((NV)uv))
+#endif
+#ifndef sv_2uv
+#  define sv_2uv(sv)                     ((PL_Sv = (sv)), (UV) (SvNOK(PL_Sv) ? SvNV(PL_Sv) : sv_2nv(PL_Sv)))
+#endif
+
+#ifndef SvUVX
+#  define SvUVX(sv)                      ((UV)SvIVX(sv))
+#endif
+
+#ifndef SvUVXx
+#  define SvUVXx(sv)                     SvUVX(sv)
+#endif
+
+#ifndef SvUV
+#  define SvUV(sv)                       (SvIOK(sv) ? SvUVX(sv) : sv_2uv(sv))
+#endif
+
+#ifndef SvUVx
+#  define SvUVx(sv)                      ((PL_Sv = (sv)), SvUV(PL_Sv))
+#endif
+
+/* Hint: sv_uv
+ * Always use the SvUVx() macro instead of sv_uv().
+ */
+#ifndef sv_uv
+#  define sv_uv(sv)                      SvUVx(sv)
+#endif
+
+#if !defined(SvUOK) && defined(SvIOK_UV)
+#  define SvUOK(sv) SvIOK_UV(sv)
+#endif
+#ifndef XST_mUV
+#  define XST_mUV(i,v)                   (ST(i) = sv_2mortal(newSVuv(v))  )
+#endif
+
+#ifndef XSRETURN_UV
+#  define XSRETURN_UV(v)                 STMT_START { XST_mUV(0,v);  XSRETURN(1); } STMT_END
+#endif
+#ifndef PUSHu
+#  define PUSHu(u)                       STMT_START { sv_setuv(TARG, (UV)(u)); PUSHTARG;  } STMT_END
+#endif
+
+#ifndef XPUSHu
+#  define XPUSHu(u)                      STMT_START { sv_setuv(TARG, (UV)(u)); XPUSHTARG; } STMT_END
+#endif
+
+#ifdef HAS_MEMCMP
+#ifndef memNE
+#  define memNE(s1,s2,l)                 (memcmp(s1,s2,l))
+#endif
+
+#ifndef memEQ
+#  define memEQ(s1,s2,l)                 (!memcmp(s1,s2,l))
+#endif
+
+#else
+#ifndef memNE
+#  define memNE(s1,s2,l)                 (bcmp(s1,s2,l))
+#endif
+
+#ifndef memEQ
+#  define memEQ(s1,s2,l)                 (!bcmp(s1,s2,l))
+#endif
+
+#endif
+#ifndef MoveD
+#  define MoveD(s,d,n,t)                 memmove((char*)(d),(char*)(s), (n) * sizeof(t))
+#endif
+
+#ifndef CopyD
+#  define CopyD(s,d,n,t)                 memcpy((char*)(d),(char*)(s), (n) * sizeof(t))
+#endif
+
+#ifdef HAS_MEMSET
+#ifndef ZeroD
+#  define ZeroD(d,n,t)                   memzero((char*)(d), (n) * sizeof(t))
+#endif
+
+#else
+#ifndef ZeroD
+#  define ZeroD(d,n,t)                   ((void)memzero((char*)(d), (n) * sizeof(t)), d)
+#endif
+
+#endif
+#ifndef PoisonWith
+#  define PoisonWith(d,n,t,b)            (void)memset((char*)(d), (U8)(b), (n) * sizeof(t))
+#endif
+
+#ifndef PoisonNew
+#  define PoisonNew(d,n,t)               PoisonWith(d,n,t,0xAB)
+#endif
+
+#ifndef PoisonFree
+#  define PoisonFree(d,n,t)              PoisonWith(d,n,t,0xEF)
+#endif
+
+#ifndef Poison
+#  define Poison(d,n,t)                  PoisonFree(d,n,t)
+#endif
+#ifndef Newx
+#  define Newx(v,n,t)                    New(0,v,n,t)
+#endif
+
+#ifndef Newxc
+#  define Newxc(v,n,t,c)                 Newc(0,v,n,t,c)
+#endif
+
+#ifndef Newxz
+#  define Newxz(v,n,t)                   Newz(0,v,n,t)
+#endif
+
+#ifndef PERL_UNUSED_DECL
+#  ifdef HASATTRIBUTE
+#    if (defined(__GNUC__) && defined(__cplusplus)) || defined(__INTEL_COMPILER)
+#      define PERL_UNUSED_DECL
+#    else
+#      define PERL_UNUSED_DECL __attribute__((unused))
+#    endif
+#  else
+#    define PERL_UNUSED_DECL
+#  endif
+#endif
+
+#ifndef PERL_UNUSED_ARG
+#  if defined(lint) && defined(S_SPLINT_S) /* www.splint.org */
+#    include <note.h>
+#    define PERL_UNUSED_ARG(x) NOTE(ARGUNUSED(x))
+#  else
+#    define PERL_UNUSED_ARG(x) ((void)x)
+#  endif
+#endif
+
+#ifndef PERL_UNUSED_VAR
+#  define PERL_UNUSED_VAR(x) ((void)x)
+#endif
+
+#ifndef PERL_UNUSED_CONTEXT
+#  ifdef USE_ITHREADS
+#    define PERL_UNUSED_CONTEXT PERL_UNUSED_ARG(my_perl)
+#  else
+#    define PERL_UNUSED_CONTEXT
+#  endif
+#endif
+#ifndef NOOP
+#  define NOOP                           /*EMPTY*/(void)0
+#endif
+
+#ifndef dNOOP
+#  define dNOOP                          extern int /*@unused@*/ Perl___notused PERL_UNUSED_DECL
+#endif
+
+#ifndef NVTYPE
+#  if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE)
+#    define NVTYPE long double
+#  else
+#    define NVTYPE double
+#  endif
+typedef NVTYPE NV;
+#endif
+
+#ifndef INT2PTR
+#  if (IVSIZE == PTRSIZE) && (UVSIZE == PTRSIZE)
+#    define PTRV                  UV
+#    define INT2PTR(any,d)        (any)(d)
+#  else
+#    if PTRSIZE == LONGSIZE
+#      define PTRV                unsigned long
+#    else
+#      define PTRV                unsigned
+#    endif
+#    define INT2PTR(any,d)        (any)(PTRV)(d)
+#  endif
+#endif
+
+#ifndef PTR2ul
+#  if PTRSIZE == LONGSIZE
+#    define PTR2ul(p)     (unsigned long)(p)
+#  else
+#    define PTR2ul(p)     INT2PTR(unsigned long,p)
+#  endif
+#endif
+#ifndef PTR2nat
+#  define PTR2nat(p)                     (PTRV)(p)
+#endif
+
+#ifndef NUM2PTR
+#  define NUM2PTR(any,d)                 (any)PTR2nat(d)
+#endif
+
+#ifndef PTR2IV
+#  define PTR2IV(p)                      INT2PTR(IV,p)
+#endif
+
+#ifndef PTR2UV
+#  define PTR2UV(p)                      INT2PTR(UV,p)
+#endif
+
+#ifndef PTR2NV
+#  define PTR2NV(p)                      NUM2PTR(NV,p)
+#endif
+
+#undef START_EXTERN_C
+#undef END_EXTERN_C
+#undef EXTERN_C
+#ifdef __cplusplus
+#  define START_EXTERN_C extern "C" {
+#  define END_EXTERN_C }
+#  define EXTERN_C extern "C"
+#else
+#  define START_EXTERN_C
+#  define END_EXTERN_C
+#  define EXTERN_C extern
+#endif
+
+#if defined(PERL_GCC_PEDANTIC)
+#  ifndef PERL_GCC_BRACE_GROUPS_FORBIDDEN
+#    define PERL_GCC_BRACE_GROUPS_FORBIDDEN
+#  endif
+#endif
+
+#if defined(__GNUC__) && !defined(PERL_GCC_BRACE_GROUPS_FORBIDDEN) && !defined(__cplusplus)
+#  ifndef PERL_USE_GCC_BRACE_GROUPS
+#    define PERL_USE_GCC_BRACE_GROUPS
+#  endif
+#endif
+
+#undef STMT_START
+#undef STMT_END
+#ifdef PERL_USE_GCC_BRACE_GROUPS
+#  define STMT_START   (void)( /* gcc supports ``({ STATEMENTS; })'' */
+#  define STMT_END     )
+#else
+#  if defined(VOIDFLAGS) && (VOIDFLAGS) && (defined(sun) || defined(__sun__)) && !defined(__GNUC__)
+#    define STMT_START if (1)
+#    define STMT_END   else (void)0
+#  else
+#    define STMT_START do
+#    define STMT_END   while (0)
+#  endif
+#endif
+#ifndef boolSV
+#  define boolSV(b)                      ((b) ? &PL_sv_yes : &PL_sv_no)
+#endif
+
+/* DEFSV appears first in 5.004_56 */
+#ifndef DEFSV
+#  define DEFSV                          GvSV(PL_defgv)
+#endif
+
+#ifndef SAVE_DEFSV
+#  define SAVE_DEFSV                     SAVESPTR(GvSV(PL_defgv))
+#endif
+
+#ifndef DEFSV_set
+#  define DEFSV_set(sv)                  (DEFSV = (sv))
+#endif
+
+/* Older perls (<=5.003) lack AvFILLp */
+#ifndef AvFILLp
+#  define AvFILLp                        AvFILL
+#endif
+#ifndef ERRSV
+#  define ERRSV                          get_sv("@",FALSE)
+#endif
+
+/* Hint: gv_stashpvn
+ * This function's backport doesn't support the length parameter, but
+ * rather ignores it. Portability can only be ensured if the length
+ * parameter is used for speed reasons, but the length can always be
+ * correctly computed from the string argument.
+ */
+#ifndef gv_stashpvn
+#  define gv_stashpvn(str,len,create)    gv_stashpv(str,create)
+#endif
+
+/* Replace: 1 */
+#ifndef get_cv
+#  define get_cv                         perl_get_cv
+#endif
+
+#ifndef get_sv
+#  define get_sv                         perl_get_sv
+#endif
+
+#ifndef get_av
+#  define get_av                         perl_get_av
+#endif
+
+#ifndef get_hv
+#  define get_hv                         perl_get_hv
+#endif
+
+/* Replace: 0 */
+#ifndef dUNDERBAR
+#  define dUNDERBAR                      dNOOP
+#endif
+
+#ifndef UNDERBAR
+#  define UNDERBAR                       DEFSV
+#endif
+#ifndef dAX
+#  define dAX                            I32 ax = MARK - PL_stack_base + 1
+#endif
+
+#ifndef dITEMS
+#  define dITEMS                         I32 items = SP - MARK
+#endif
+#ifndef dXSTARG
+#  define dXSTARG                        SV * targ = sv_newmortal()
+#endif
+#ifndef dAXMARK
+#  define dAXMARK                        I32 ax = POPMARK; \
+                               register SV ** const mark = PL_stack_base + ax++
+#endif
+#ifndef XSprePUSH
+#  define XSprePUSH                      (sp = PL_stack_base + ax - 1)
+#endif
+
+#if (PERL_BCDVERSION < 0x5005000)
+#  undef XSRETURN
+#  define XSRETURN(off)                                   \
+      STMT_START {                                        \
+          PL_stack_sp = PL_stack_base + ax + ((off) - 1); \
+          return;                                         \
+      } STMT_END
+#endif
+#ifndef XSPROTO
+#  define XSPROTO(name)                  void name(pTHX_ CV* cv)
+#endif
+
+#ifndef SVfARG
+#  define SVfARG(p)                      ((void*)(p))
+#endif
+#ifndef PERL_ABS
+#  define PERL_ABS(x)                    ((x) < 0 ? -(x) : (x))
+#endif
+#ifndef dVAR
+#  define dVAR                           dNOOP
+#endif
+#ifndef SVf
+#  define SVf                            "_"
+#endif
+#ifndef UTF8_MAXBYTES
+#  define UTF8_MAXBYTES                  UTF8_MAXLEN
+#endif
+#ifndef CPERLscope
+#  define CPERLscope(x)                  x
+#endif
+#ifndef PERL_HASH
+#  define PERL_HASH(hash,str,len)        \
+     STMT_START        { \
+       const char *s_PeRlHaSh = str; \
+       I32 i_PeRlHaSh = len; \
+       U32 hash_PeRlHaSh = 0; \
+       while (i_PeRlHaSh--) \
+           hash_PeRlHaSh = hash_PeRlHaSh * 33 + *s_PeRlHaSh++; \
+       (hash) = hash_PeRlHaSh; \
+    } STMT_END
+#endif
+
+#ifndef PERLIO_FUNCS_DECL
+# ifdef PERLIO_FUNCS_CONST
+#  define PERLIO_FUNCS_DECL(funcs) const PerlIO_funcs funcs
+#  define PERLIO_FUNCS_CAST(funcs) (PerlIO_funcs*)(funcs)
+# else
+#  define PERLIO_FUNCS_DECL(funcs) PerlIO_funcs funcs
+#  define PERLIO_FUNCS_CAST(funcs) (funcs)
+# endif
+#endif
+
+/* provide these typedefs for older perls */
+#if (PERL_BCDVERSION < 0x5009003)
+
+# ifdef ARGSproto
+typedef OP* (CPERLscope(*Perl_ppaddr_t))(ARGSproto);
+# else
+typedef OP* (CPERLscope(*Perl_ppaddr_t))(pTHX);
+# endif
+
+typedef OP* (CPERLscope(*Perl_check_t)) (pTHX_ OP*);
+
+#endif
+#ifndef isPSXSPC
+#  define isPSXSPC(c)                    (isSPACE(c) || (c) == '\v')
+#endif
+
+#ifndef isBLANK
+#  define isBLANK(c)                     ((c) == ' ' || (c) == '\t')
+#endif
+
+#ifdef EBCDIC
+#ifndef isALNUMC
+#  define isALNUMC(c)                    isalnum(c)
+#endif
+
+#ifndef isASCII
+#  define isASCII(c)                     isascii(c)
+#endif
+
+#ifndef isCNTRL
+#  define isCNTRL(c)                     iscntrl(c)
+#endif
+
+#ifndef isGRAPH
+#  define isGRAPH(c)                     isgraph(c)
+#endif
+
+#ifndef isPRINT
+#  define isPRINT(c)                     isprint(c)
+#endif
+
+#ifndef isPUNCT
+#  define isPUNCT(c)                     ispunct(c)
+#endif
+
+#ifndef isXDIGIT
+#  define isXDIGIT(c)                    isxdigit(c)
+#endif
+
+#else
+# if (PERL_BCDVERSION < 0x5010000)
+/* Hint: isPRINT
+ * The implementation in older perl versions includes all of the
+ * isSPACE() characters, which is wrong. The version provided by
+ * Devel::PPPort always overrides a present buggy version.
+ */
+#  undef isPRINT
+# endif
+#ifndef isALNUMC
+#  define isALNUMC(c)                    (isALPHA(c) || isDIGIT(c))
+#endif
+
+#ifndef isASCII
+#  define isASCII(c)                     ((c) <= 127)
+#endif
+
+#ifndef isCNTRL
+#  define isCNTRL(c)                     ((c) < ' ' || (c) == 127)
+#endif
+
+#ifndef isGRAPH
+#  define isGRAPH(c)                     (isALNUM(c) || isPUNCT(c))
+#endif
+
+#ifndef isPRINT
+#  define isPRINT(c)                     (((c) >= 32 && (c) < 127))
+#endif
+
+#ifndef isPUNCT
+#  define isPUNCT(c)                     (((c) >= 33 && (c) <= 47) || ((c) >= 58 && (c) <= 64)  || ((c) >= 91 && (c) <= 96) || ((c) >= 123 && (c) <= 126))
+#endif
+
+#ifndef isXDIGIT
+#  define isXDIGIT(c)                    (isDIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
+#endif
+
+#endif
+
+#ifndef PERL_SIGNALS_UNSAFE_FLAG
+
+#define PERL_SIGNALS_UNSAFE_FLAG 0x0001
+
+#if (PERL_BCDVERSION < 0x5008000)
+#  define D_PPP_PERL_SIGNALS_INIT   PERL_SIGNALS_UNSAFE_FLAG
+#else
+#  define D_PPP_PERL_SIGNALS_INIT   0
+#endif
+
+#if defined(NEED_PL_signals)
+static U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT;
+#elif defined(NEED_PL_signals_GLOBAL)
+U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT;
+#else
+extern U32 DPPP_(my_PL_signals);
+#endif
+#define PL_signals DPPP_(my_PL_signals)
+
+#endif
+
+/* Hint: PL_ppaddr
+ * Calling an op via PL_ppaddr requires passing a context argument
+ * for threaded builds. Since the context argument is different for
+ * 5.005 perls, you can use aTHXR (supplied by ppport.h), which will
+ * automatically be defined as the correct argument.
+ */
+
+#if (PERL_BCDVERSION <= 0x5005005)
+/* Replace: 1 */
+#  define PL_ppaddr                 ppaddr
+#  define PL_no_modify              no_modify
+/* Replace: 0 */
+#endif
+
+#if (PERL_BCDVERSION <= 0x5004005)
+/* Replace: 1 */
+#  define PL_DBsignal               DBsignal
+#  define PL_DBsingle               DBsingle
+#  define PL_DBsub                  DBsub
+#  define PL_DBtrace                DBtrace
+#  define PL_Sv                     Sv
+#  define PL_bufend                 bufend
+#  define PL_bufptr                 bufptr
+#  define PL_compiling              compiling
+#  define PL_copline                copline
+#  define PL_curcop                 curcop
+#  define PL_curstash               curstash
+#  define PL_debstash               debstash
+#  define PL_defgv                  defgv
+#  define PL_diehook                diehook
+#  define PL_dirty                  dirty
+#  define PL_dowarn                 dowarn
+#  define PL_errgv                  errgv
+#  define PL_error_count            error_count
+#  define PL_expect                 expect
+#  define PL_hexdigit               hexdigit
+#  define PL_hints                  hints
+#  define PL_in_my                  in_my
+#  define PL_laststatval            laststatval
+#  define PL_lex_state              lex_state
+#  define PL_lex_stuff              lex_stuff
+#  define PL_linestr                linestr
+#  define PL_na                     na
+#  define PL_perl_destruct_level    perl_destruct_level
+#  define PL_perldb                 perldb
+#  define PL_rsfp_filters           rsfp_filters
+#  define PL_rsfp                   rsfp
+#  define PL_stack_base             stack_base
+#  define PL_stack_sp               stack_sp
+#  define PL_statcache              statcache
+#  define PL_stdingv                stdingv
+#  define PL_sv_arenaroot           sv_arenaroot
+#  define PL_sv_no                  sv_no
+#  define PL_sv_undef               sv_undef
+#  define PL_sv_yes                 sv_yes
+#  define PL_tainted                tainted
+#  define PL_tainting               tainting
+#  define PL_tokenbuf               tokenbuf
+/* Replace: 0 */
+#endif
+
+/* Warning: PL_parser
+ * For perl versions earlier than 5.9.5, this is an always
+ * non-NULL dummy. Also, it cannot be dereferenced. Don't
+ * use it if you can avoid is and unless you absolutely know
+ * what you're doing.
+ * If you always check that PL_parser is non-NULL, you can
+ * define DPPP_PL_parser_NO_DUMMY to avoid the creation of
+ * a dummy parser structure.
+ */
+
+#if (PERL_BCDVERSION >= 0x5009005)
+# ifdef DPPP_PL_parser_NO_DUMMY
+#  define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \
+                (croak("panic: PL_parser == NULL in %s:%d", \
+                       __FILE__, __LINE__), (yy_parser *) NULL))->var)
+# else
+#  ifdef DPPP_PL_parser_NO_DUMMY_WARNING
+#   define D_PPP_parser_dummy_warning(var)
+#  else
+#   define D_PPP_parser_dummy_warning(var) \
+             warn("warning: dummy PL_" #var " used in %s:%d", __FILE__, __LINE__),
+#  endif
+#  define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \
+                (D_PPP_parser_dummy_warning(var) &DPPP_(dummy_PL_parser)))->var)
+#if defined(NEED_PL_parser)
+static yy_parser DPPP_(dummy_PL_parser);
+#elif defined(NEED_PL_parser_GLOBAL)
+yy_parser DPPP_(dummy_PL_parser);
+#else
+extern yy_parser DPPP_(dummy_PL_parser);
+#endif
+
+# endif
+
+/* PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf depends on PL_parser */
+/* Warning: PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf
+ * Do not use this variable unless you know exactly what you're
+ * doint. It is internal to the perl parser and may change or even
+ * be removed in the future. As of perl 5.9.5, you have to check
+ * for (PL_parser != NULL) for this variable to have any effect.
+ * An always non-NULL PL_parser dummy is provided for earlier
+ * perl versions.
+ * If PL_parser is NULL when you try to access this variable, a
+ * dummy is being accessed instead and a warning is issued unless
+ * you define DPPP_PL_parser_NO_DUMMY_WARNING.
+ * If DPPP_PL_parser_NO_DUMMY is defined, the code trying to access
+ * this variable will croak with a panic message.
+ */
+
+# define PL_expect         D_PPP_my_PL_parser_var(expect)
+# define PL_copline        D_PPP_my_PL_parser_var(copline)
+# define PL_rsfp           D_PPP_my_PL_parser_var(rsfp)
+# define PL_rsfp_filters   D_PPP_my_PL_parser_var(rsfp_filters)
+# define PL_linestr        D_PPP_my_PL_parser_var(linestr)
+# define PL_bufptr         D_PPP_my_PL_parser_var(bufptr)
+# define PL_bufend         D_PPP_my_PL_parser_var(bufend)
+# define PL_lex_state      D_PPP_my_PL_parser_var(lex_state)
+# define PL_lex_stuff      D_PPP_my_PL_parser_var(lex_stuff)
+# define PL_tokenbuf       D_PPP_my_PL_parser_var(tokenbuf)
+# define PL_in_my          D_PPP_my_PL_parser_var(in_my)
+# define PL_in_my_stash    D_PPP_my_PL_parser_var(in_my_stash)
+# define PL_error_count    D_PPP_my_PL_parser_var(error_count)
+
+
+#else
+
+/* ensure that PL_parser != NULL and cannot be dereferenced */
+# define PL_parser         ((void *) 1)
+
+#endif
+#ifndef mPUSHs
+#  define mPUSHs(s)                      PUSHs(sv_2mortal(s))
+#endif
+
+#ifndef PUSHmortal
+#  define PUSHmortal                     PUSHs(sv_newmortal())
+#endif
+
+#ifndef mPUSHp
+#  define mPUSHp(p,l)                    sv_setpvn(PUSHmortal, (p), (l))
+#endif
+
+#ifndef mPUSHn
+#  define mPUSHn(n)                      sv_setnv(PUSHmortal, (NV)(n))
+#endif
+
+#ifndef mPUSHi
+#  define mPUSHi(i)                      sv_setiv(PUSHmortal, (IV)(i))
+#endif
+
+#ifndef mPUSHu
+#  define mPUSHu(u)                      sv_setuv(PUSHmortal, (UV)(u))
+#endif
+#ifndef mXPUSHs
+#  define mXPUSHs(s)                     XPUSHs(sv_2mortal(s))
+#endif
+
+#ifndef XPUSHmortal
+#  define XPUSHmortal                    XPUSHs(sv_newmortal())
+#endif
+
+#ifndef mXPUSHp
+#  define mXPUSHp(p,l)                   STMT_START { EXTEND(sp,1); sv_setpvn(PUSHmortal, (p), (l)); } STMT_END
+#endif
+
+#ifndef mXPUSHn
+#  define mXPUSHn(n)                     STMT_START { EXTEND(sp,1); sv_setnv(PUSHmortal, (NV)(n)); } STMT_END
+#endif
+
+#ifndef mXPUSHi
+#  define mXPUSHi(i)                     STMT_START { EXTEND(sp,1); sv_setiv(PUSHmortal, (IV)(i)); } STMT_END
+#endif
+
+#ifndef mXPUSHu
+#  define mXPUSHu(u)                     STMT_START { EXTEND(sp,1); sv_setuv(PUSHmortal, (UV)(u)); } STMT_END
+#endif
+
+/* Replace: 1 */
+#ifndef call_sv
+#  define call_sv                        perl_call_sv
+#endif
+
+#ifndef call_pv
+#  define call_pv                        perl_call_pv
+#endif
+
+#ifndef call_argv
+#  define call_argv                      perl_call_argv
+#endif
+
+#ifndef call_method
+#  define call_method                    perl_call_method
+#endif
+#ifndef eval_sv
+#  define eval_sv                        perl_eval_sv
+#endif
+
+/* Replace: 0 */
+#ifndef PERL_LOADMOD_DENY
+#  define PERL_LOADMOD_DENY              0x1
+#endif
+
+#ifndef PERL_LOADMOD_NOIMPORT
+#  define PERL_LOADMOD_NOIMPORT          0x2
+#endif
+
+#ifndef PERL_LOADMOD_IMPORT_OPS
+#  define PERL_LOADMOD_IMPORT_OPS        0x4
+#endif
+
+#ifndef G_METHOD
+# define G_METHOD              64
+# ifdef call_sv
+#  undef call_sv
+# endif
+# if (PERL_BCDVERSION < 0x5006000)
+#  define call_sv(sv, flags)  ((flags) & G_METHOD ? perl_call_method((char *) SvPV_nolen_const(sv), \
+                               (flags) & ~G_METHOD) : perl_call_sv(sv, flags))
+# else
+#  define call_sv(sv, flags)  ((flags) & G_METHOD ? Perl_call_method(aTHX_ (char *) SvPV_nolen_const(sv), \
+                               (flags) & ~G_METHOD) : Perl_call_sv(aTHX_ sv, flags))
+# endif
+#endif
+
+/* Replace perl_eval_pv with eval_pv */
+
+#ifndef eval_pv
+#if defined(NEED_eval_pv)
+static SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error);
+static
+#else
+extern SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error);
+#endif
+
+#ifdef eval_pv
+#  undef eval_pv
+#endif
+#define eval_pv(a,b) DPPP_(my_eval_pv)(aTHX_ a,b)
+#define Perl_eval_pv DPPP_(my_eval_pv)
+
+#if defined(NEED_eval_pv) || defined(NEED_eval_pv_GLOBAL)
+
+SV*
+DPPP_(my_eval_pv)(char *p, I32 croak_on_error)
+{
+    dSP;
+    SV* sv = newSVpv(p, 0);
+
+    PUSHMARK(sp);
+    eval_sv(sv, G_SCALAR);
+    SvREFCNT_dec(sv);
+
+    SPAGAIN;
+    sv = POPs;
+    PUTBACK;
+
+    if (croak_on_error && SvTRUE(GvSV(errgv)))
+       croak(SvPVx(GvSV(errgv), na));
+
+    return sv;
+}
+
+#endif
+#endif
+
+#ifndef vload_module
+#if defined(NEED_vload_module)
+static void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args);
+static
+#else
+extern void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args);
+#endif
+
+#ifdef vload_module
+#  undef vload_module
+#endif
+#define vload_module(a,b,c,d) DPPP_(my_vload_module)(aTHX_ a,b,c,d)
+#define Perl_vload_module DPPP_(my_vload_module)
+
+#if defined(NEED_vload_module) || defined(NEED_vload_module_GLOBAL)
+
+void
+DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args)
+{
+    dTHR;
+    dVAR;
+    OP *veop, *imop;
+
+    OP * const modname = newSVOP(OP_CONST, 0, name);
+    /* 5.005 has a somewhat hacky force_normal that doesn't croak on
+       SvREADONLY() if PL_compling is true. Current perls take care in
+       ck_require() to correctly turn off SvREADONLY before calling
+       force_normal_flags(). This seems a better fix than fudging PL_compling
+     */
+    SvREADONLY_off(((SVOP*)modname)->op_sv);
+    modname->op_private |= OPpCONST_BARE;
+    if (ver) {
+       veop = newSVOP(OP_CONST, 0, ver);
+    }
+    else
+       veop = NULL;
+    if (flags & PERL_LOADMOD_NOIMPORT) {
+       imop = sawparens(newNULLLIST());
+    }
+    else if (flags & PERL_LOADMOD_IMPORT_OPS) {
+       imop = va_arg(*args, OP*);
+    }
+    else {
+       SV *sv;
+       imop = NULL;
+       sv = va_arg(*args, SV*);
+       while (sv) {
+           imop = append_elem(OP_LIST, imop, newSVOP(OP_CONST, 0, sv));
+           sv = va_arg(*args, SV*);
+       }
+    }
+    {
+       const line_t ocopline = PL_copline;
+       COP * const ocurcop = PL_curcop;
+       const int oexpect = PL_expect;
+
+#if (PERL_BCDVERSION >= 0x5004000)
+       utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(FALSE, 0),
+               veop, modname, imop);
+#else
+       utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(),
+               modname, imop);
+#endif
+       PL_expect = oexpect;
+       PL_copline = ocopline;
+       PL_curcop = ocurcop;
+    }
+}
+
+#endif
+#endif
+
+#ifndef load_module
+#if defined(NEED_load_module)
+static void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...);
+static
+#else
+extern void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...);
+#endif
+
+#ifdef load_module
+#  undef load_module
+#endif
+#define load_module DPPP_(my_load_module)
+#define Perl_load_module DPPP_(my_load_module)
+
+#if defined(NEED_load_module) || defined(NEED_load_module_GLOBAL)
+
+void
+DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...)
+{
+    va_list args;
+    va_start(args, ver);
+    vload_module(flags, name, ver, &args);
+    va_end(args);
+}
+
+#endif
+#endif
+#ifndef newRV_inc
+#  define newRV_inc(sv)                  newRV(sv)   /* Replace */
+#endif
+
+#ifndef newRV_noinc
+#if defined(NEED_newRV_noinc)
+static SV * DPPP_(my_newRV_noinc)(SV *sv);
+static
+#else
+extern SV * DPPP_(my_newRV_noinc)(SV *sv);
+#endif
+
+#ifdef newRV_noinc
+#  undef newRV_noinc
+#endif
+#define newRV_noinc(a) DPPP_(my_newRV_noinc)(aTHX_ a)
+#define Perl_newRV_noinc DPPP_(my_newRV_noinc)
+
+#if defined(NEED_newRV_noinc) || defined(NEED_newRV_noinc_GLOBAL)
+SV *
+DPPP_(my_newRV_noinc)(SV *sv)
+{
+  SV *rv = (SV *)newRV(sv);
+  SvREFCNT_dec(sv);
+  return rv;
+}
+#endif
+#endif
+
+/* Hint: newCONSTSUB
+ * Returns a CV* as of perl-5.7.1. This return value is not supported
+ * by Devel::PPPort.
+ */
+
+/* newCONSTSUB from IO.xs is in the core starting with 5.004_63 */
+#if (PERL_BCDVERSION < 0x5004063) && (PERL_BCDVERSION != 0x5004005)
+#if defined(NEED_newCONSTSUB)
+static void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv);
+static
+#else
+extern void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv);
+#endif
+
+#ifdef newCONSTSUB
+#  undef newCONSTSUB
+#endif
+#define newCONSTSUB(a,b,c) DPPP_(my_newCONSTSUB)(aTHX_ a,b,c)
+#define Perl_newCONSTSUB DPPP_(my_newCONSTSUB)
+
+#if defined(NEED_newCONSTSUB) || defined(NEED_newCONSTSUB_GLOBAL)
+
+/* This is just a trick to avoid a dependency of newCONSTSUB on PL_parser */
+/* (There's no PL_parser in perl < 5.005, so this is completely safe)     */
+#define D_PPP_PL_copline PL_copline
+
+void
+DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv)
+{
+       U32 oldhints = PL_hints;
+       HV *old_cop_stash = PL_curcop->cop_stash;
+       HV *old_curstash = PL_curstash;
+       line_t oldline = PL_curcop->cop_line;
+       PL_curcop->cop_line = D_PPP_PL_copline;
+
+       PL_hints &= ~HINT_BLOCK_SCOPE;
+       if (stash)
+               PL_curstash = PL_curcop->cop_stash = stash;
+
+       newSUB(
+
+#if   (PERL_BCDVERSION < 0x5003022)
+               start_subparse(),
+#elif (PERL_BCDVERSION == 0x5003022)
+               start_subparse(0),
+#else  /* 5.003_23  onwards */
+               start_subparse(FALSE, 0),
+#endif
+
+               newSVOP(OP_CONST, 0, newSVpv((char *) name, 0)),
+               newSVOP(OP_CONST, 0, &PL_sv_no),   /* SvPV(&PL_sv_no) == "" -- GMB */
+               newSTATEOP(0, Nullch, newSVOP(OP_CONST, 0, sv))
+       );
+
+       PL_hints = oldhints;
+       PL_curcop->cop_stash = old_cop_stash;
+       PL_curstash = old_curstash;
+       PL_curcop->cop_line = oldline;
+}
+#endif
+#endif
+
+/*
+ * Boilerplate macros for initializing and accessing interpreter-local
+ * data from C.  All statics in extensions should be reworked to use
+ * this, if you want to make the extension thread-safe.  See ext/re/re.xs
+ * for an example of the use of these macros.
+ *
+ * Code that uses these macros is responsible for the following:
+ * 1. #define MY_CXT_KEY to a unique string, e.g. "DynaLoader_guts"
+ * 2. Declare a typedef named my_cxt_t that is a structure that contains
+ *    all the data that needs to be interpreter-local.
+ * 3. Use the START_MY_CXT macro after the declaration of my_cxt_t.
+ * 4. Use the MY_CXT_INIT macro such that it is called exactly once
+ *    (typically put in the BOOT: section).
+ * 5. Use the members of the my_cxt_t structure everywhere as
+ *    MY_CXT.member.
+ * 6. Use the dMY_CXT macro (a declaration) in all the functions that
+ *    access MY_CXT.
+ */
+
+#if defined(MULTIPLICITY) || defined(PERL_OBJECT) || \
+    defined(PERL_CAPI)    || defined(PERL_IMPLICIT_CONTEXT)
+
+#ifndef START_MY_CXT
+
+/* This must appear in all extensions that define a my_cxt_t structure,
+ * right after the definition (i.e. at file scope).  The non-threads
+ * case below uses it to declare the data as static. */
+#define START_MY_CXT
+
+#if (PERL_BCDVERSION < 0x5004068)
+/* Fetches the SV that keeps the per-interpreter data. */
+#define dMY_CXT_SV \
+       SV *my_cxt_sv = get_sv(MY_CXT_KEY, FALSE)
+#else /* >= perl5.004_68 */
+#define dMY_CXT_SV \
+       SV *my_cxt_sv = *hv_fetch(PL_modglobal, MY_CXT_KEY,             \
+                                 sizeof(MY_CXT_KEY)-1, TRUE)
+#endif /* < perl5.004_68 */
+
+/* This declaration should be used within all functions that use the
+ * interpreter-local data. */
+#define dMY_CXT        \
+       dMY_CXT_SV;                                                     \
+       my_cxt_t *my_cxtp = INT2PTR(my_cxt_t*,SvUV(my_cxt_sv))
+
+/* Creates and zeroes the per-interpreter data.
+ * (We allocate my_cxtp in a Perl SV so that it will be released when
+ * the interpreter goes away.) */
+#define MY_CXT_INIT \
+       dMY_CXT_SV;                                                     \
+       /* newSV() allocates one more than needed */                    \
+       my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\
+       Zero(my_cxtp, 1, my_cxt_t);                                     \
+       sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
+
+/* This macro must be used to access members of the my_cxt_t structure.
+ * e.g. MYCXT.some_data */
+#define MY_CXT         (*my_cxtp)
+
+/* Judicious use of these macros can reduce the number of times dMY_CXT
+ * is used.  Use is similar to pTHX, aTHX etc. */
+#define pMY_CXT                my_cxt_t *my_cxtp
+#define pMY_CXT_       pMY_CXT,
+#define _pMY_CXT       ,pMY_CXT
+#define aMY_CXT                my_cxtp
+#define aMY_CXT_       aMY_CXT,
+#define _aMY_CXT       ,aMY_CXT
+
+#endif /* START_MY_CXT */
+
+#ifndef MY_CXT_CLONE
+/* Clones the per-interpreter data. */
+#define MY_CXT_CLONE \
+       dMY_CXT_SV;                                                     \
+       my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\
+       Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t);\
+       sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
+#endif
+
+#else /* single interpreter */
+
+#ifndef START_MY_CXT
+
+#define START_MY_CXT   static my_cxt_t my_cxt;
+#define dMY_CXT_SV     dNOOP
+#define dMY_CXT                dNOOP
+#define MY_CXT_INIT    NOOP
+#define MY_CXT         my_cxt
+
+#define pMY_CXT                void
+#define pMY_CXT_
+#define _pMY_CXT
+#define aMY_CXT
+#define aMY_CXT_
+#define _aMY_CXT
+
+#endif /* START_MY_CXT */
+
+#ifndef MY_CXT_CLONE
+#define MY_CXT_CLONE   NOOP
+#endif
+
+#endif
+
+#ifndef IVdf
+#  if IVSIZE == LONGSIZE
+#    define    IVdf      "ld"
+#    define    UVuf      "lu"
+#    define    UVof      "lo"
+#    define    UVxf      "lx"
+#    define    UVXf      "lX"
+#  else
+#    if IVSIZE == INTSIZE
+#      define  IVdf      "d"
+#      define  UVuf      "u"
+#      define  UVof      "o"
+#      define  UVxf      "x"
+#      define  UVXf      "X"
+#    endif
+#  endif
+#endif
+
+#ifndef NVef
+#  if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) && \
+      defined(PERL_PRIfldbl) && (PERL_BCDVERSION != 0x5006000)
+            /* Not very likely, but let's try anyway. */
+#    define NVef          PERL_PRIeldbl
+#    define NVff          PERL_PRIfldbl
+#    define NVgf          PERL_PRIgldbl
+#  else
+#    define NVef          "e"
+#    define NVff          "f"
+#    define NVgf          "g"
+#  endif
+#endif
+
+#ifndef SvREFCNT_inc
+#  ifdef PERL_USE_GCC_BRACE_GROUPS
+#    define SvREFCNT_inc(sv)           \
+      ({                               \
+          SV * const _sv = (SV*)(sv);  \
+          if (_sv)                     \
+               (SvREFCNT(_sv))++;      \
+          _sv;                         \
+      })
+#  else
+#    define SvREFCNT_inc(sv)   \
+          ((PL_Sv=(SV*)(sv)) ? (++(SvREFCNT(PL_Sv)),PL_Sv) : NULL)
+#  endif
+#endif
+
+#ifndef SvREFCNT_inc_simple
+#  ifdef PERL_USE_GCC_BRACE_GROUPS
+#    define SvREFCNT_inc_simple(sv)    \
+      ({                                       \
+          if (sv)                              \
+               (SvREFCNT(sv))++;               \
+          (SV *)(sv);                          \
+      })
+#  else
+#    define SvREFCNT_inc_simple(sv) \
+          ((sv) ? (SvREFCNT(sv)++,(SV*)(sv)) : NULL)
+#  endif
+#endif
+
+#ifndef SvREFCNT_inc_NN
+#  ifdef PERL_USE_GCC_BRACE_GROUPS
+#    define SvREFCNT_inc_NN(sv)                \
+      ({                                       \
+          SV * const _sv = (SV*)(sv);  \
+          SvREFCNT(_sv)++;             \
+          _sv;                         \
+      })
+#  else
+#    define SvREFCNT_inc_NN(sv) \
+          (PL_Sv=(SV*)(sv),++(SvREFCNT(PL_Sv)),PL_Sv)
+#  endif
+#endif
+
+#ifndef SvREFCNT_inc_void
+#  ifdef PERL_USE_GCC_BRACE_GROUPS
+#    define SvREFCNT_inc_void(sv)              \
+      ({                                       \
+          SV * const _sv = (SV*)(sv);  \
+          if (_sv)                     \
+              (void)(SvREFCNT(_sv)++); \
+      })
+#  else
+#    define SvREFCNT_inc_void(sv) \
+          (void)((PL_Sv=(SV*)(sv)) ? ++(SvREFCNT(PL_Sv)) : 0)
+#  endif
+#endif
+#ifndef SvREFCNT_inc_simple_void
+#  define SvREFCNT_inc_simple_void(sv)   STMT_START { if (sv) SvREFCNT(sv)++; } STMT_END
+#endif
+
+#ifndef SvREFCNT_inc_simple_NN
+#  define SvREFCNT_inc_simple_NN(sv)     (++SvREFCNT(sv), (SV*)(sv))
+#endif
+
+#ifndef SvREFCNT_inc_void_NN
+#  define SvREFCNT_inc_void_NN(sv)       (void)(++SvREFCNT((SV*)(sv)))
+#endif
+
+#ifndef SvREFCNT_inc_simple_void_NN
+#  define SvREFCNT_inc_simple_void_NN(sv) (void)(++SvREFCNT((SV*)(sv)))
+#endif
+
+#ifndef newSV_type
+
+#if defined(NEED_newSV_type)
+static SV* DPPP_(my_newSV_type)(pTHX_ svtype const t);
+static
+#else
+extern SV* DPPP_(my_newSV_type)(pTHX_ svtype const t);
+#endif
+
+#ifdef newSV_type
+#  undef newSV_type
+#endif
+#define newSV_type(a) DPPP_(my_newSV_type)(aTHX_ a)
+#define Perl_newSV_type DPPP_(my_newSV_type)
+
+#if defined(NEED_newSV_type) || defined(NEED_newSV_type_GLOBAL)
+
+SV*
+DPPP_(my_newSV_type)(pTHX_ svtype const t)
+{
+  SV* const sv = newSV(0);
+  sv_upgrade(sv, t);
+  return sv;
+}
+
+#endif
+
+#endif
+
+#if (PERL_BCDVERSION < 0x5006000)
+# define D_PPP_CONSTPV_ARG(x)  ((char *) (x))
+#else
+# define D_PPP_CONSTPV_ARG(x)  (x)
+#endif
+#ifndef newSVpvn
+#  define newSVpvn(data,len)             ((data)                                              \
+                                    ? ((len) ? newSVpv((data), (len)) : newSVpv("", 0)) \
+                                    : newSV(0))
+#endif
+#ifndef newSVpvn_utf8
+#  define newSVpvn_utf8(s, len, u)       newSVpvn_flags((s), (len), (u) ? SVf_UTF8 : 0)
+#endif
+#ifndef SVf_UTF8
+#  define SVf_UTF8                       0
+#endif
+
+#ifndef newSVpvn_flags
+
+#if defined(NEED_newSVpvn_flags)
+static SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags);
+static
+#else
+extern SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags);
+#endif
+
+#ifdef newSVpvn_flags
+#  undef newSVpvn_flags
+#endif
+#define newSVpvn_flags(a,b,c) DPPP_(my_newSVpvn_flags)(aTHX_ a,b,c)
+#define Perl_newSVpvn_flags DPPP_(my_newSVpvn_flags)
+
+#if defined(NEED_newSVpvn_flags) || defined(NEED_newSVpvn_flags_GLOBAL)
+
+SV *
+DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags)
+{
+  SV *sv = newSVpvn(D_PPP_CONSTPV_ARG(s), len);
+  SvFLAGS(sv) |= (flags & SVf_UTF8);
+  return (flags & SVs_TEMP) ? sv_2mortal(sv) : sv;
+}
+
+#endif
+
+#endif
+
+/* Backwards compatibility stuff... :-( */
+#if !defined(NEED_sv_2pv_flags) && defined(NEED_sv_2pv_nolen)
+#  define NEED_sv_2pv_flags
+#endif
+#if !defined(NEED_sv_2pv_flags_GLOBAL) && defined(NEED_sv_2pv_nolen_GLOBAL)
+#  define NEED_sv_2pv_flags_GLOBAL
+#endif
+
+/* Hint: sv_2pv_nolen
+ * Use the SvPV_nolen() or SvPV_nolen_const() macros instead of sv_2pv_nolen().
+ */
+#ifndef sv_2pv_nolen
+#  define sv_2pv_nolen(sv)               SvPV_nolen(sv)
+#endif
+
+#ifdef SvPVbyte
+
+/* Hint: SvPVbyte
+ * Does not work in perl-5.6.1, ppport.h implements a version
+ * borrowed from perl-5.7.3.
+ */
+
+#if (PERL_BCDVERSION < 0x5007000)
+
+#if defined(NEED_sv_2pvbyte)
+static char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp);
+static
+#else
+extern char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp);
+#endif
+
+#ifdef sv_2pvbyte
+#  undef sv_2pvbyte
+#endif
+#define sv_2pvbyte(a,b) DPPP_(my_sv_2pvbyte)(aTHX_ a,b)
+#define Perl_sv_2pvbyte DPPP_(my_sv_2pvbyte)
+
+#if defined(NEED_sv_2pvbyte) || defined(NEED_sv_2pvbyte_GLOBAL)
+
+char *
+DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp)
+{
+  sv_utf8_downgrade(sv,0);
+  return SvPV(sv,*lp);
+}
+
+#endif
+
+/* Hint: sv_2pvbyte
+ * Use the SvPVbyte() macro instead of sv_2pvbyte().
+ */
+
+#undef SvPVbyte
+
+#define SvPVbyte(sv, lp)                                                \
+        ((SvFLAGS(sv) & (SVf_POK|SVf_UTF8)) == (SVf_POK)                \
+         ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pvbyte(sv, &lp))
+
+#endif
+
+#else
+
+#  define SvPVbyte          SvPV
+#  define sv_2pvbyte        sv_2pv
+
+#endif
+#ifndef sv_2pvbyte_nolen
+#  define sv_2pvbyte_nolen(sv)           sv_2pv_nolen(sv)
+#endif
+
+/* Hint: sv_pvn
+ * Always use the SvPV() macro instead of sv_pvn().
+ */
+
+/* Hint: sv_pvn_force
+ * Always use the SvPV_force() macro instead of sv_pvn_force().
+ */
+
+/* If these are undefined, they're not handled by the core anyway */
+#ifndef SV_IMMEDIATE_UNREF
+#  define SV_IMMEDIATE_UNREF             0
+#endif
+
+#ifndef SV_GMAGIC
+#  define SV_GMAGIC                      0
+#endif
+
+#ifndef SV_COW_DROP_PV
+#  define SV_COW_DROP_PV                 0
+#endif
+
+#ifndef SV_UTF8_NO_ENCODING
+#  define SV_UTF8_NO_ENCODING            0
+#endif
+
+#ifndef SV_NOSTEAL
+#  define SV_NOSTEAL                     0
+#endif
+
+#ifndef SV_CONST_RETURN
+#  define SV_CONST_RETURN                0
+#endif
+
+#ifndef SV_MUTABLE_RETURN
+#  define SV_MUTABLE_RETURN              0
+#endif
+
+#ifndef SV_SMAGIC
+#  define SV_SMAGIC                      0
+#endif
+
+#ifndef SV_HAS_TRAILING_NUL
+#  define SV_HAS_TRAILING_NUL            0
+#endif
+
+#ifndef SV_COW_SHARED_HASH_KEYS
+#  define SV_COW_SHARED_HASH_KEYS        0
+#endif
+
+#if (PERL_BCDVERSION < 0x5007002)
+
+#if defined(NEED_sv_2pv_flags)
+static char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
+static
+#else
+extern char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
+#endif
+
+#ifdef sv_2pv_flags
+#  undef sv_2pv_flags
+#endif
+#define sv_2pv_flags(a,b,c) DPPP_(my_sv_2pv_flags)(aTHX_ a,b,c)
+#define Perl_sv_2pv_flags DPPP_(my_sv_2pv_flags)
+
+#if defined(NEED_sv_2pv_flags) || defined(NEED_sv_2pv_flags_GLOBAL)
+
+char *
+DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags)
+{
+  STRLEN n_a = (STRLEN) flags;
+  return sv_2pv(sv, lp ? lp : &n_a);
+}
+
+#endif
+
+#if defined(NEED_sv_pvn_force_flags)
+static char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
+static
+#else
+extern char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags);
+#endif
+
+#ifdef sv_pvn_force_flags
+#  undef sv_pvn_force_flags
+#endif
+#define sv_pvn_force_flags(a,b,c) DPPP_(my_sv_pvn_force_flags)(aTHX_ a,b,c)
+#define Perl_sv_pvn_force_flags DPPP_(my_sv_pvn_force_flags)
+
+#if defined(NEED_sv_pvn_force_flags) || defined(NEED_sv_pvn_force_flags_GLOBAL)
+
+char *
+DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags)
+{
+  STRLEN n_a = (STRLEN) flags;
+  return sv_pvn_force(sv, lp ? lp : &n_a);
+}
+
+#endif
+
+#endif
+
+#if (PERL_BCDVERSION < 0x5008008) || ( (PERL_BCDVERSION >= 0x5009000) && (PERL_BCDVERSION < 0x5009003) )
+# define DPPP_SVPV_NOLEN_LP_ARG &PL_na
+#else
+# define DPPP_SVPV_NOLEN_LP_ARG 0
+#endif
+#ifndef SvPV_const
+#  define SvPV_const(sv, lp)             SvPV_flags_const(sv, lp, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_mutable
+#  define SvPV_mutable(sv, lp)           SvPV_flags_mutable(sv, lp, SV_GMAGIC)
+#endif
+#ifndef SvPV_flags
+#  define SvPV_flags(sv, lp, flags)      \
+                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+                  ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pv_flags(sv, &lp, flags))
+#endif
+#ifndef SvPV_flags_const
+#  define SvPV_flags_const(sv, lp, flags) \
+                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+                  ? ((lp = SvCUR(sv)), SvPVX_const(sv)) : \
+                  (const char*) sv_2pv_flags(sv, &lp, flags|SV_CONST_RETURN))
+#endif
+#ifndef SvPV_flags_const_nolen
+#  define SvPV_flags_const_nolen(sv, flags) \
+                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+                  ? SvPVX_const(sv) : \
+                  (const char*) sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags|SV_CONST_RETURN))
+#endif
+#ifndef SvPV_flags_mutable
+#  define SvPV_flags_mutable(sv, lp, flags) \
+                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+                  ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) : \
+                  sv_2pv_flags(sv, &lp, flags|SV_MUTABLE_RETURN))
+#endif
+#ifndef SvPV_force
+#  define SvPV_force(sv, lp)             SvPV_force_flags(sv, lp, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_force_nolen
+#  define SvPV_force_nolen(sv)           SvPV_force_flags_nolen(sv, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_force_mutable
+#  define SvPV_force_mutable(sv, lp)     SvPV_force_flags_mutable(sv, lp, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_force_nomg
+#  define SvPV_force_nomg(sv, lp)        SvPV_force_flags(sv, lp, 0)
+#endif
+
+#ifndef SvPV_force_nomg_nolen
+#  define SvPV_force_nomg_nolen(sv)      SvPV_force_flags_nolen(sv, 0)
+#endif
+#ifndef SvPV_force_flags
+#  define SvPV_force_flags(sv, lp, flags) \
+                 ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
+                 ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_pvn_force_flags(sv, &lp, flags))
+#endif
+#ifndef SvPV_force_flags_nolen
+#  define SvPV_force_flags_nolen(sv, flags) \
+                 ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
+                 ? SvPVX(sv) : sv_pvn_force_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags))
+#endif
+#ifndef SvPV_force_flags_mutable
+#  define SvPV_force_flags_mutable(sv, lp, flags) \
+                 ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
+                 ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) \
+                  : sv_pvn_force_flags(sv, &lp, flags|SV_MUTABLE_RETURN))
+#endif
+#ifndef SvPV_nolen
+#  define SvPV_nolen(sv)                 \
+                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+                  ? SvPVX(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC))
+#endif
+#ifndef SvPV_nolen_const
+#  define SvPV_nolen_const(sv)           \
+                 ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+                  ? SvPVX_const(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC|SV_CONST_RETURN))
+#endif
+#ifndef SvPV_nomg
+#  define SvPV_nomg(sv, lp)              SvPV_flags(sv, lp, 0)
+#endif
+
+#ifndef SvPV_nomg_const
+#  define SvPV_nomg_const(sv, lp)        SvPV_flags_const(sv, lp, 0)
+#endif
+
+#ifndef SvPV_nomg_const_nolen
+#  define SvPV_nomg_const_nolen(sv)      SvPV_flags_const_nolen(sv, 0)
+#endif
+#ifndef SvPV_renew
+#  define SvPV_renew(sv,n)               STMT_START { SvLEN_set(sv, n); \
+                 SvPV_set((sv), (char *) saferealloc(          \
+                       (Malloc_t)SvPVX(sv), (MEM_SIZE)((n)))); \
+               } STMT_END
+#endif
+#ifndef SvMAGIC_set
+#  define SvMAGIC_set(sv, val)           \
+                STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \
+                (((XPVMG*) SvANY(sv))->xmg_magic = (val)); } STMT_END
+#endif
+
+#if (PERL_BCDVERSION < 0x5009003)
+#ifndef SvPVX_const
+#  define SvPVX_const(sv)                ((const char*) (0 + SvPVX(sv)))
+#endif
+
+#ifndef SvPVX_mutable
+#  define SvPVX_mutable(sv)              (0 + SvPVX(sv))
+#endif
+#ifndef SvRV_set
+#  define SvRV_set(sv, val)              \
+                STMT_START { assert(SvTYPE(sv) >=  SVt_RV); \
+                (((XRV*) SvANY(sv))->xrv_rv = (val)); } STMT_END
+#endif
+
+#else
+#ifndef SvPVX_const
+#  define SvPVX_const(sv)                ((const char*)((sv)->sv_u.svu_pv))
+#endif
+
+#ifndef SvPVX_mutable
+#  define SvPVX_mutable(sv)              ((sv)->sv_u.svu_pv)
+#endif
+#ifndef SvRV_set
+#  define SvRV_set(sv, val)              \
+                STMT_START { assert(SvTYPE(sv) >=  SVt_RV); \
+                ((sv)->sv_u.svu_rv = (val)); } STMT_END
+#endif
+
+#endif
+#ifndef SvSTASH_set
+#  define SvSTASH_set(sv, val)           \
+                STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \
+                (((XPVMG*) SvANY(sv))->xmg_stash = (val)); } STMT_END
+#endif
+
+#if (PERL_BCDVERSION < 0x5004000)
+#ifndef SvUV_set
+#  define SvUV_set(sv, val)              \
+                STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \
+                (((XPVIV*) SvANY(sv))->xiv_iv = (IV) (val)); } STMT_END
+#endif
+
+#else
+#ifndef SvUV_set
+#  define SvUV_set(sv, val)              \
+                STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \
+                (((XPVUV*) SvANY(sv))->xuv_uv = (val)); } STMT_END
+#endif
+
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(vnewSVpvf)
+#if defined(NEED_vnewSVpvf)
+static SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args);
+static
+#else
+extern SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args);
+#endif
+
+#ifdef vnewSVpvf
+#  undef vnewSVpvf
+#endif
+#define vnewSVpvf(a,b) DPPP_(my_vnewSVpvf)(aTHX_ a,b)
+#define Perl_vnewSVpvf DPPP_(my_vnewSVpvf)
+
+#if defined(NEED_vnewSVpvf) || defined(NEED_vnewSVpvf_GLOBAL)
+
+SV *
+DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args)
+{
+  register SV *sv = newSV(0);
+  sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*));
+  return sv;
+}
+
+#endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf)
+#  define sv_vcatpvf(sv, pat, args)  sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*))
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf)
+#  define sv_vsetpvf(sv, pat, args)  sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*))
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg)
+#if defined(NEED_sv_catpvf_mg)
+static void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
+#endif
+
+#define Perl_sv_catpvf_mg DPPP_(my_sv_catpvf_mg)
+
+#if defined(NEED_sv_catpvf_mg) || defined(NEED_sv_catpvf_mg_GLOBAL)
+
+void
+DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...)
+{
+  va_list args;
+  va_start(args, pat);
+  sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+  SvSETMAGIC(sv);
+  va_end(args);
+}
+
+#endif
+#endif
+
+#ifdef PERL_IMPLICIT_CONTEXT
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg_nocontext)
+#if defined(NEED_sv_catpvf_mg_nocontext)
+static void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...);
+#endif
+
+#define sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext)
+#define Perl_sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext)
+
+#if defined(NEED_sv_catpvf_mg_nocontext) || defined(NEED_sv_catpvf_mg_nocontext_GLOBAL)
+
+void
+DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...)
+{
+  dTHX;
+  va_list args;
+  va_start(args, pat);
+  sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+  SvSETMAGIC(sv);
+  va_end(args);
+}
+
+#endif
+#endif
+#endif
+
+/* sv_catpvf_mg depends on sv_catpvf_mg_nocontext */
+#ifndef sv_catpvf_mg
+#  ifdef PERL_IMPLICIT_CONTEXT
+#    define sv_catpvf_mg   Perl_sv_catpvf_mg_nocontext
+#  else
+#    define sv_catpvf_mg   Perl_sv_catpvf_mg
+#  endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf_mg)
+#  define sv_vcatpvf_mg(sv, pat, args)                                     \
+   STMT_START {                                                            \
+     sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*));  \
+     SvSETMAGIC(sv);                                                       \
+   } STMT_END
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg)
+#if defined(NEED_sv_setpvf_mg)
+static void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...);
+#endif
+
+#define Perl_sv_setpvf_mg DPPP_(my_sv_setpvf_mg)
+
+#if defined(NEED_sv_setpvf_mg) || defined(NEED_sv_setpvf_mg_GLOBAL)
+
+void
+DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...)
+{
+  va_list args;
+  va_start(args, pat);
+  sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+  SvSETMAGIC(sv);
+  va_end(args);
+}
+
+#endif
+#endif
+
+#ifdef PERL_IMPLICIT_CONTEXT
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg_nocontext)
+#if defined(NEED_sv_setpvf_mg_nocontext)
+static void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...);
+#endif
+
+#define sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext)
+#define Perl_sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext)
+
+#if defined(NEED_sv_setpvf_mg_nocontext) || defined(NEED_sv_setpvf_mg_nocontext_GLOBAL)
+
+void
+DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...)
+{
+  dTHX;
+  va_list args;
+  va_start(args, pat);
+  sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+  SvSETMAGIC(sv);
+  va_end(args);
+}
+
+#endif
+#endif
+#endif
+
+/* sv_setpvf_mg depends on sv_setpvf_mg_nocontext */
+#ifndef sv_setpvf_mg
+#  ifdef PERL_IMPLICIT_CONTEXT
+#    define sv_setpvf_mg   Perl_sv_setpvf_mg_nocontext
+#  else
+#    define sv_setpvf_mg   Perl_sv_setpvf_mg
+#  endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf_mg)
+#  define sv_vsetpvf_mg(sv, pat, args)                                     \
+   STMT_START {                                                            \
+     sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*));  \
+     SvSETMAGIC(sv);                                                       \
+   } STMT_END
+#endif
+
+#ifndef newSVpvn_share
+
+#if defined(NEED_newSVpvn_share)
+static SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash);
+static
+#else
+extern SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash);
+#endif
+
+#ifdef newSVpvn_share
+#  undef newSVpvn_share
+#endif
+#define newSVpvn_share(a,b,c) DPPP_(my_newSVpvn_share)(aTHX_ a,b,c)
+#define Perl_newSVpvn_share DPPP_(my_newSVpvn_share)
+
+#if defined(NEED_newSVpvn_share) || defined(NEED_newSVpvn_share_GLOBAL)
+
+SV *
+DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash)
+{
+  SV *sv;
+  if (len < 0)
+    len = -len;
+  if (!hash)
+    PERL_HASH(hash, (char*) src, len);
+  sv = newSVpvn((char *) src, len);
+  sv_upgrade(sv, SVt_PVIV);
+  SvIVX(sv) = hash;
+  SvREADONLY_on(sv);
+  SvPOK_on(sv);
+  return sv;
+}
+
+#endif
+
+#endif
+#ifndef SvSHARED_HASH
+#  define SvSHARED_HASH(sv)              (0 + SvUVX(sv))
+#endif
+#ifndef HvNAME_get
+#  define HvNAME_get(hv)                 HvNAME(hv)
+#endif
+#ifndef HvNAMELEN_get
+#  define HvNAMELEN_get(hv)              (HvNAME_get(hv) ? (I32)strlen(HvNAME_get(hv)) : 0)
+#endif
+#ifndef GvSVn
+#  define GvSVn(gv)                      GvSV(gv)
+#endif
+
+#ifndef isGV_with_GP
+#  define isGV_with_GP(gv)               isGV(gv)
+#endif
+#ifndef WARN_ALL
+#  define WARN_ALL                       0
+#endif
+
+#ifndef WARN_CLOSURE
+#  define WARN_CLOSURE                   1
+#endif
+
+#ifndef WARN_DEPRECATED
+#  define WARN_DEPRECATED                2
+#endif
+
+#ifndef WARN_EXITING
+#  define WARN_EXITING                   3
+#endif
+
+#ifndef WARN_GLOB
+#  define WARN_GLOB                      4
+#endif
+
+#ifndef WARN_IO
+#  define WARN_IO                        5
+#endif
+
+#ifndef WARN_CLOSED
+#  define WARN_CLOSED                    6
+#endif
+
+#ifndef WARN_EXEC
+#  define WARN_EXEC                      7
+#endif
+
+#ifndef WARN_LAYER
+#  define WARN_LAYER                     8
+#endif
+
+#ifndef WARN_NEWLINE
+#  define WARN_NEWLINE                   9
+#endif
+
+#ifndef WARN_PIPE
+#  define WARN_PIPE                      10
+#endif
+
+#ifndef WARN_UNOPENED
+#  define WARN_UNOPENED                  11
+#endif
+
+#ifndef WARN_MISC
+#  define WARN_MISC                      12
+#endif
+
+#ifndef WARN_NUMERIC
+#  define WARN_NUMERIC                   13
+#endif
+
+#ifndef WARN_ONCE
+#  define WARN_ONCE                      14
+#endif
+
+#ifndef WARN_OVERFLOW
+#  define WARN_OVERFLOW                  15
+#endif
+
+#ifndef WARN_PACK
+#  define WARN_PACK                      16
+#endif
+
+#ifndef WARN_PORTABLE
+#  define WARN_PORTABLE                  17
+#endif
+
+#ifndef WARN_RECURSION
+#  define WARN_RECURSION                 18
+#endif
+
+#ifndef WARN_REDEFINE
+#  define WARN_REDEFINE                  19
+#endif
+
+#ifndef WARN_REGEXP
+#  define WARN_REGEXP                    20
+#endif
+
+#ifndef WARN_SEVERE
+#  define WARN_SEVERE                    21
+#endif
+
+#ifndef WARN_DEBUGGING
+#  define WARN_DEBUGGING                 22
+#endif
+
+#ifndef WARN_INPLACE
+#  define WARN_INPLACE                   23
+#endif
+
+#ifndef WARN_INTERNAL
+#  define WARN_INTERNAL                  24
+#endif
+
+#ifndef WARN_MALLOC
+#  define WARN_MALLOC                    25
+#endif
+
+#ifndef WARN_SIGNAL
+#  define WARN_SIGNAL                    26
+#endif
+
+#ifndef WARN_SUBSTR
+#  define WARN_SUBSTR                    27
+#endif
+
+#ifndef WARN_SYNTAX
+#  define WARN_SYNTAX                    28
+#endif
+
+#ifndef WARN_AMBIGUOUS
+#  define WARN_AMBIGUOUS                 29
+#endif
+
+#ifndef WARN_BAREWORD
+#  define WARN_BAREWORD                  30
+#endif
+
+#ifndef WARN_DIGIT
+#  define WARN_DIGIT                     31
+#endif
+
+#ifndef WARN_PARENTHESIS
+#  define WARN_PARENTHESIS               32
+#endif
+
+#ifndef WARN_PRECEDENCE
+#  define WARN_PRECEDENCE                33
+#endif
+
+#ifndef WARN_PRINTF
+#  define WARN_PRINTF                    34
+#endif
+
+#ifndef WARN_PROTOTYPE
+#  define WARN_PROTOTYPE                 35
+#endif
+
+#ifndef WARN_QW
+#  define WARN_QW                        36
+#endif
+
+#ifndef WARN_RESERVED
+#  define WARN_RESERVED                  37
+#endif
+
+#ifndef WARN_SEMICOLON
+#  define WARN_SEMICOLON                 38
+#endif
+
+#ifndef WARN_TAINT
+#  define WARN_TAINT                     39
+#endif
+
+#ifndef WARN_THREADS
+#  define WARN_THREADS                   40
+#endif
+
+#ifndef WARN_UNINITIALIZED
+#  define WARN_UNINITIALIZED             41
+#endif
+
+#ifndef WARN_UNPACK
+#  define WARN_UNPACK                    42
+#endif
+
+#ifndef WARN_UNTIE
+#  define WARN_UNTIE                     43
+#endif
+
+#ifndef WARN_UTF8
+#  define WARN_UTF8                      44
+#endif
+
+#ifndef WARN_VOID
+#  define WARN_VOID                      45
+#endif
+
+#ifndef WARN_ASSERTIONS
+#  define WARN_ASSERTIONS                46
+#endif
+#ifndef packWARN
+#  define packWARN(a)                    (a)
+#endif
+
+#ifndef ckWARN
+#  ifdef G_WARN_ON
+#    define  ckWARN(a)                  (PL_dowarn & G_WARN_ON)
+#  else
+#    define  ckWARN(a)                  PL_dowarn
+#  endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(warner)
+#if defined(NEED_warner)
+static void DPPP_(my_warner)(U32 err, const char *pat, ...);
+static
+#else
+extern void DPPP_(my_warner)(U32 err, const char *pat, ...);
+#endif
+
+#define Perl_warner DPPP_(my_warner)
+
+#if defined(NEED_warner) || defined(NEED_warner_GLOBAL)
+
+void
+DPPP_(my_warner)(U32 err, const char *pat, ...)
+{
+  SV *sv;
+  va_list args;
+
+  PERL_UNUSED_ARG(err);
+
+  va_start(args, pat);
+  sv = vnewSVpvf(pat, &args);
+  va_end(args);
+  sv_2mortal(sv);
+  warn("%s", SvPV_nolen(sv));
+}
+
+#define warner  Perl_warner
+
+#define Perl_warner_nocontext  Perl_warner
+
+#endif
+#endif
+
+/* concatenating with "" ensures that only literal strings are accepted as argument
+ * note that STR_WITH_LEN() can't be used as argument to macros or functions that
+ * under some configurations might be macros
+ */
+#ifndef STR_WITH_LEN
+#  define STR_WITH_LEN(s)                (s ""), (sizeof(s)-1)
+#endif
+#ifndef newSVpvs
+#  define newSVpvs(str)                  newSVpvn(str "", sizeof(str) - 1)
+#endif
+
+#ifndef newSVpvs_flags
+#  define newSVpvs_flags(str, flags)     newSVpvn_flags(str "", sizeof(str) - 1, flags)
+#endif
+
+#ifndef sv_catpvs
+#  define sv_catpvs(sv, str)             sv_catpvn(sv, str "", sizeof(str) - 1)
+#endif
+
+#ifndef sv_setpvs
+#  define sv_setpvs(sv, str)             sv_setpvn(sv, str "", sizeof(str) - 1)
+#endif
+
+#ifndef hv_fetchs
+#  define hv_fetchs(hv, key, lval)       hv_fetch(hv, key "", sizeof(key) - 1, lval)
+#endif
+
+#ifndef hv_stores
+#  define hv_stores(hv, key, val)        hv_store(hv, key "", sizeof(key) - 1, val, 0)
+#endif
+#ifndef gv_fetchpvn_flags
+#  define gv_fetchpvn_flags(name, len, flags, svt) gv_fetchpv(name, flags, svt)
+#endif
+
+#ifndef gv_fetchpvs
+#  define gv_fetchpvs(name, flags, svt)  gv_fetchpvn_flags(name "", sizeof(name) - 1, flags, svt)
+#endif
+
+#ifndef gv_stashpvs
+#  define gv_stashpvs(name, flags)       gv_stashpvn(name "", sizeof(name) - 1, flags)
+#endif
+#ifndef SvGETMAGIC
+#  define SvGETMAGIC(x)                  STMT_START { if (SvGMAGICAL(x)) mg_get(x); } STMT_END
+#endif
+#ifndef PERL_MAGIC_sv
+#  define PERL_MAGIC_sv                  '\0'
+#endif
+
+#ifndef PERL_MAGIC_overload
+#  define PERL_MAGIC_overload            'A'
+#endif
+
+#ifndef PERL_MAGIC_overload_elem
+#  define PERL_MAGIC_overload_elem       'a'
+#endif
+
+#ifndef PERL_MAGIC_overload_table
+#  define PERL_MAGIC_overload_table      'c'
+#endif
+
+#ifndef PERL_MAGIC_bm
+#  define PERL_MAGIC_bm                  'B'
+#endif
+
+#ifndef PERL_MAGIC_regdata
+#  define PERL_MAGIC_regdata             'D'
+#endif
+
+#ifndef PERL_MAGIC_regdatum
+#  define PERL_MAGIC_regdatum            'd'
+#endif
+
+#ifndef PERL_MAGIC_env
+#  define PERL_MAGIC_env                 'E'
+#endif
+
+#ifndef PERL_MAGIC_envelem
+#  define PERL_MAGIC_envelem             'e'
+#endif
+
+#ifndef PERL_MAGIC_fm
+#  define PERL_MAGIC_fm                  'f'
+#endif
+
+#ifndef PERL_MAGIC_regex_global
+#  define PERL_MAGIC_regex_global        'g'
+#endif
+
+#ifndef PERL_MAGIC_isa
+#  define PERL_MAGIC_isa                 'I'
+#endif
+
+#ifndef PERL_MAGIC_isaelem
+#  define PERL_MAGIC_isaelem             'i'
+#endif
+
+#ifndef PERL_MAGIC_nkeys
+#  define PERL_MAGIC_nkeys               'k'
+#endif
+
+#ifndef PERL_MAGIC_dbfile
+#  define PERL_MAGIC_dbfile              'L'
+#endif
+
+#ifndef PERL_MAGIC_dbline
+#  define PERL_MAGIC_dbline              'l'
+#endif
+
+#ifndef PERL_MAGIC_mutex
+#  define PERL_MAGIC_mutex               'm'
+#endif
+
+#ifndef PERL_MAGIC_shared
+#  define PERL_MAGIC_shared              'N'
+#endif
+
+#ifndef PERL_MAGIC_shared_scalar
+#  define PERL_MAGIC_shared_scalar       'n'
+#endif
+
+#ifndef PERL_MAGIC_collxfrm
+#  define PERL_MAGIC_collxfrm            'o'
+#endif
+
+#ifndef PERL_MAGIC_tied
+#  define PERL_MAGIC_tied                'P'
+#endif
+
+#ifndef PERL_MAGIC_tiedelem
+#  define PERL_MAGIC_tiedelem            'p'
+#endif
+
+#ifndef PERL_MAGIC_tiedscalar
+#  define PERL_MAGIC_tiedscalar          'q'
+#endif
+
+#ifndef PERL_MAGIC_qr
+#  define PERL_MAGIC_qr                  'r'
+#endif
+
+#ifndef PERL_MAGIC_sig
+#  define PERL_MAGIC_sig                 'S'
+#endif
+
+#ifndef PERL_MAGIC_sigelem
+#  define PERL_MAGIC_sigelem             's'
+#endif
+
+#ifndef PERL_MAGIC_taint
+#  define PERL_MAGIC_taint               't'
+#endif
+
+#ifndef PERL_MAGIC_uvar
+#  define PERL_MAGIC_uvar                'U'
+#endif
+
+#ifndef PERL_MAGIC_uvar_elem
+#  define PERL_MAGIC_uvar_elem           'u'
+#endif
+
+#ifndef PERL_MAGIC_vstring
+#  define PERL_MAGIC_vstring             'V'
+#endif
+
+#ifndef PERL_MAGIC_vec
+#  define PERL_MAGIC_vec                 'v'
+#endif
+
+#ifndef PERL_MAGIC_utf8
+#  define PERL_MAGIC_utf8                'w'
+#endif
+
+#ifndef PERL_MAGIC_substr
+#  define PERL_MAGIC_substr              'x'
+#endif
+
+#ifndef PERL_MAGIC_defelem
+#  define PERL_MAGIC_defelem             'y'
+#endif
+
+#ifndef PERL_MAGIC_glob
+#  define PERL_MAGIC_glob                '*'
+#endif
+
+#ifndef PERL_MAGIC_arylen
+#  define PERL_MAGIC_arylen              '#'
+#endif
+
+#ifndef PERL_MAGIC_pos
+#  define PERL_MAGIC_pos                 '.'
+#endif
+
+#ifndef PERL_MAGIC_backref
+#  define PERL_MAGIC_backref             '<'
+#endif
+
+#ifndef PERL_MAGIC_ext
+#  define PERL_MAGIC_ext                 '~'
+#endif
+
+/* That's the best we can do... */
+#ifndef sv_catpvn_nomg
+#  define sv_catpvn_nomg                 sv_catpvn
+#endif
+
+#ifndef sv_catsv_nomg
+#  define sv_catsv_nomg                  sv_catsv
+#endif
+
+#ifndef sv_setsv_nomg
+#  define sv_setsv_nomg                  sv_setsv
+#endif
+
+#ifndef sv_pvn_nomg
+#  define sv_pvn_nomg                    sv_pvn
+#endif
+
+#ifndef SvIV_nomg
+#  define SvIV_nomg                      SvIV
+#endif
+
+#ifndef SvUV_nomg
+#  define SvUV_nomg                      SvUV
+#endif
+
+#ifndef sv_catpv_mg
+#  define sv_catpv_mg(sv, ptr)          \
+   STMT_START {                         \
+     SV *TeMpSv = sv;                   \
+     sv_catpv(TeMpSv,ptr);              \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+
+#ifndef sv_catpvn_mg
+#  define sv_catpvn_mg(sv, ptr, len)    \
+   STMT_START {                         \
+     SV *TeMpSv = sv;                   \
+     sv_catpvn(TeMpSv,ptr,len);         \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+
+#ifndef sv_catsv_mg
+#  define sv_catsv_mg(dsv, ssv)         \
+   STMT_START {                         \
+     SV *TeMpSv = dsv;                  \
+     sv_catsv(TeMpSv,ssv);              \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+
+#ifndef sv_setiv_mg
+#  define sv_setiv_mg(sv, i)            \
+   STMT_START {                         \
+     SV *TeMpSv = sv;                   \
+     sv_setiv(TeMpSv,i);                \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+
+#ifndef sv_setnv_mg
+#  define sv_setnv_mg(sv, num)          \
+   STMT_START {                         \
+     SV *TeMpSv = sv;                   \
+     sv_setnv(TeMpSv,num);              \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+
+#ifndef sv_setpv_mg
+#  define sv_setpv_mg(sv, ptr)          \
+   STMT_START {                         \
+     SV *TeMpSv = sv;                   \
+     sv_setpv(TeMpSv,ptr);              \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+
+#ifndef sv_setpvn_mg
+#  define sv_setpvn_mg(sv, ptr, len)    \
+   STMT_START {                         \
+     SV *TeMpSv = sv;                   \
+     sv_setpvn(TeMpSv,ptr,len);         \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+
+#ifndef sv_setsv_mg
+#  define sv_setsv_mg(dsv, ssv)         \
+   STMT_START {                         \
+     SV *TeMpSv = dsv;                  \
+     sv_setsv(TeMpSv,ssv);              \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+
+#ifndef sv_setuv_mg
+#  define sv_setuv_mg(sv, i)            \
+   STMT_START {                         \
+     SV *TeMpSv = sv;                   \
+     sv_setuv(TeMpSv,i);                \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+
+#ifndef sv_usepvn_mg
+#  define sv_usepvn_mg(sv, ptr, len)    \
+   STMT_START {                         \
+     SV *TeMpSv = sv;                   \
+     sv_usepvn(TeMpSv,ptr,len);         \
+     SvSETMAGIC(TeMpSv);                \
+   } STMT_END
+#endif
+#ifndef SvVSTRING_mg
+#  define SvVSTRING_mg(sv)               (SvMAGICAL(sv) ? mg_find(sv, PERL_MAGIC_vstring) : NULL)
+#endif
+
+/* Hint: sv_magic_portable
+ * This is a compatibility function that is only available with
+ * Devel::PPPort. It is NOT in the perl core.
+ * Its purpose is to mimic the 5.8.0 behaviour of sv_magic() when
+ * it is being passed a name pointer with namlen == 0. In that
+ * case, perl 5.8.0 and later store the pointer, not a copy of it.
+ * The compatibility can be provided back to perl 5.004. With
+ * earlier versions, the code will not compile.
+ */
+
+#if (PERL_BCDVERSION < 0x5004000)
+
+  /* code that uses sv_magic_portable will not compile */
+
+#elif (PERL_BCDVERSION < 0x5008000)
+
+#  define sv_magic_portable(sv, obj, how, name, namlen)     \
+   STMT_START {                                             \
+     SV *SvMp_sv = (sv);                                    \
+     char *SvMp_name = (char *) (name);                     \
+     I32 SvMp_namlen = (namlen);                            \
+     if (SvMp_name && SvMp_namlen == 0)                     \
+     {                                                      \
+       MAGIC *mg;                                           \
+       sv_magic(SvMp_sv, obj, how, 0, 0);                   \
+       mg = SvMAGIC(SvMp_sv);                               \
+       mg->mg_len = -42; /* XXX: this is the tricky part */ \
+       mg->mg_ptr = SvMp_name;                              \
+     }                                                      \
+     else                                                   \
+     {                                                      \
+       sv_magic(SvMp_sv, obj, how, SvMp_name, SvMp_namlen); \
+     }                                                      \
+   } STMT_END
+
+#else
+
+#  define sv_magic_portable(a, b, c, d, e)  sv_magic(a, b, c, d, e)
+
+#endif
+
+#ifdef USE_ITHREADS
+#ifndef CopFILE
+#  define CopFILE(c)                     ((c)->cop_file)
+#endif
+
+#ifndef CopFILEGV
+#  define CopFILEGV(c)                   (CopFILE(c) ? gv_fetchfile(CopFILE(c)) : Nullgv)
+#endif
+
+#ifndef CopFILE_set
+#  define CopFILE_set(c,pv)              ((c)->cop_file = savepv(pv))
+#endif
+
+#ifndef CopFILESV
+#  define CopFILESV(c)                   (CopFILE(c) ? GvSV(gv_fetchfile(CopFILE(c))) : Nullsv)
+#endif
+
+#ifndef CopFILEAV
+#  define CopFILEAV(c)                   (CopFILE(c) ? GvAV(gv_fetchfile(CopFILE(c))) : Nullav)
+#endif
+
+#ifndef CopSTASHPV
+#  define CopSTASHPV(c)                  ((c)->cop_stashpv)
+#endif
+
+#ifndef CopSTASHPV_set
+#  define CopSTASHPV_set(c,pv)           ((c)->cop_stashpv = ((pv) ? savepv(pv) : Nullch))
+#endif
+
+#ifndef CopSTASH
+#  define CopSTASH(c)                    (CopSTASHPV(c) ? gv_stashpv(CopSTASHPV(c),GV_ADD) : Nullhv)
+#endif
+
+#ifndef CopSTASH_set
+#  define CopSTASH_set(c,hv)             CopSTASHPV_set(c, (hv) ? HvNAME(hv) : Nullch)
+#endif
+
+#ifndef CopSTASH_eq
+#  define CopSTASH_eq(c,hv)              ((hv) && (CopSTASHPV(c) == HvNAME(hv) \
+                                       || (CopSTASHPV(c) && HvNAME(hv) \
+                                       && strEQ(CopSTASHPV(c), HvNAME(hv)))))
+#endif
+
+#else
+#ifndef CopFILEGV
+#  define CopFILEGV(c)                   ((c)->cop_filegv)
+#endif
+
+#ifndef CopFILEGV_set
+#  define CopFILEGV_set(c,gv)            ((c)->cop_filegv = (GV*)SvREFCNT_inc(gv))
+#endif
+
+#ifndef CopFILE_set
+#  define CopFILE_set(c,pv)              CopFILEGV_set((c), gv_fetchfile(pv))
+#endif
+
+#ifndef CopFILESV
+#  define CopFILESV(c)                   (CopFILEGV(c) ? GvSV(CopFILEGV(c)) : Nullsv)
+#endif
+
+#ifndef CopFILEAV
+#  define CopFILEAV(c)                   (CopFILEGV(c) ? GvAV(CopFILEGV(c)) : Nullav)
+#endif
+
+#ifndef CopFILE
+#  define CopFILE(c)                     (CopFILESV(c) ? SvPVX(CopFILESV(c)) : Nullch)
+#endif
+
+#ifndef CopSTASH
+#  define CopSTASH(c)                    ((c)->cop_stash)
+#endif
+
+#ifndef CopSTASH_set
+#  define CopSTASH_set(c,hv)             ((c)->cop_stash = (hv))
+#endif
+
+#ifndef CopSTASHPV
+#  define CopSTASHPV(c)                  (CopSTASH(c) ? HvNAME(CopSTASH(c)) : Nullch)
+#endif
+
+#ifndef CopSTASHPV_set
+#  define CopSTASHPV_set(c,pv)           CopSTASH_set((c), gv_stashpv(pv,GV_ADD))
+#endif
+
+#ifndef CopSTASH_eq
+#  define CopSTASH_eq(c,hv)              (CopSTASH(c) == (hv))
+#endif
+
+#endif /* USE_ITHREADS */
+#ifndef IN_PERL_COMPILETIME
+#  define IN_PERL_COMPILETIME            (PL_curcop == &PL_compiling)
+#endif
+
+#ifndef IN_LOCALE_RUNTIME
+#  define IN_LOCALE_RUNTIME              (PL_curcop->op_private & HINT_LOCALE)
+#endif
+
+#ifndef IN_LOCALE_COMPILETIME
+#  define IN_LOCALE_COMPILETIME          (PL_hints & HINT_LOCALE)
+#endif
+
+#ifndef IN_LOCALE
+#  define IN_LOCALE                      (IN_PERL_COMPILETIME ? IN_LOCALE_COMPILETIME : IN_LOCALE_RUNTIME)
+#endif
+#ifndef IS_NUMBER_IN_UV
+#  define IS_NUMBER_IN_UV                0x01
+#endif
+
+#ifndef IS_NUMBER_GREATER_THAN_UV_MAX
+#  define IS_NUMBER_GREATER_THAN_UV_MAX  0x02
+#endif
+
+#ifndef IS_NUMBER_NOT_INT
+#  define IS_NUMBER_NOT_INT              0x04
+#endif
+
+#ifndef IS_NUMBER_NEG
+#  define IS_NUMBER_NEG                  0x08
+#endif
+
+#ifndef IS_NUMBER_INFINITY
+#  define IS_NUMBER_INFINITY             0x10
+#endif
+
+#ifndef IS_NUMBER_NAN
+#  define IS_NUMBER_NAN                  0x20
+#endif
+#ifndef GROK_NUMERIC_RADIX
+#  define GROK_NUMERIC_RADIX(sp, send)   grok_numeric_radix(sp, send)
+#endif
+#ifndef PERL_SCAN_GREATER_THAN_UV_MAX
+#  define PERL_SCAN_GREATER_THAN_UV_MAX  0x02
+#endif
+
+#ifndef PERL_SCAN_SILENT_ILLDIGIT
+#  define PERL_SCAN_SILENT_ILLDIGIT      0x04
+#endif
+
+#ifndef PERL_SCAN_ALLOW_UNDERSCORES
+#  define PERL_SCAN_ALLOW_UNDERSCORES    0x01
+#endif
+
+#ifndef PERL_SCAN_DISALLOW_PREFIX
+#  define PERL_SCAN_DISALLOW_PREFIX      0x02
+#endif
+
+#ifndef grok_numeric_radix
+#if defined(NEED_grok_numeric_radix)
+static bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send);
+static
+#else
+extern bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send);
+#endif
+
+#ifdef grok_numeric_radix
+#  undef grok_numeric_radix
+#endif
+#define grok_numeric_radix(a,b) DPPP_(my_grok_numeric_radix)(aTHX_ a,b)
+#define Perl_grok_numeric_radix DPPP_(my_grok_numeric_radix)
+
+#if defined(NEED_grok_numeric_radix) || defined(NEED_grok_numeric_radix_GLOBAL)
+bool
+DPPP_(my_grok_numeric_radix)(pTHX_ const char **sp, const char *send)
+{
+#ifdef USE_LOCALE_NUMERIC
+#ifdef PL_numeric_radix_sv
+    if (PL_numeric_radix_sv && IN_LOCALE) {
+        STRLEN len;
+        char* radix = SvPV(PL_numeric_radix_sv, len);
+        if (*sp + len <= send && memEQ(*sp, radix, len)) {
+            *sp += len;
+            return TRUE;
+        }
+    }
+#else
+    /* older perls don't have PL_numeric_radix_sv so the radix
+     * must manually be requested from locale.h
+     */
+#include <locale.h>
+    dTHR;  /* needed for older threaded perls */
+    struct lconv *lc = localeconv();
+    char *radix = lc->decimal_point;
+    if (radix && IN_LOCALE) {
+        STRLEN len = strlen(radix);
+        if (*sp + len <= send && memEQ(*sp, radix, len)) {
+            *sp += len;
+            return TRUE;
+        }
+    }
+#endif
+#endif /* USE_LOCALE_NUMERIC */
+    /* always try "." if numeric radix didn't match because
+     * we may have data from different locales mixed */
+    if (*sp < send && **sp == '.') {
+        ++*sp;
+        return TRUE;
+    }
+    return FALSE;
+}
+#endif
+#endif
+
+#ifndef grok_number
+#if defined(NEED_grok_number)
+static int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep);
+static
+#else
+extern int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep);
+#endif
+
+#ifdef grok_number
+#  undef grok_number
+#endif
+#define grok_number(a,b,c) DPPP_(my_grok_number)(aTHX_ a,b,c)
+#define Perl_grok_number DPPP_(my_grok_number)
+
+#if defined(NEED_grok_number) || defined(NEED_grok_number_GLOBAL)
+int
+DPPP_(my_grok_number)(pTHX_ const char *pv, STRLEN len, UV *valuep)
+{
+  const char *s = pv;
+  const char *send = pv + len;
+  const UV max_div_10 = UV_MAX / 10;
+  const char max_mod_10 = UV_MAX % 10;
+  int numtype = 0;
+  int sawinf = 0;
+  int sawnan = 0;
+
+  while (s < send && isSPACE(*s))
+    s++;
+  if (s == send) {
+    return 0;
+  } else if (*s == '-') {
+    s++;
+    numtype = IS_NUMBER_NEG;
+  }
+  else if (*s == '+')
+  s++;
+
+  if (s == send)
+    return 0;
+
+  /* next must be digit or the radix separator or beginning of infinity */
+  if (isDIGIT(*s)) {
+    /* UVs are at least 32 bits, so the first 9 decimal digits cannot
+       overflow.  */
+    UV value = *s - '0';
+    /* This construction seems to be more optimiser friendly.
+       (without it gcc does the isDIGIT test and the *s - '0' separately)
+       With it gcc on arm is managing 6 instructions (6 cycles) per digit.
+       In theory the optimiser could deduce how far to unroll the loop
+       before checking for overflow.  */
+    if (++s < send) {
+      int digit = *s - '0';
+      if (digit >= 0 && digit <= 9) {
+        value = value * 10 + digit;
+        if (++s < send) {
+          digit = *s - '0';
+          if (digit >= 0 && digit <= 9) {
+            value = value * 10 + digit;
+            if (++s < send) {
+              digit = *s - '0';
+              if (digit >= 0 && digit <= 9) {
+                value = value * 10 + digit;
+               if (++s < send) {
+                  digit = *s - '0';
+                  if (digit >= 0 && digit <= 9) {
+                    value = value * 10 + digit;
+                    if (++s < send) {
+                      digit = *s - '0';
+                      if (digit >= 0 && digit <= 9) {
+                        value = value * 10 + digit;
+                        if (++s < send) {
+                          digit = *s - '0';
+                          if (digit >= 0 && digit <= 9) {
+                            value = value * 10 + digit;
+                            if (++s < send) {
+                              digit = *s - '0';
+                              if (digit >= 0 && digit <= 9) {
+                                value = value * 10 + digit;
+                                if (++s < send) {
+                                  digit = *s - '0';
+                                  if (digit >= 0 && digit <= 9) {
+                                    value = value * 10 + digit;
+                                    if (++s < send) {
+                                      /* Now got 9 digits, so need to check
+                                         each time for overflow.  */
+                                      digit = *s - '0';
+                                      while (digit >= 0 && digit <= 9
+                                             && (value < max_div_10
+                                                 || (value == max_div_10
+                                                     && digit <= max_mod_10))) {
+                                        value = value * 10 + digit;
+                                        if (++s < send)
+                                          digit = *s - '0';
+                                        else
+                                          break;
+                                      }
+                                      if (digit >= 0 && digit <= 9
+                                          && (s < send)) {
+                                        /* value overflowed.
+                                           skip the remaining digits, don't
+                                           worry about setting *valuep.  */
+                                        do {
+                                          s++;
+                                        } while (s < send && isDIGIT(*s));
+                                        numtype |=
+                                          IS_NUMBER_GREATER_THAN_UV_MAX;
+                                        goto skip_value;
+                                      }
+                                    }
+                                  }
+                               }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+       }
+      }
+    }
+    numtype |= IS_NUMBER_IN_UV;
+    if (valuep)
+      *valuep = value;
+
+  skip_value:
+    if (GROK_NUMERIC_RADIX(&s, send)) {
+      numtype |= IS_NUMBER_NOT_INT;
+      while (s < send && isDIGIT(*s))  /* optional digits after the radix */
+        s++;
+    }
+  }
+  else if (GROK_NUMERIC_RADIX(&s, send)) {
+    numtype |= IS_NUMBER_NOT_INT | IS_NUMBER_IN_UV; /* valuep assigned below */
+    /* no digits before the radix means we need digits after it */
+    if (s < send && isDIGIT(*s)) {
+      do {
+        s++;
+      } while (s < send && isDIGIT(*s));
+      if (valuep) {
+        /* integer approximation is valid - it's 0.  */
+        *valuep = 0;
+      }
+    }
+    else
+      return 0;
+  } else if (*s == 'I' || *s == 'i') {
+    s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
+    s++; if (s == send || (*s != 'F' && *s != 'f')) return 0;
+    s++; if (s < send && (*s == 'I' || *s == 'i')) {
+      s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
+      s++; if (s == send || (*s != 'I' && *s != 'i')) return 0;
+      s++; if (s == send || (*s != 'T' && *s != 't')) return 0;
+      s++; if (s == send || (*s != 'Y' && *s != 'y')) return 0;
+      s++;
+    }
+    sawinf = 1;
+  } else if (*s == 'N' || *s == 'n') {
+    /* XXX TODO: There are signaling NaNs and quiet NaNs. */
+    s++; if (s == send || (*s != 'A' && *s != 'a')) return 0;
+    s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
+    s++;
+    sawnan = 1;
+  } else
+    return 0;
+
+  if (sawinf) {
+    numtype &= IS_NUMBER_NEG; /* Keep track of sign  */
+    numtype |= IS_NUMBER_INFINITY | IS_NUMBER_NOT_INT;
+  } else if (sawnan) {
+    numtype &= IS_NUMBER_NEG; /* Keep track of sign  */
+    numtype |= IS_NUMBER_NAN | IS_NUMBER_NOT_INT;
+  } else if (s < send) {
+    /* we can have an optional exponent part */
+    if (*s == 'e' || *s == 'E') {
+      /* The only flag we keep is sign.  Blow away any "it's UV"  */
+      numtype &= IS_NUMBER_NEG;
+      numtype |= IS_NUMBER_NOT_INT;
+      s++;
+      if (s < send && (*s == '-' || *s == '+'))
+        s++;
+      if (s < send && isDIGIT(*s)) {
+        do {
+          s++;
+        } while (s < send && isDIGIT(*s));
+      }
+      else
+      return 0;
+    }
+  }
+  while (s < send && isSPACE(*s))
+    s++;
+  if (s >= send)
+    return numtype;
+  if (len == 10 && memEQ(pv, "0 but true", 10)) {
+    if (valuep)
+      *valuep = 0;
+    return IS_NUMBER_IN_UV;
+  }
+  return 0;
+}
+#endif
+#endif
+
+/*
+ * The grok_* routines have been modified to use warn() instead of
+ * Perl_warner(). Also, 'hexdigit' was the former name of PL_hexdigit,
+ * which is why the stack variable has been renamed to 'xdigit'.
+ */
+
+#ifndef grok_bin
+#if defined(NEED_grok_bin)
+static UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+static
+#else
+extern UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+#endif
+
+#ifdef grok_bin
+#  undef grok_bin
+#endif
+#define grok_bin(a,b,c,d) DPPP_(my_grok_bin)(aTHX_ a,b,c,d)
+#define Perl_grok_bin DPPP_(my_grok_bin)
+
+#if defined(NEED_grok_bin) || defined(NEED_grok_bin_GLOBAL)
+UV
+DPPP_(my_grok_bin)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
+{
+    const char *s = start;
+    STRLEN len = *len_p;
+    UV value = 0;
+    NV value_nv = 0;
+
+    const UV max_div_2 = UV_MAX / 2;
+    bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
+    bool overflowed = FALSE;
+
+    if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) {
+        /* strip off leading b or 0b.
+           for compatibility silently suffer "b" and "0b" as valid binary
+           numbers. */
+        if (len >= 1) {
+            if (s[0] == 'b') {
+                s++;
+                len--;
+            }
+            else if (len >= 2 && s[0] == '0' && s[1] == 'b') {
+                s+=2;
+                len-=2;
+            }
+        }
+    }
+
+    for (; len-- && *s; s++) {
+        char bit = *s;
+        if (bit == '0' || bit == '1') {
+            /* Write it in this wonky order with a goto to attempt to get the
+               compiler to make the common case integer-only loop pretty tight.
+               With gcc seems to be much straighter code than old scan_bin.  */
+          redo:
+            if (!overflowed) {
+                if (value <= max_div_2) {
+                    value = (value << 1) | (bit - '0');
+                    continue;
+                }
+                /* Bah. We're just overflowed.  */
+                warn("Integer overflow in binary number");
+                overflowed = TRUE;
+                value_nv = (NV) value;
+            }
+            value_nv *= 2.0;
+           /* If an NV has not enough bits in its mantissa to
+            * represent a UV this summing of small low-order numbers
+            * is a waste of time (because the NV cannot preserve
+            * the low-order bits anyway): we could just remember when
+            * did we overflow and in the end just multiply value_nv by the
+            * right amount. */
+            value_nv += (NV)(bit - '0');
+            continue;
+        }
+        if (bit == '_' && len && allow_underscores && (bit = s[1])
+            && (bit == '0' || bit == '1'))
+           {
+               --len;
+               ++s;
+                goto redo;
+           }
+        if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
+            warn("Illegal binary digit '%c' ignored", *s);
+        break;
+    }
+
+    if (   ( overflowed && value_nv > 4294967295.0)
+#if UVSIZE > 4
+       || (!overflowed && value > 0xffffffff  )
+#endif
+       ) {
+       warn("Binary number > 0b11111111111111111111111111111111 non-portable");
+    }
+    *len_p = s - start;
+    if (!overflowed) {
+        *flags = 0;
+        return value;
+    }
+    *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
+    if (result)
+        *result = value_nv;
+    return UV_MAX;
+}
+#endif
+#endif
+
+#ifndef grok_hex
+#if defined(NEED_grok_hex)
+static UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+static
+#else
+extern UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+#endif
+
+#ifdef grok_hex
+#  undef grok_hex
+#endif
+#define grok_hex(a,b,c,d) DPPP_(my_grok_hex)(aTHX_ a,b,c,d)
+#define Perl_grok_hex DPPP_(my_grok_hex)
+
+#if defined(NEED_grok_hex) || defined(NEED_grok_hex_GLOBAL)
+UV
+DPPP_(my_grok_hex)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
+{
+    const char *s = start;
+    STRLEN len = *len_p;
+    UV value = 0;
+    NV value_nv = 0;
+
+    const UV max_div_16 = UV_MAX / 16;
+    bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
+    bool overflowed = FALSE;
+    const char *xdigit;
+
+    if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) {
+        /* strip off leading x or 0x.
+           for compatibility silently suffer "x" and "0x" as valid hex numbers.
+        */
+        if (len >= 1) {
+            if (s[0] == 'x') {
+                s++;
+                len--;
+            }
+            else if (len >= 2 && s[0] == '0' && s[1] == 'x') {
+                s+=2;
+                len-=2;
+            }
+        }
+    }
+
+    for (; len-- && *s; s++) {
+       xdigit = strchr((char *) PL_hexdigit, *s);
+        if (xdigit) {
+            /* Write it in this wonky order with a goto to attempt to get the
+               compiler to make the common case integer-only loop pretty tight.
+               With gcc seems to be much straighter code than old scan_hex.  */
+          redo:
+            if (!overflowed) {
+                if (value <= max_div_16) {
+                    value = (value << 4) | ((xdigit - PL_hexdigit) & 15);
+                    continue;
+                }
+                warn("Integer overflow in hexadecimal number");
+                overflowed = TRUE;
+                value_nv = (NV) value;
+            }
+            value_nv *= 16.0;
+           /* If an NV has not enough bits in its mantissa to
+            * represent a UV this summing of small low-order numbers
+            * is a waste of time (because the NV cannot preserve
+            * the low-order bits anyway): we could just remember when
+            * did we overflow and in the end just multiply value_nv by the
+            * right amount of 16-tuples. */
+            value_nv += (NV)((xdigit - PL_hexdigit) & 15);
+            continue;
+        }
+        if (*s == '_' && len && allow_underscores && s[1]
+               && (xdigit = strchr((char *) PL_hexdigit, s[1])))
+           {
+               --len;
+               ++s;
+                goto redo;
+           }
+        if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
+            warn("Illegal hexadecimal digit '%c' ignored", *s);
+        break;
+    }
+
+    if (   ( overflowed && value_nv > 4294967295.0)
+#if UVSIZE > 4
+       || (!overflowed && value > 0xffffffff  )
+#endif
+       ) {
+       warn("Hexadecimal number > 0xffffffff non-portable");
+    }
+    *len_p = s - start;
+    if (!overflowed) {
+        *flags = 0;
+        return value;
+    }
+    *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
+    if (result)
+        *result = value_nv;
+    return UV_MAX;
+}
+#endif
+#endif
+
+#ifndef grok_oct
+#if defined(NEED_grok_oct)
+static UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+static
+#else
+extern UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+#endif
+
+#ifdef grok_oct
+#  undef grok_oct
+#endif
+#define grok_oct(a,b,c,d) DPPP_(my_grok_oct)(aTHX_ a,b,c,d)
+#define Perl_grok_oct DPPP_(my_grok_oct)
+
+#if defined(NEED_grok_oct) || defined(NEED_grok_oct_GLOBAL)
+UV
+DPPP_(my_grok_oct)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
+{
+    const char *s = start;
+    STRLEN len = *len_p;
+    UV value = 0;
+    NV value_nv = 0;
+
+    const UV max_div_8 = UV_MAX / 8;
+    bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
+    bool overflowed = FALSE;
+
+    for (; len-- && *s; s++) {
+         /* gcc 2.95 optimiser not smart enough to figure that this subtraction
+            out front allows slicker code.  */
+        int digit = *s - '0';
+        if (digit >= 0 && digit <= 7) {
+            /* Write it in this wonky order with a goto to attempt to get the
+               compiler to make the common case integer-only loop pretty tight.
+            */
+          redo:
+            if (!overflowed) {
+                if (value <= max_div_8) {
+                    value = (value << 3) | digit;
+                    continue;
+                }
+                /* Bah. We're just overflowed.  */
+                warn("Integer overflow in octal number");
+                overflowed = TRUE;
+                value_nv = (NV) value;
+            }
+            value_nv *= 8.0;
+           /* If an NV has not enough bits in its mantissa to
+            * represent a UV this summing of small low-order numbers
+            * is a waste of time (because the NV cannot preserve
+            * the low-order bits anyway): we could just remember when
+            * did we overflow and in the end just multiply value_nv by the
+            * right amount of 8-tuples. */
+            value_nv += (NV)digit;
+            continue;
+        }
+        if (digit == ('_' - '0') && len && allow_underscores
+            && (digit = s[1] - '0') && (digit >= 0 && digit <= 7))
+           {
+               --len;
+               ++s;
+                goto redo;
+           }
+        /* Allow \octal to work the DWIM way (that is, stop scanning
+         * as soon as non-octal characters are seen, complain only iff
+         * someone seems to want to use the digits eight and nine). */
+        if (digit == 8 || digit == 9) {
+            if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
+                warn("Illegal octal digit '%c' ignored", *s);
+        }
+        break;
+    }
+
+    if (   ( overflowed && value_nv > 4294967295.0)
+#if UVSIZE > 4
+       || (!overflowed && value > 0xffffffff  )
+#endif
+       ) {
+       warn("Octal number > 037777777777 non-portable");
+    }
+    *len_p = s - start;
+    if (!overflowed) {
+        *flags = 0;
+        return value;
+    }
+    *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
+    if (result)
+        *result = value_nv;
+    return UV_MAX;
+}
+#endif
+#endif
+
+#if !defined(my_snprintf)
+#if defined(NEED_my_snprintf)
+static int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...);
+static
+#else
+extern int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...);
+#endif
+
+#define my_snprintf DPPP_(my_my_snprintf)
+#define Perl_my_snprintf DPPP_(my_my_snprintf)
+
+#if defined(NEED_my_snprintf) || defined(NEED_my_snprintf_GLOBAL)
+
+int
+DPPP_(my_my_snprintf)(char *buffer, const Size_t len, const char *format, ...)
+{
+    dTHX;
+    int retval;
+    va_list ap;
+    va_start(ap, format);
+#ifdef HAS_VSNPRINTF
+    retval = vsnprintf(buffer, len, format, ap);
+#else
+    retval = vsprintf(buffer, format, ap);
+#endif
+    va_end(ap);
+    if (retval < 0 || (len > 0 && (Size_t)retval >= len))
+       Perl_croak(aTHX_ "panic: my_snprintf buffer overflow");
+    return retval;
+}
+
+#endif
+#endif
+
+#if !defined(my_sprintf)
+#if defined(NEED_my_sprintf)
+static int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...);
+static
+#else
+extern int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...);
+#endif
+
+#define my_sprintf DPPP_(my_my_sprintf)
+#define Perl_my_sprintf DPPP_(my_my_sprintf)
+
+#if defined(NEED_my_sprintf) || defined(NEED_my_sprintf_GLOBAL)
+
+int
+DPPP_(my_my_sprintf)(char *buffer, const char* pat, ...)
+{
+    va_list args;
+    va_start(args, pat);
+    vsprintf(buffer, pat, args);
+    va_end(args);
+    return strlen(buffer);
+}
+
+#endif
+#endif
+
+#ifdef NO_XSLOCKS
+#  ifdef dJMPENV
+#    define dXCPT             dJMPENV; int rEtV = 0
+#    define XCPT_TRY_START    JMPENV_PUSH(rEtV); if (rEtV == 0)
+#    define XCPT_TRY_END      JMPENV_POP;
+#    define XCPT_CATCH        if (rEtV != 0)
+#    define XCPT_RETHROW      JMPENV_JUMP(rEtV)
+#  else
+#    define dXCPT             Sigjmp_buf oldTOP; int rEtV = 0
+#    define XCPT_TRY_START    Copy(top_env, oldTOP, 1, Sigjmp_buf); rEtV = Sigsetjmp(top_env, 1); if (rEtV == 0)
+#    define XCPT_TRY_END      Copy(oldTOP, top_env, 1, Sigjmp_buf);
+#    define XCPT_CATCH        if (rEtV != 0)
+#    define XCPT_RETHROW      Siglongjmp(top_env, rEtV)
+#  endif
+#endif
+
+#if !defined(my_strlcat)
+#if defined(NEED_my_strlcat)
+static Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size);
+static
+#else
+extern Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size);
+#endif
+
+#define my_strlcat DPPP_(my_my_strlcat)
+#define Perl_my_strlcat DPPP_(my_my_strlcat)
+
+#if defined(NEED_my_strlcat) || defined(NEED_my_strlcat_GLOBAL)
+
+Size_t
+DPPP_(my_my_strlcat)(char *dst, const char *src, Size_t size)
+{
+    Size_t used, length, copy;
+
+    used = strlen(dst);
+    length = strlen(src);
+    if (size > 0 && used < size - 1) {
+        copy = (length >= size - used) ? size - used - 1 : length;
+        memcpy(dst + used, src, copy);
+        dst[used + copy] = '\0';
+    }
+    return used + length;
+}
+#endif
+#endif
+
+#if !defined(my_strlcpy)
+#if defined(NEED_my_strlcpy)
+static Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size);
+static
+#else
+extern Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size);
+#endif
+
+#define my_strlcpy DPPP_(my_my_strlcpy)
+#define Perl_my_strlcpy DPPP_(my_my_strlcpy)
+
+#if defined(NEED_my_strlcpy) || defined(NEED_my_strlcpy_GLOBAL)
+
+Size_t
+DPPP_(my_my_strlcpy)(char *dst, const char *src, Size_t size)
+{
+    Size_t length, copy;
+
+    length = strlen(src);
+    if (size > 0) {
+        copy = (length >= size) ? size - 1 : length;
+        memcpy(dst, src, copy);
+        dst[copy] = '\0';
+    }
+    return length;
+}
+
+#endif
+#endif
+#ifndef PERL_PV_ESCAPE_QUOTE
+#  define PERL_PV_ESCAPE_QUOTE           0x0001
+#endif
+
+#ifndef PERL_PV_PRETTY_QUOTE
+#  define PERL_PV_PRETTY_QUOTE           PERL_PV_ESCAPE_QUOTE
+#endif
+
+#ifndef PERL_PV_PRETTY_ELLIPSES
+#  define PERL_PV_PRETTY_ELLIPSES        0x0002
+#endif
+
+#ifndef PERL_PV_PRETTY_LTGT
+#  define PERL_PV_PRETTY_LTGT            0x0004
+#endif
+
+#ifndef PERL_PV_ESCAPE_FIRSTCHAR
+#  define PERL_PV_ESCAPE_FIRSTCHAR       0x0008
+#endif
+
+#ifndef PERL_PV_ESCAPE_UNI
+#  define PERL_PV_ESCAPE_UNI             0x0100
+#endif
+
+#ifndef PERL_PV_ESCAPE_UNI_DETECT
+#  define PERL_PV_ESCAPE_UNI_DETECT      0x0200
+#endif
+
+#ifndef PERL_PV_ESCAPE_ALL
+#  define PERL_PV_ESCAPE_ALL             0x1000
+#endif
+
+#ifndef PERL_PV_ESCAPE_NOBACKSLASH
+#  define PERL_PV_ESCAPE_NOBACKSLASH     0x2000
+#endif
+
+#ifndef PERL_PV_ESCAPE_NOCLEAR
+#  define PERL_PV_ESCAPE_NOCLEAR         0x4000
+#endif
+
+#ifndef PERL_PV_ESCAPE_RE
+#  define PERL_PV_ESCAPE_RE              0x8000
+#endif
+
+#ifndef PERL_PV_PRETTY_NOCLEAR
+#  define PERL_PV_PRETTY_NOCLEAR         PERL_PV_ESCAPE_NOCLEAR
+#endif
+#ifndef PERL_PV_PRETTY_DUMP
+#  define PERL_PV_PRETTY_DUMP            PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_QUOTE
+#endif
+
+#ifndef PERL_PV_PRETTY_REGPROP
+#  define PERL_PV_PRETTY_REGPROP         PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_LTGT|PERL_PV_ESCAPE_RE
+#endif
+
+/* Hint: pv_escape
+ * Note that unicode functionality is only backported to
+ * those perl versions that support it. For older perl
+ * versions, the implementation will fall back to bytes.
+ */
+
+#ifndef pv_escape
+#if defined(NEED_pv_escape)
+static char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags);
+static
+#else
+extern char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags);
+#endif
+
+#ifdef pv_escape
+#  undef pv_escape
+#endif
+#define pv_escape(a,b,c,d,e,f) DPPP_(my_pv_escape)(aTHX_ a,b,c,d,e,f)
+#define Perl_pv_escape DPPP_(my_pv_escape)
+
+#if defined(NEED_pv_escape) || defined(NEED_pv_escape_GLOBAL)
+
+char *
+DPPP_(my_pv_escape)(pTHX_ SV *dsv, char const * const str,
+  const STRLEN count, const STRLEN max,
+  STRLEN * const escaped, const U32 flags)
+{
+    const char esc = flags & PERL_PV_ESCAPE_RE ? '%' : '\\';
+    const char dq = flags & PERL_PV_ESCAPE_QUOTE ? '"' : esc;
+    char octbuf[32] = "%123456789ABCDF";
+    STRLEN wrote = 0;
+    STRLEN chsize = 0;
+    STRLEN readsize = 1;
+#if defined(is_utf8_string) && defined(utf8_to_uvchr)
+    bool isuni = flags & PERL_PV_ESCAPE_UNI ? 1 : 0;
+#endif
+    const char *pv  = str;
+    const char * const end = pv + count;
+    octbuf[0] = esc;
+
+    if (!(flags & PERL_PV_ESCAPE_NOCLEAR))
+       sv_setpvs(dsv, "");
+
+#if defined(is_utf8_string) && defined(utf8_to_uvchr)
+    if ((flags & PERL_PV_ESCAPE_UNI_DETECT) && is_utf8_string((U8*)pv, count))
+        isuni = 1;
+#endif
+
+    for (; pv < end && (!max || wrote < max) ; pv += readsize) {
+        const UV u =
+#if defined(is_utf8_string) && defined(utf8_to_uvchr)
+                    isuni ? utf8_to_uvchr((U8*)pv, &readsize) :
+#endif
+                            (U8)*pv;
+        const U8 c = (U8)u & 0xFF;
+
+        if (u > 255 || (flags & PERL_PV_ESCAPE_ALL)) {
+            if (flags & PERL_PV_ESCAPE_FIRSTCHAR)
+                chsize = my_snprintf(octbuf, sizeof octbuf,
+                                      "%"UVxf, u);
+            else
+                chsize = my_snprintf(octbuf, sizeof octbuf,
+                                      "%cx{%"UVxf"}", esc, u);
+        } else if (flags & PERL_PV_ESCAPE_NOBACKSLASH) {
+            chsize = 1;
+        } else {
+            if (c == dq || c == esc || !isPRINT(c)) {
+               chsize = 2;
+                switch (c) {
+               case '\\' : /* fallthrough */
+               case '%'  : if (c == esc)
+                               octbuf[1] = esc;
+                           else
+                               chsize = 1;
+                           break;
+               case '\v' : octbuf[1] = 'v'; break;
+               case '\t' : octbuf[1] = 't'; break;
+               case '\r' : octbuf[1] = 'r'; break;
+               case '\n' : octbuf[1] = 'n'; break;
+               case '\f' : octbuf[1] = 'f'; break;
+                case '"'  : if (dq == '"')
+                               octbuf[1] = '"';
+                           else
+                               chsize = 1;
+                           break;
+               default:    chsize = my_snprintf(octbuf, sizeof octbuf,
+                               pv < end && isDIGIT((U8)*(pv+readsize))
+                               ? "%c%03o" : "%c%o", esc, c);
+                }
+            } else {
+                chsize = 1;
+            }
+       }
+       if (max && wrote + chsize > max) {
+           break;
+        } else if (chsize > 1) {
+            sv_catpvn(dsv, octbuf, chsize);
+            wrote += chsize;
+       } else {
+           char tmp[2];
+           my_snprintf(tmp, sizeof tmp, "%c", c);
+            sv_catpvn(dsv, tmp, 1);
+           wrote++;
+       }
+        if (flags & PERL_PV_ESCAPE_FIRSTCHAR)
+            break;
+    }
+    if (escaped != NULL)
+        *escaped= pv - str;
+    return SvPVX(dsv);
+}
+
+#endif
+#endif
+
+#ifndef pv_pretty
+#if defined(NEED_pv_pretty)
+static char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags);
+static
+#else
+extern char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags);
+#endif
+
+#ifdef pv_pretty
+#  undef pv_pretty
+#endif
+#define pv_pretty(a,b,c,d,e,f,g) DPPP_(my_pv_pretty)(aTHX_ a,b,c,d,e,f,g)
+#define Perl_pv_pretty DPPP_(my_pv_pretty)
+
+#if defined(NEED_pv_pretty) || defined(NEED_pv_pretty_GLOBAL)
+
+char *
+DPPP_(my_pv_pretty)(pTHX_ SV *dsv, char const * const str, const STRLEN count,
+  const STRLEN max, char const * const start_color, char const * const end_color,
+  const U32 flags)
+{
+    const U8 dq = (flags & PERL_PV_PRETTY_QUOTE) ? '"' : '%';
+    STRLEN escaped;
+
+    if (!(flags & PERL_PV_PRETTY_NOCLEAR))
+       sv_setpvs(dsv, "");
+
+    if (dq == '"')
+        sv_catpvs(dsv, "\"");
+    else if (flags & PERL_PV_PRETTY_LTGT)
+        sv_catpvs(dsv, "<");
+
+    if (start_color != NULL)
+        sv_catpv(dsv, D_PPP_CONSTPV_ARG(start_color));
+
+    pv_escape(dsv, str, count, max, &escaped, flags | PERL_PV_ESCAPE_NOCLEAR);
+
+    if (end_color != NULL)
+        sv_catpv(dsv, D_PPP_CONSTPV_ARG(end_color));
+
+    if (dq == '"')
+       sv_catpvs(dsv, "\"");
+    else if (flags & PERL_PV_PRETTY_LTGT)
+        sv_catpvs(dsv, ">");
+
+    if ((flags & PERL_PV_PRETTY_ELLIPSES) && escaped < count)
+       sv_catpvs(dsv, "...");
+
+    return SvPVX(dsv);
+}
+
+#endif
+#endif
+
+#ifndef pv_display
+#if defined(NEED_pv_display)
+static char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim);
+static
+#else
+extern char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim);
+#endif
+
+#ifdef pv_display
+#  undef pv_display
+#endif
+#define pv_display(a,b,c,d,e) DPPP_(my_pv_display)(aTHX_ a,b,c,d,e)
+#define Perl_pv_display DPPP_(my_pv_display)
+
+#if defined(NEED_pv_display) || defined(NEED_pv_display_GLOBAL)
+
+char *
+DPPP_(my_pv_display)(pTHX_ SV *dsv, const char *pv, STRLEN cur, STRLEN len, STRLEN pvlim)
+{
+    pv_pretty(dsv, pv, cur, pvlim, NULL, NULL, PERL_PV_PRETTY_DUMP);
+    if (len > cur && pv[cur] == '\0')
+       sv_catpvs(dsv, "\\0");
+    return SvPVX(dsv);
+}
+
+#endif
+#endif
+
+#endif /* _P_P_PORTABILITY_H_ */
+
+/* End of File ppport.h */
diff --git a/src/PVE/pvecm b/src/PVE/pvecm
new file mode 100755 (executable)
index 0000000..87bc9f6
--- /dev/null
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use PVE::INotify;
+use PVE::Cluster;
+use PVE::CLI::pvecm;
+
+my $prepare = sub {
+    my $nodename = PVE::INotify::nodename();
+
+    PVE::Cluster::check_cfs_is_mounted();
+    PVE::Cluster::cfs_update();
+
+    # trigger check that we have resolvable name
+    my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
+};
+
+PVE::CLI::pvecm->run_cli_handler(prepare => $prepare, no_rpcenv => 1);
diff --git a/src/PVE/test.pl b/src/PVE/test.pl
new file mode 100755 (executable)
index 0000000..89fb062
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use PVE::IPCC;
+
+if (defined(my $res = PVE::IPCC::ipcc_send_rec(2))) {
+    print "GOT: $res\n";
+} else {
+    die "ipcc_send_rec failed: $!\n";
+}
+exit 0;
+
+my $i = 0;
+    
+
+for($i = 0; $i < 10000; $i++) {
+    print "t1\n";
+    print "c1: " . PVE::IPCC::ipcc_send_rec(1, "adas\0defg") . "\n";
+    print "t1\n";
+}
+
diff --git a/src/PVE/test1.pl b/src/PVE/test1.pl
new file mode 100755 (executable)
index 0000000..717e7e0
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/perl -w -T
+
+use strict;
+
+use PVE::Cluster;
+use PVE::INotify;
+use PVE::AccessControl;
+use Data::Dumper;
+
+
+my $nodename = PVE::INotify::nodename();
+PVE::Cluster::log_msg(1, "ident2", "msg1 Ã¶Ã¤Ã¼");
+PVE::Cluster::log_msg(1, "root\@pam", "msg1 Ã¶Ã¤Ã¼");
+#print PVE::Cluster::get_system_log(undef, 0);
+exit 0;
+
+#print PVE::Cluster::get_system_log(undef, 0);
+
+#PVE::Cluster::cfs_update();
+
+#my $res = PVE::Cluster::get_vmlist();
+#print "TEST1: " . Dumper($res->{ids});
+
+#exit 0;
+
+while (1) {
+
+    
+    print "update start\n";
+    PVE::Cluster::cfs_update();
+    print "update end\n";
+
+    my $res = PVE::Cluster::rrd_dump();
+    print "RRDDATA:" . Dumper($res);
+
+    #my $res = PVE::Cluster::cfs_file_version('user.cfg');
+    #print "VER $res\n";
+
+    sleep(1);
+}
+exit 0;
+
+my $loopcount = 0;
+
+
+while (1) {
+
+    PVE::Cluster::update();
+
+    PVE::Cluster::broadcast_vminfo({ count => $loopcount});
+
+    my $res = PVE::Cluster::get_vminfo($nodename);
+    print "TEST1: " . Dumper($res);
+
+   if (defined($res = PVE::Cluster::get_config("cluster.conf"))) {
+       print "TEST2: " . Dumper($res);
+    } else {
+       warn "get_config failed: $!\n";
+    }
+
+    $loopcount++;
+
+    sleep(2);
+}
+
diff --git a/src/README b/src/README
new file mode 100644 (file)
index 0000000..4d884e0
--- /dev/null
@@ -0,0 +1,377 @@
+Enable/Disable debugging
+========================
+
+# echo "1" >/etc/pve/.debug 
+# echo "0" >/etc/pve/.debug 
+
+Memory leak debugging (valgrind)
+================================
+
+export G_SLICE=always-malloc 
+export G_DEBUG=gc-friendly
+valgrind --leak-check=full ./pmxcfs -f
+
+# pmap <PID>
+# cat /proc/<PID>/maps
+
+Profiling (google-perftools)
+============================
+
+compile with: -lprofiler
+CPUPROFILE=./profile ./pmxcfs -f
+google-pprof --text ./pmxcfs profile 
+google-pprof --gv ./pmxcfs profile 
+
+Proposed file system layout
+============================
+
+The file system is mounted at:
+
+/etc/pve
+
+Files:
+
+cluster.conf
+storage.cfg
+user.cfg
+domains.cfg
+authkey.pub
+
+priv/shadow.cfg
+priv/authkey.key
+
+nodes/${NAME}/pve-ssl.pem
+nodes/${NAME}/priv/pve-ssl.key
+nodes/${NAME}/qemu-server/${VMID}.conf
+nodes/${NAME}/openvz/${VMID}.conf
+
+Symbolic links:
+
+local => nodes/${LOCALNAME}
+qemu-server => nodes/${LOCALNAME}/qemu-server/
+openvz => nodes/${LOCALNAME}/openvz/
+
+Special status files for debugging (JSON):
+.version    => file versions (to detect file modifications)
+.members    => Info about cluster members
+.vmlist     => List of all VMs
+.clusterlog => Cluster log (last 50 entries)
+.rrd        => RRD data (most recent entries)
+
+POSIX Compatibility
+===================
+
+The file system is based on fuse, so the behavior is POSIX like. But
+many feature are simply not implemented, because we do not need them:
+
+    - just normal files, no symbolic links, ...
+    - you can't rename non-empty directories (because this makes it easier 
+      to guarantee that VMIDs are unique).
+    - you can't change file permissions (permissions are based on path)
+    - O_EXCL creates were not atomic (like old NFS)
+    - O_TRUNC creates are not atomic (fuse restriction)
+    - ...
+
+File access rights
+==================
+
+All files/dirs are owned by user 'root' and have group
+'www-data'. Only root has write permissions, but group 'www-data' can
+read most files. Files below the following paths:
+
+ priv/
+ nodes/${NAME}/priv/
+
+are only accessible by root.
+
+SOURCE FILES
+============
+
+src/pmxcfs.c
+
+The final fuse binary which mounts the file system at '/etc/pve' is
+called 'pmxcfs'.
+
+
+src/cfs-plug.c
+src/cfs-plug.h
+
+That files implement some kind of fuse plugins - we can assemble our
+file system using several plugins (like bind mounts).
+
+
+src/cfs-plug-memdb.h
+src/cfs-plug-memdb.c
+src/dcdb.c
+src/dcdb.h
+
+This plugin implements the distributed, replicated file system. All
+file system operations are sent over the wire.
+
+
+src/cfs-plug-link.c
+
+Plugin for symbolic links.
+
+src/cfs-plug-func.c
+
+Plugin to dump data returned from a function. We use this to provide
+status information (for example the .version or .vmlist files)
+
+
+src/cfs-utils.c
+src/cfs-utils.h
+
+Some helper function.
+
+
+src/memdb.c
+src/memdb.h
+
+In memory file system, which writes data back to the disk.
+
+
+src/database.c 
+
+This implements the sqlite backend for memdb.c 
+
+src/server.c
+src/server.h
+
+A simple IPC server based on libqb. Provides fast access to
+configuration and status.
+
+src/status.c
+src/status.h
+
+A simple key/value store. Values are copied to all cluster members.
+
+src/dfsm.c
+src/dfsm.h
+
+Helper to simplify the implementation of a distributed finite state
+machine on top of corosync CPG.
+
+src/loop.c
+src/loop.h
+
+A simple event loop for corosync services.
+
+HOW TO COMPILE AND TEST
+=======================
+
+# ./autogen.sh
+# ./configure
+# make
+
+To test, you need a working corosync installation. First create
+the mount point with:
+
+# mkdir /etc/pve
+
+and create the directory to store the database:
+
+# mkdir /var/lib/pve-cluster/
+
+Then start the fuse file system with:
+
+# ./src/pmxcfs
+
+The distributed file system is accessible under /etc/pve
+
+There is a small test program to dump the database (and the index used
+to compare database contents).
+
+# ./src/testmemdb
+
+To build the Debian package use:
+
+# dpkg-buildpackage -rfakeroot -b -us -uc
+
+Distributed Configuration Database (DCDB)
+===========================================
+
+We want to implement a simple way to distribute small configuration
+files among the cluster on top of corosync CPG.
+
+The set of all configuration files defines the 'state'. That state is
+stored persistently on all members using a backend
+database. Configuration files are usually quite small, and we can even
+set a limit for the file size.
+
+* Backend Database
+
+Each node stores the state using a backend database. That database
+need to have transaction support, because we want to do atomic
+updates. It must also be possible to get a copy/snapshot of the
+current state.
+
+** File Based Backend (not implemented)
+
+Seems possible, but its hard to implement atomic update and snapshots.
+
+** Berkeley Database Backend (not implemented)
+
+The Berkeley DB provides full featured transaction support, including
+atomic commits and snapshot isolation. 
+
+** SQLite Database Backend (currently in use)
+
+This is simpler than BDB. All data is inside a single file. And there
+is a defined way to access that data (SQL). It is also very stable.
+
+We can use the following simple database table:
+
+  INODE PARENT NAME WRITER VERSION SIZE VALUE
+
+We use a global 'version' number (64bit) to uniquely identify the
+current version. This 'version' is incremented on any database
+modification. We also use it as 'inode' number when we create a new
+entry. The 'inode' is the primary key.
+
+** RAM/File Based Backend
+
+If the state is small enough we can hold all data in RAM. Then a
+'snapshot' is a simple copy of the state in RAM. Although all data is
+in RAM, a copy is written to the disk. The idea is that the state in
+RAM is the 'correct' one. If any file/database operations fails the
+saved state can become inconsistent, and the node must trigger a state
+resync operation if that happens.
+
+We can use the DB design from above to store data on disk.
+
+* Comparing States
+
+We need an effective way to compare states and test if they are
+equal. The easiest way is to assign a version number which increase on
+every change. States are equal if they have the same version. Also,
+the version provides a way to determine which state is newer. We can
+gain additional safety by 
+
+  - adding the ID of the last writer for each value
+  - computing a hash for each value
+
+And on partition merge we use that info to compare the version of each
+entry.
+
+* Quorum
+
+Quorum is necessary to modify state. Else we allow read-only access.
+
+* State Transfer to a Joining Process ([Ban], [Bir96, ch. 15.3.2])
+
+We adopt the general mechanism described in [Ban] to avoid making
+copies of the state. This can be achieved by initiating a state
+transfer immediately after a configuration change. We implemented this
+protocol in 'dfsm.c'. It is used by the DCDB implementation 'dcdb.c'.
+
+There are to types of messages:
+
+  - normal: only delivered when the state is synchronized. We queue
+    them until the state is in sync.
+
+  - state transfer: used to implement the state transfer
+
+The following example assumes that 'P' joins, 'Q' and 'R' share the
+same state.
+
+init:
+       P       Q       R
+        c-------c-------c new configuration
+       *       *       * change mode: DFSM_MODE_START_SYNC
+       *       *       * start queuing
+       *       *       * $state[X] = dfsm_get_state_fn()
+       |------->-------> send(DFSM_MESSAGE_STATE, $state[P]) 
+       |<------|------>| send(DFSM_MESSAGE_STATE, $state[Q]) 
+       <-------<-------| send(DFSM_MESSAGE_STATE, $state[R]) 
+       w-------w-------w wait until we have received all states
+       *       *       * dfsm_process_state_update($state[P,Q,R])
+       *       |       | change mode: DFSM_MODE_UPDATE
+       |       *       * change mode: DFSM_MODE_SYNCED
+       |       *       * stop queuing (deliver queue)
+       |       *       | selected Q as leader: send updates 
+       |<------*       | send(DFSM_MESSAGE_UPDATE, $updates) 
+       |<------*       | send(DFSM_MESSAGE_UPDATE_COMPLETE) 
+
+update:
+       P       Q       R
+       *<------|       | record updates: dfsm_process_update_fn() 
+       *<------|-------| queue normal messages 
+       w       |       | wait for DFSM_MESSAGE_UPDATE_COMPLETE
+       *       |       | commit new state: dfsm_commit_fn()
+       *       |       | change mode: DFSM_MODE_SYNCED
+       *       |       | stop queuing (deliver queue)
+
+
+While the general algorithm seems quite easy, there are some pitfalls
+when implementing it using corosync CPG (extended virtual synchrony):
+
+Messages sent in one configuration can be received in a later
+configuration. This is perfect for normal messages, but must not
+happen for state transfer message. We add an unique epoch to all state
+transfer messages, and simply discard messages from other
+configurations.
+
+Configuration change may happen before the protocol finish. This is
+particularly bad when we have already queued messages. Those queued
+messages needs to be considered part of the state (and thus we need
+to make sure that all nodes have exactly the same queue).
+
+A simple solution is to resend all queued messages. We just need to
+make sure that we still have a reasonable order (resend changes the
+order). A sender expects that sent messages are received in the same
+order. We include a 'msg_count' (local to each member) in all 'normal'
+messages, and so we can use that to sort the queue.
+
+A second problem arrives from the fact that we allow synced member to
+continue operation while other members doing state updates. We
+basically use 2 different queues:
+
+  queue 1: Contain messages from 'unsynced' members. This queue is
+  sorted and resent on configuration change. We commit those messages
+  when we get the DFSM_MESSAGE_UPDATE_COMPLETE message.
+
+  queue 2: Contain messages from 'synced' members. This queue is only
+  used by 'unsynced' members, because 'synced' members commits those
+  messages immediately. We can safely discard this queue at
+  configuration change.
+
+File Locking
+============
+
+We implement a simple lock-file based locking mechanism on top of the
+distributed file system. You can create/acquire a lock with:
+
+  $filename = "/etc/pve/priv/lock/<A-LOCK-NAME>";
+  while(!(mkdir $filename)) {
+      (utime 0, 0, $filename); # cfs unlock request
+      sleep(1);
+  }
+  /* got the lock */
+
+If above command succeed, you got the lock for 120 seconds (hard coded
+time limit). The 'mkdir' command is atomic and only succeed if the
+directory does not exist. The 'utime 0 0' triggers a cluster wide
+test, and removes $filename if it is older than 120 seconds. This test
+does not use the mtime stored inside the file system, because there can
+be a time drift between nodes. Instead each node stores the local time when
+it first see a lock file. This time is used to calculate the age of
+the lock.
+
+With version 3.0-17, it is possible to update an existing lock using
+
+  utime 0, time();
+
+This succeeds if run from the same node you created the lock, and updates
+the lock lifetime for another 120 seconds. 
+
+
+References
+==========
+
+[Bir96]        Kenneth P. Birman, Building Secure and Reliable Network Applications,
+       Manning Publications Co., 1996 
+
+[Ban]   Bela Ban, Flexible API for State Transfer in the JavaGroups Toolkit,
+       http://www.jgroups.org/papers/state.ps.gz
+
diff --git a/src/pmxcfs/Makefile b/src/pmxcfs/Makefile
new file mode 100644 (file)
index 0000000..743f949
--- /dev/null
@@ -0,0 +1,62 @@
+
+DEPENDENCIES=libcpg libcmap libquorum libqb glib-2.0 fuse sqlite3 librrd
+
+CC = gcc
+CFLAGS += -std=gnu99
+CFLAGS += -Wall -Werror -Wno-unknown-pragmas -Wno-strict-aliasing
+CFLAGS += -Wpedantic
+CFLAGS += -g -O2 -Wl,-z,relro
+CFLAGS += -I.
+CFLAGS += $(shell pkg-config --cflags ${DEPENDENCIES})
+
+LDFLAGS += $(shell pkg-config --libs ${DEPENDENCIES})
+
+AR = ar
+ARFLAGS = crs
+
+.c.o:
+       $(CC) $(CFLAGS) -c -o $@ $< -MMD -MT $@ -MF $@.d
+
+all: pmxcfs create_pmxcfs_db logtest check_memdb
+
+libpmxcfs.a: logger.o loop.o server.o status.o confdb.o quorum.o dcdb.o dfsm.o
+libpmxcfs.a: cfs-plug.o cfs-plug-memdb.o cfs-plug-link.o cfs-plug-func.o
+libpmxcfs.a: cfs-utils.o memdb.o database.o
+libpmxcfs.a:
+       $(AR) $(ARFLAGS) $@ $^
+
+pmxcfs: pmxcfs.o libpmxcfs.a
+       $(CC) -o $@ $^ $(LDFLAGS)
+
+
+create_pmxcfs_db: create_pmxcfs_db.o libpmxcfs.a
+       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+logtest: logtest.o libpmxcfs.a
+       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+logtest2: logtest2.o libpmxcfs.a
+       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+check_memdb: check_memdb.o libpmxcfs.a
+       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(shell pkg-config --libs check)
+
+pmxcfs.8:
+
+-include /usr/share/pve-doc-generator/pve-doc-generator.mk
+
+.PHONY: install
+install: pmxcfs create_pmxcfs_db pmxcfs.8
+       install -D -m 0755 pmxcfs ${DESTDIR}/usr/bin/pmxcfs
+       install -D -m 0755 create_pmxcfs_db ${DESTDIR}/usr/bin/create_pmxcfs_db
+       install -D pmxcfs.8 ${DESTDIR}/usr/share/man/man8/pmxcfs.8
+
+.PHONY: check
+check: check_memdb
+       ./check_memdb
+
+.PHONY: clean
+clean:
+       rm -f *.o *.o.d *.a pmxcfs create_pmxcfs_db check_memdb logtest pmxcfs.8
+
+-include *.o.d
diff --git a/src/pmxcfs/cfs-ipc-ops.h b/src/pmxcfs/cfs-ipc-ops.h
new file mode 100644 (file)
index 0000000..249308d
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+  Copyright (C) 2018 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Thomas Lamprecht <t.lamprecht@proxmox.com>
+
+*/
+
+#ifndef _PVE_CFS_IPC_OPS_H_
+#define _PVE_CFS_IPC_OPS_H_
+
+#define CFS_IPC_GET_FS_VERSION 1
+
+#define CFS_IPC_GET_CLUSTER_INFO 2
+
+#define CFS_IPC_GET_GUEST_LIST 3
+
+#define CFS_IPC_SET_STATUS 4
+
+#define CFS_IPC_GET_STATUS 5
+
+#define CFS_IPC_GET_CONFIG 6
+
+#define CFS_IPC_LOG_CLUSTER_MSG 7
+
+#define CFS_IPC_GET_CLUSTER_LOG 8
+
+#define CFS_IPC_GET_RRD_DUMP 10
+
+#define CFS_IPC_GET_GUEST_CONFIG_PROPERTY 11
+
+#define CFS_IPC_VERIFY_TOKEN 12
+
+#define CFS_IPC_GET_GUEST_CONFIG_PROPERTIES 13
+
+#endif
diff --git a/src/pmxcfs/cfs-plug-func.c b/src/pmxcfs/cfs-plug-func.c
new file mode 100644 (file)
index 0000000..96d8e8d
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+  Copyright (C) 2011 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+
+#include "cfs-utils.h"
+#include "cfs-plug.h"
+
+static struct cfs_operations cfs_ops;
+
+static cfs_plug_t *cfs_plug_func_lookup_plug(cfs_plug_t *plug, char **path)
+{
+       g_return_val_if_fail(plug != NULL, NULL);
+       g_return_val_if_fail(plug->ops == &cfs_ops, NULL);
+
+       return (!*path || !(*path)[0]) ? plug : NULL;
+}
+
+static void cfs_plug_func_destroy(cfs_plug_t *plug)
+{
+       g_return_if_fail(plug != NULL);
+       g_return_if_fail(plug->ops == &cfs_ops);
+
+       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
+
+       cfs_debug("enter cfs_plug_func_destroy %s", plug->name);
+
+       if (fplug->data)
+               g_free(fplug->data);
+
+       g_free(plug->name);
+
+       g_free(plug);
+}
+
+static int 
+cfs_plug_func_getattr(
+       cfs_plug_t *plug, 
+       const char *path, 
+       struct stat *stbuf)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_func_getattr %s", path);
+
+       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
+
+       memset(stbuf, 0, sizeof(struct stat));
+
+       g_rw_lock_writer_lock(&fplug->data_rw_lock);
+       if (fplug->data)
+               g_free(fplug->data);
+
+       fplug->data = fplug->update_callback(plug);
+
+       stbuf->st_size = fplug->data ? strlen(fplug->data) : 0;
+
+       g_rw_lock_writer_unlock(&fplug->data_rw_lock);
+
+       stbuf->st_mode = fplug->mode;
+       stbuf->st_nlink = 1;
+
+       return 0;
+}
+
+static int 
+cfs_plug_func_read(
+       cfs_plug_t *plug, 
+       const char *path, 
+       char *buf, 
+       size_t size, 
+       off_t offset,
+       struct fuse_file_info *fi)
+{
+       (void) fi;
+
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
+
+       g_rw_lock_reader_lock(&fplug->data_rw_lock);
+       char *data = fplug->data;
+
+       cfs_debug("enter cfs_plug_func_read %s", data);
+
+       if (!data) {
+               g_rw_lock_reader_unlock(&fplug->data_rw_lock);
+               return 0;
+       }
+
+       int len = strlen(data);
+
+       if (offset < len) {
+               if (offset + size > len)
+                       size = len - offset;
+               memcpy(buf, data + offset, size);
+       } else {
+               size = 0;
+       }
+       g_rw_lock_reader_unlock(&fplug->data_rw_lock);
+
+       return size;
+}
+
+static int 
+cfs_plug_func_write(
+       cfs_plug_t *plug, 
+       const char *path, 
+       const char *buf, 
+       size_t size,
+       off_t offset, 
+       struct fuse_file_info *fi)
+{
+       (void) fi;
+
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_func_write");
+
+       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
+
+       if (offset != 0 || !fplug->write_callback)
+               return -EIO;
+
+       return fplug->write_callback(plug, buf, size);
+}
+
+static int 
+cfs_plug_func_truncate(
+       cfs_plug_t *plug, 
+       const char *path, 
+       off_t size)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_plug_func_t *fplug = (cfs_plug_func_t *)plug;
+
+       if (fplug->write_callback)
+               return 0;
+
+       return -EIO;
+}
+
+static int 
+cfs_plug_func_open(
+       cfs_plug_t *plug, 
+       const char *path, 
+       struct fuse_file_info *fi)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_func_open %s", path);
+
+       return 0;
+}
+
+static struct cfs_operations cfs_ops = {
+       .getattr = cfs_plug_func_getattr,
+       .read = cfs_plug_func_read,
+       .write = cfs_plug_func_write,
+       .truncate = cfs_plug_func_truncate,
+       .open = cfs_plug_func_open,
+};
+
+
+cfs_plug_func_t *
+cfs_plug_func_new(
+       const char *name, 
+       mode_t mode,
+       cfs_plug_func_update_data_fn_t update_callback,
+       cfs_plug_func_write_data_fn_t write_callback)
+{
+       g_return_val_if_fail(name != NULL, NULL);
+       g_return_val_if_fail(update_callback != NULL, NULL);
+
+       cfs_plug_func_t *fplug = g_new0(cfs_plug_func_t, 1);
+
+       fplug->plug.ops = &cfs_ops;
+
+       fplug->plug.lookup_plug = cfs_plug_func_lookup_plug;
+       fplug->plug.destroy_plug = cfs_plug_func_destroy;
+
+       fplug->plug.name = g_strdup(name);
+
+       fplug->update_callback = update_callback;
+       fplug->write_callback = write_callback;
+       if (!write_callback)
+               mode = mode & ~0222;
+
+       fplug->mode = S_IFREG | mode;
+
+       return fplug;
+}
+
diff --git a/src/pmxcfs/cfs-plug-link.c b/src/pmxcfs/cfs-plug-link.c
new file mode 100644 (file)
index 0000000..58d442d
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+
+#include "cfs-utils.h"
+#include "cfs-plug.h"
+#include "status.h"
+
+static struct cfs_operations cfs_ops;
+
+static cfs_plug_t *cfs_plug_link_lookup_plug(cfs_plug_t *plug, char **path)
+{
+       g_return_val_if_fail(plug != NULL, NULL);
+       g_return_val_if_fail(plug->ops == &cfs_ops, NULL);
+
+       return (!*path || !(*path)[0]) ? plug : NULL;
+}
+
+static int cfs_plug_link_getattr(cfs_plug_t *plug, const char *path, struct stat *stbuf)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_link_getattr %s", path);
+
+       memset(stbuf, 0, sizeof(struct stat));
+
+       if (cfs_is_quorate()) {
+               stbuf->st_mode = S_IFLNK | 0777;
+       } else {
+               stbuf->st_mode = S_IFLNK | 0555;
+       }
+
+       stbuf->st_nlink = 1;
+
+       return 0;
+}
+
+static int cfs_plug_link_readlink(cfs_plug_t *plug, const char *path, char *buf, size_t max)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_link_readlink %s", path);
+
+       int ret = -EACCES;
+
+       cfs_plug_link_t *lnk = (cfs_plug_link_t *)plug;
+
+       if (!lnk->symlink)
+               goto ret;
+
+       strncpy(buf, lnk->symlink, max);
+       if (max > 0)
+               buf[max - 1]= '\0';
+       ret = 0;
+
+ret:
+       return ret;
+}
+
+static void cfs_plug_link_destroy(cfs_plug_t *plug)
+{
+       g_return_if_fail(plug != NULL);
+       g_return_if_fail(plug->ops == &cfs_ops);
+
+       cfs_plug_link_t *lnk = (cfs_plug_link_t *)plug;
+
+       cfs_debug("enter cfs_plug_link_destroy %s", plug->name);
+
+       g_free(plug->name);
+
+       g_free(lnk->symlink);
+
+       g_free(plug);
+}
+
+static struct cfs_operations cfs_ops = {
+       .getattr = cfs_plug_link_getattr,
+       .readlink = cfs_plug_link_readlink,
+};
+
+
+cfs_plug_link_t *cfs_plug_link_new(const char *name, const char *symlink)
+{
+       g_return_val_if_fail(name != NULL, NULL);
+       g_return_val_if_fail(symlink != NULL, NULL);
+
+       cfs_plug_link_t *lnk = g_new0(cfs_plug_link_t, 1);
+
+       lnk->plug.ops = &cfs_ops;
+
+       lnk->plug.lookup_plug = cfs_plug_link_lookup_plug;
+       lnk->plug.destroy_plug = cfs_plug_link_destroy;
+
+       lnk->plug.name = g_strdup(name);
+
+       lnk->symlink = g_strdup(symlink);
+
+       return lnk;
+}
+
diff --git a/src/pmxcfs/cfs-plug-memdb.c b/src/pmxcfs/cfs-plug-memdb.c
new file mode 100644 (file)
index 0000000..b7a5567
--- /dev/null
@@ -0,0 +1,527 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <arpa/inet.h>
+
+#include "cfs-utils.h"
+#include "cfs-plug-memdb.h"
+#include "dcdb.h"
+#include "status.h"
+
+static struct cfs_operations cfs_ops;
+
+// FIXME: remove openvz stuff for 7.x
+static gboolean 
+name_is_openvz_script(
+       const char *name, 
+       guint32 *vmid_ret)
+{
+       if (!name)
+               return FALSE;
+
+       guint32 vmid = 0;
+       char *end = NULL;
+
+       if (name[0] == 'v' && name[1] == 'p' && name[2] == 's') {
+               end = (char *)name + 3;
+       } else {
+               if (name[0] < '1' || name[0] > '9')
+                       return FALSE;
+
+               vmid = strtoul(name, &end, 10);
+       }
+
+       if (!end || end[0] != '.')
+               return FALSE;
+
+       end++;
+
+       gboolean res = FALSE;
+
+       if (end[0] == 'm' && strcmp(end, "mount") == 0)
+               res = TRUE;
+
+       if (end[0] == 'u' && strcmp(end, "umount") == 0)
+               res = TRUE;
+
+       if (end[0] == 's' && 
+           (strcmp(end, "start") == 0 || strcmp(end, "stop") == 0))
+               res = TRUE;
+
+       if (end[0] == 'p' && 
+           (strcmp(end, "premount") == 0 || strcmp(end, "postumount") == 0))
+               res = TRUE;
+
+
+       if (res && vmid_ret)
+               *vmid_ret = vmid;
+
+       return res;
+}
+
+static void tree_entry_stat(memdb_tree_entry_t *te, struct stat *stbuf, gboolean quorate)
+{
+       g_return_if_fail(te != NULL);
+       g_return_if_fail(stbuf != NULL);
+
+       if (te->type == DT_DIR) {
+               stbuf->st_mode = S_IFDIR | (quorate ? 0777 : 0555);
+               stbuf->st_nlink = 2;
+       } else {
+               stbuf->st_mode = S_IFREG | (quorate ? 0666 : 0444);
+               stbuf->st_nlink = 1;
+               // FIXME: remove openvz stuff for 7.x
+               if (name_is_openvz_script(te->name, NULL)) {
+                       stbuf->st_mode |= S_IXUSR;
+               }
+       } 
+               
+       stbuf->st_size = te->size;
+       stbuf->st_blocks = 
+               (stbuf->st_size + MEMDB_BLOCKSIZE -1)/MEMDB_BLOCKSIZE;
+       stbuf->st_atime = te->mtime;
+       stbuf->st_mtime = te->mtime;
+       stbuf->st_ctime = te->mtime;
+}
+
+static int cfs_plug_memdb_getattr(cfs_plug_t *plug, const char *path, struct stat *stbuf)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
+
+       memset(stbuf, 0, sizeof(struct stat));
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       memdb_tree_entry_t *te = memdb_getattr(mdb->memdb, path);
+
+       if (te) {
+               tree_entry_stat(te, stbuf, cfs_is_quorate());
+               memdb_tree_entry_free(te);
+               return 0;
+       }
+
+       return  -ENOENT;
+}
+
+static int cfs_plug_memdb_readdir(
+       cfs_plug_t *plug, 
+       const char *path, 
+       void *buf, 
+       fuse_fill_dir_t filler, 
+       off_t offset, 
+       struct fuse_file_info *fi)
+{
+       (void) offset;
+       (void) fi;
+
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(filler != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       GList *dirlist = memdb_readdir(mdb->memdb, path);
+
+       if (dirlist) {
+
+               filler(buf, ".", NULL, 0);
+               filler(buf, "..", NULL, 0);
+
+               struct stat stbuf;
+               memset(&stbuf, 0, sizeof(struct stat));
+            
+               GList *l = dirlist;
+               while (l) {
+                       memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
+
+                       tree_entry_stat(te, &stbuf, 0);
+                       filler(buf, te->name, &stbuf, 0);
+                       l = g_list_next(l);
+               }
+
+               memdb_dirlist_free(dirlist);
+       }
+
+       return 0;
+}
+
+static int cfs_plug_memdb_open(cfs_plug_t *plug, const char *path, struct fuse_file_info *fi)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
+
+       memdb_tree_entry_t *te;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       if ((te = memdb_getattr(mdb->memdb, path))) {
+               memdb_tree_entry_free(te);
+       } else 
+               return -ENOENT;
+
+       return 0;
+}
+
+static int cfs_plug_memdb_read(
+       cfs_plug_t *plug, 
+       const char *path, 
+       char *buf, 
+       size_t size, 
+       off_t offset,
+       struct fuse_file_info *fi)
+{
+       (void) fi;
+
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+
+       int len;
+       gpointer data = NULL;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       len = memdb_read(mdb->memdb, path, &data);
+       if (len < 0)
+               return len;
+
+       if (offset < len) {
+               if (offset + size > len)
+                       size = len - offset;
+               memcpy(buf, (uint8_t *) data + offset, size);
+       } else {
+               size = 0;
+       }
+
+       if (data)
+               g_free(data);
+
+       return size;
+}
+
+static int cfs_plug_memdb_write(
+       cfs_plug_t *plug, 
+       const char *path, 
+       const char *buf, 
+       size_t size,
+       off_t offset, 
+       struct fuse_file_info *fi)
+{
+       (void) fi;
+
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+
+       int res;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       if (mdb->dfsm) {
+               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_WRITE, path, NULL, buf,
+                                            size, offset, 0);
+       } else {
+               uint32_t ctime = time(NULL);
+               res = memdb_write(mdb->memdb, path, 0, ctime, buf, size, offset, 0);
+       }
+
+       return res;
+}
+
+static int cfs_plug_memdb_truncate(cfs_plug_t *plug, const char *path, off_t size)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+
+       int res;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       if (mdb->dfsm) {
+               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_WRITE, path, NULL, NULL,
+                                            0, size, 1);
+       } else {
+               uint32_t ctime = time(NULL);
+               res = memdb_write(mdb->memdb, path, 0, ctime, NULL, 0, size, 1);
+       }
+
+       return res;
+}
+
+static int cfs_plug_memdb_create (
+       cfs_plug_t *plug, 
+       const char *path, 
+       mode_t mode, 
+       struct fuse_file_info *fi)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
+
+       int res;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       if (mdb->dfsm) {
+               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_CREATE, path, 
+                                            NULL, NULL, 0, 0, 0);
+       } else {
+               uint32_t ctime = time(NULL);
+
+               res = memdb_create(mdb->memdb, path, 0, ctime);
+       }
+
+       return res;
+}
+
+static int cfs_plug_memdb_mkdir(cfs_plug_t *plug, const char *path, mode_t mode)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+
+       int res;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       if (mdb->dfsm) {
+               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_MKDIR, path, 
+                                            NULL, NULL, 0, 0, 0);
+       } else {
+               uint32_t ctime = time(NULL);
+               res = memdb_mkdir(mdb->memdb, path, 0, ctime);
+       }
+
+       return res;
+}
+
+static int cfs_plug_memdb_rmdir(cfs_plug_t *plug, const char *path)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+
+       int res;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       if (mdb->dfsm) {
+               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_DELETE, path, 
+                                            NULL, NULL, 0, 0, 0);
+       } else {
+               uint32_t ctime = time(NULL);
+               res = memdb_delete(mdb->memdb, path, 0, ctime);
+       }
+
+       return res;
+}
+
+static int cfs_plug_memdb_rename(cfs_plug_t *plug, const char *from, const char *to)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(from != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(to != NULL, PARAM_CHECK_ERRNO);
+
+       int res;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       if (mdb->dfsm) {
+               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_RENAME, from, to, 
+                                            NULL, 0, 0, 0);
+       } else {
+               uint32_t ctime = time(NULL);
+               res = memdb_rename(mdb->memdb, from, to, 0, ctime);
+       }
+
+       return res;
+}
+
+static int cfs_plug_memdb_unlink(cfs_plug_t *plug, const char *path)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+
+       int res;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       if (mdb->dfsm) {
+               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_DELETE, path, 
+                                            NULL, NULL, 0, 0, 0);
+       } else {
+               uint32_t ctime = time(NULL);
+               res = memdb_delete(mdb->memdb, path, 0, ctime);
+       }
+
+       return res;
+}
+
+static int cfs_plug_memdb_utimens(
+       cfs_plug_t *plug, 
+       const char *path, 
+       const struct timespec tv[2])
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(tv != NULL, PARAM_CHECK_ERRNO);
+
+       int res;
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       res = -EIO;
+
+       memdb_tree_entry_t *te = memdb_getattr(mdb->memdb, path);
+       uint32_t mtime = tv[1].tv_sec;
+
+       gboolean unlock_req = FALSE;
+       guchar csum[32];
+
+       if (te && mtime == 0 && te->type == DT_DIR &&
+           path_is_lockdir(path)) {
+               unlock_req = TRUE;
+       }
+
+       if (mdb->dfsm) {
+               if (unlock_req && memdb_tree_entry_csum(te, csum))
+                       dcdb_send_unlock(mdb->dfsm, path, csum, TRUE);
+           
+               res = dcdb_send_fuse_message(mdb->dfsm, DCDB_MESSAGE_CFS_MTIME, path, 
+                                            NULL, NULL, 0, mtime, 0);
+       } else {
+               uint32_t ctime = time(NULL);
+               if (unlock_req && memdb_tree_entry_csum(te, csum) &&
+                   memdb_lock_expired(mdb->memdb, path, csum)) {
+                       res = memdb_delete(mdb->memdb, path, 0, ctime);
+               } else {
+                       res = memdb_mtime(mdb->memdb, path, 0, mtime);
+               }
+       }
+
+       memdb_tree_entry_free(te);
+
+       return res;
+}
+
+static int cfs_plug_memdb_statfs(cfs_plug_t *plug, const char *path, struct statvfs *stbuf)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_plug_memdb_t *mdb = (cfs_plug_memdb_t *)plug;
+
+       return memdb_statfs(mdb->memdb, stbuf);
+}
+
+static void cfs_plug_memdb_destroy(cfs_plug_t *plug)
+{
+       g_return_if_fail(plug != NULL);
+       g_return_if_fail(plug->ops == &cfs_ops);
+       
+       g_free(plug->name);
+
+       g_free(plug);
+}
+
+static cfs_plug_t *cfs_plug_memdb_lookup_plug(cfs_plug_t *plug, char **path)
+{
+       g_return_val_if_fail(plug != NULL, NULL);
+       g_return_val_if_fail(plug->ops == &cfs_ops, NULL);
+
+       return plug;
+}
+
+static struct cfs_operations cfs_ops = {
+       .getattr = cfs_plug_memdb_getattr,
+       .readdir = cfs_plug_memdb_readdir,
+       .open = cfs_plug_memdb_open,
+       .create = cfs_plug_memdb_create,
+       .read   = cfs_plug_memdb_read,
+       .write = cfs_plug_memdb_write,
+       .truncate = cfs_plug_memdb_truncate,
+       .unlink = cfs_plug_memdb_unlink,
+       .mkdir = cfs_plug_memdb_mkdir,
+       .rmdir = cfs_plug_memdb_rmdir,
+       .rename = cfs_plug_memdb_rename,
+       .utimens = cfs_plug_memdb_utimens,
+       .statfs = cfs_plug_memdb_statfs,
+#ifdef HAS_CFS_PLUG_MEMDB_LOCK
+       .lock = cfs_plug_memdb_lock,
+#endif
+};
+
+cfs_plug_memdb_t *cfs_plug_memdb_new(
+       const char *name, 
+       memdb_t *memdb,
+       dfsm_t *dfsm)
+{
+       g_return_val_if_fail(name != NULL, NULL);
+       g_return_val_if_fail(memdb != NULL, NULL);
+
+       cfs_plug_memdb_t *mdb = g_new0(cfs_plug_memdb_t, 1);
+
+       g_return_val_if_fail(mdb != NULL, NULL);
+
+       mdb->plug.ops = &cfs_ops;
+
+       mdb->plug.lookup_plug = cfs_plug_memdb_lookup_plug;
+
+       mdb->plug.destroy_plug = cfs_plug_memdb_destroy;
+
+       mdb->plug.name = g_strdup(name);
+
+       mdb->memdb = memdb;
+
+       mdb->dfsm = dfsm;
+
+       return mdb;
+}
diff --git a/src/pmxcfs/cfs-plug-memdb.h b/src/pmxcfs/cfs-plug-memdb.h
new file mode 100644 (file)
index 0000000..936272c
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_CFS_PLUG_MEMDB_H_
+#define _PVE_CFS_PLUG_MEMDB_H_
+
+#include <unistd.h>
+#include <fcntl.h>
+#include "cfs-plug.h"
+
+#include "dfsm.h"
+#include "memdb.h"
+
+
+typedef struct {
+       cfs_plug_t plug;
+       memdb_t *memdb;
+       dfsm_t *dfsm;
+} cfs_plug_memdb_t;
+
+cfs_plug_memdb_t *cfs_plug_memdb_new(
+       const char *name, 
+       memdb_t *memdb, 
+       dfsm_t *dfsm);
+
+#endif /* _PVE_CFS_PLUG_MEMDB_H_ */
diff --git a/src/pmxcfs/cfs-plug.c b/src/pmxcfs/cfs-plug.c
new file mode 100644 (file)
index 0000000..108ed6b
--- /dev/null
@@ -0,0 +1,553 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+
+#include "cfs-utils.h"
+#include "cfs-plug.h"
+
+static struct cfs_operations cfs_ops;
+
+static cfs_plug_t *cfs_plug_base_lookup_plug(cfs_plug_t *plug, char **path)
+{
+       g_return_val_if_fail(plug != NULL, NULL);
+       g_return_val_if_fail(plug->ops == &cfs_ops, NULL);
+       g_return_val_if_fail(path != NULL, NULL);
+
+       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
+
+       g_return_val_if_fail(bplug->entries != NULL, NULL);
+
+       cfs_debug("cfs_plug_base_lookup_plug %s", *path);
+
+       if (!*path || !(*path)[0])
+               return plug;
+
+       char *name = strsep(path, "/");
+
+       cfs_debug("cfs_plug_base_lookup_plug name = %s new path = %s", name, *path);
+
+       cfs_plug_t *sub;
+
+       if (!(sub = (cfs_plug_t *)g_hash_table_lookup(bplug->entries, name))) {
+               /* revert strsep modification */
+               if (*path) (*path)[-1] = '/';
+               *path = name;
+               return plug;
+       }
+
+       if ((sub = sub->lookup_plug(sub, path)))
+               return sub;
+
+       *path = NULL;
+       return NULL;
+}
+
+static int cfs_plug_base_getattr(cfs_plug_t *plug, const char *path, struct stat *stbuf)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_getattr %s", path);
+
+       int ret = -EACCES;
+
+       memset(stbuf, 0, sizeof(struct stat));
+
+       if (*path) {
+               cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+               if (base && base->ops && base->ops->getattr)
+                       ret = base->ops->getattr(base, path, stbuf);
+               goto ret;
+       }
+
+       stbuf->st_mode = S_IFDIR | 0777;
+       stbuf->st_nlink = 2;
+       ret = 0;
+
+ret:
+       cfs_debug("leave cfs_plug_base_getattr %s", path);
+       return ret;
+}
+
+
+struct hash_filler {
+       void *buf;
+       GHashTable *entries;
+       fuse_fill_dir_t filler;
+};
+
+static int tmp_hash_filler (
+       void *buf, 
+       const char *name,
+       const struct stat *stbuf, 
+       off_t off) {
+
+       struct hash_filler *hf = (struct hash_filler *)buf;
+
+       if (hf->entries && g_hash_table_lookup(hf->entries, name))
+               return 0;
+               
+       if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] ==  0)))
+               return 0;
+
+       hf->filler(hf->buf, name, stbuf, off);
+
+       return 0;
+}
+
+static int cfs_plug_base_readdir(cfs_plug_t *plug, const char *path, void *buf, fuse_fill_dir_t filler,
+                                off_t offset, struct fuse_file_info *fi)
+{
+       (void) offset;
+       (void) fi;
+
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(filler != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
+
+       cfs_debug("enter cfs_plug_base_readdir %s", path);
+
+       int ret = -EACCES;
+
+       filler(buf, ".", NULL, 0);
+       filler(buf, "..", NULL, 0);
+
+       if (!path[0]) {
+               GHashTableIter iter;
+               gpointer key, value;
+
+               g_hash_table_iter_init (&iter, bplug->entries);
+
+               while (g_hash_table_iter_next (&iter, &key, &value)) {
+                       filler(buf, key, NULL, 0);
+               }
+       }
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->readdir) {
+               struct hash_filler hf = { 
+                       .buf = buf, 
+                       .filler = filler,
+                       .entries = NULL
+               };
+
+               if (!path[0])
+                       hf.entries = bplug->entries;
+
+               ret = base->ops->readdir(base, path, &hf, tmp_hash_filler, 0, fi);
+
+       } else {
+               ret = 0;
+       }       
+       
+       return ret;
+}
+
+static int cfs_plug_base_mkdir(cfs_plug_t *plug, const char *path, mode_t mode)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_mkdir %s", path);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (*path && base && base->ops && base->ops->mkdir)
+               ret = base->ops->mkdir(base, path, mode);
+
+       return ret;
+}
+
+static int cfs_plug_base_rmdir(cfs_plug_t *plug, const char *path)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_rmdir %s", path);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (*path && base && base->ops && base->ops->rmdir)
+               ret = base->ops->rmdir(base, path);
+
+       return ret;
+}
+
+static int cfs_plug_base_rename(cfs_plug_t *plug, const char *from, const char *to)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(from != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(to != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_rename from %s to %s", from, to);
+
+       int ret = -EACCES;
+       
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->rename)
+               ret = base->ops->rename(base, from, to);
+
+       return ret;
+}
+
+static int cfs_plug_base_open(cfs_plug_t *plug, const char *path, struct fuse_file_info *fi)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_open %s", path);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->open)
+               ret =  base->ops->open(base, path, fi);
+
+       return ret;
+}
+
+static int cfs_plug_base_read(cfs_plug_t *plug, const char *path, char *buf, 
+                             size_t size, off_t offset, struct fuse_file_info *fi)
+{
+       (void) fi;
+
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_read %s %zu %jd", path, size, offset);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->read)
+               ret = base->ops->read(base, path, buf, size, offset, fi);
+
+       return ret;
+}
+
+static int cfs_plug_base_write(cfs_plug_t *plug, const char *path, const char *buf, 
+                              size_t size, off_t offset, struct fuse_file_info *fi)
+{
+       (void) fi;
+
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_write %s %zu %jd", path, size, offset);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->write)
+               ret = base->ops->write(base, path, buf, size, offset, fi);
+
+       return ret;
+}
+
+static int cfs_plug_base_truncate(cfs_plug_t *plug, const char *path, off_t size)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_truncate %s %jd", path, size);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->truncate)
+               ret = base->ops->truncate(base, path, size);
+
+       return ret;
+}
+
+static int cfs_plug_base_create(cfs_plug_t *plug, const char *path, mode_t mode, 
+                               struct fuse_file_info *fi)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(fi != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_create %s", path);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->create)
+               ret = base->ops->create(base, path, mode, fi);
+
+       return ret;
+}
+
+static int cfs_plug_base_unlink(cfs_plug_t *plug, const char *path)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_unlink %s", path);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->unlink)
+               ret = base->ops->unlink(base, path);
+
+       return ret;
+}
+
+static int cfs_plug_base_readlink(cfs_plug_t *plug, const char *path, char *buf, size_t max)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(buf != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_readlink %s", path);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->readlink)
+               ret = base->ops->readlink(base, path, buf, max);
+
+       return ret;
+}
+
+static int cfs_plug_base_utimens(cfs_plug_t *plug, const char *path, const struct timespec tv[2])
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(tv != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_utimes %s", path);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->utimens)
+               ret = base->ops->utimens(base, path, tv);
+
+       return ret;
+}
+
+static int cfs_plug_base_statfs(cfs_plug_t *plug, const char *path, struct statvfs *stbuf)
+{
+       g_return_val_if_fail(plug != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(plug->ops == &cfs_ops, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(path != NULL, PARAM_CHECK_ERRNO);
+       g_return_val_if_fail(stbuf != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_plug_base_statfs %s", path);
+
+       int ret = -EACCES;
+
+       cfs_plug_t *base = ((cfs_plug_base_t *)plug)->base;
+
+       if (base && base->ops && base->ops->statfs)
+               ret = base->ops->statfs(base, path, stbuf);
+
+       return ret;
+}
+
+static gboolean plug_remove_func(
+       gpointer key,
+       gpointer value,
+       gpointer user_data)
+{
+       cfs_plug_t *plug = (cfs_plug_t *)value;
+
+       if (plug && plug->destroy_plug)
+               plug->destroy_plug(plug);
+
+       return TRUE;
+}
+
+static void cfs_plug_base_destroy(cfs_plug_t *plug)
+{
+       g_return_if_fail(plug != NULL);
+       g_return_if_fail(plug->ops == &cfs_ops);
+
+       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
+
+       cfs_debug("enter cfs_plug_base_destroy %s", plug->name);
+
+       if (bplug->entries) {
+               g_hash_table_foreach_remove(bplug->entries, plug_remove_func, NULL);
+               g_hash_table_destroy(bplug->entries);
+       }
+
+       if (bplug->base && bplug->base->destroy_plug) {
+               bplug->base->destroy_plug(bplug->base);
+       }
+
+       g_free(plug->name);
+
+       g_free(plug);
+}
+
+static void cfs_plug_base_start_workers(cfs_plug_t *plug)
+{
+       g_return_if_fail(plug != NULL);
+       g_return_if_fail(plug->ops == &cfs_ops);
+
+       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_hash_table_iter_init (&iter, bplug->entries);
+
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+
+               cfs_plug_t *p = (cfs_plug_t *)value;
+               
+               if (p->start_workers)
+                       p->start_workers(p);
+
+       }
+
+       if (bplug->base && bplug->base->start_workers) {
+               bplug->base->start_workers(bplug->base);
+       }
+
+}
+
+static void cfs_plug_base_stop_workers(cfs_plug_t *plug)
+{
+       g_return_if_fail(plug != NULL);
+       g_return_if_fail(plug->ops == &cfs_ops);
+
+       cfs_plug_base_t *bplug = (cfs_plug_base_t *)plug;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_hash_table_iter_init (&iter, bplug->entries);
+
+       if (bplug->base && bplug->base->stop_workers) {
+               bplug->base->stop_workers(bplug->base);
+       }
+
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+
+               cfs_plug_t *p = (cfs_plug_t *)value;
+               
+               if (p->stop_workers)
+                       p->stop_workers(p);
+
+       }
+}
+
+static struct cfs_operations cfs_ops = {
+       .getattr = cfs_plug_base_getattr,
+       .create = cfs_plug_base_create,
+       .open   = cfs_plug_base_open,
+       .read   = cfs_plug_base_read,
+       .write = cfs_plug_base_write,
+       .truncate = cfs_plug_base_truncate,
+       .unlink = cfs_plug_base_unlink,
+       .readdir = cfs_plug_base_readdir,
+       .mkdir = cfs_plug_base_mkdir,
+       .rmdir = cfs_plug_base_rmdir,
+       .rename = cfs_plug_base_rename,
+       .readlink = cfs_plug_base_readlink,
+       .utimens = cfs_plug_base_utimens,
+       .statfs = cfs_plug_base_statfs,
+};
+
+cfs_plug_base_t *cfs_plug_base_new(const char *name, cfs_plug_t *base)
+{
+       g_return_val_if_fail(name != NULL, NULL);
+       g_return_val_if_fail(base != NULL, NULL);
+
+       cfs_plug_base_t *plug = g_new0(cfs_plug_base_t, 1);
+
+       plug->plug.lookup_plug = cfs_plug_base_lookup_plug;
+       plug->plug.destroy_plug = cfs_plug_base_destroy;
+       plug->plug.start_workers = cfs_plug_base_start_workers;
+       plug->plug.stop_workers = cfs_plug_base_stop_workers;
+
+       plug->entries =  g_hash_table_new(g_str_hash, g_str_equal);
+
+       plug->plug.name = g_strdup(name);
+
+       plug->plug.ops = &cfs_ops;
+
+       plug->base = base;
+
+       return plug;
+}
+
+void cfs_plug_base_insert(cfs_plug_base_t *bplug, cfs_plug_t *sub)
+{
+       g_return_if_fail(bplug != NULL);
+       g_return_if_fail(sub != NULL);
+       g_return_if_fail(sub->name != NULL);
+
+       g_hash_table_replace(bplug->entries, sub->name, sub);
+}
diff --git a/src/pmxcfs/cfs-plug.h b/src/pmxcfs/cfs-plug.h
new file mode 100644 (file)
index 0000000..6fd2635
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_CFS_PLUG_H_
+#define _PVE_CFS_PLUG_H_
+
+#define FUSE_USE_VERSION 26
+
+#include <errno.h>
+#include <fuse.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define PARAM_CHECK_ERRNO -EREMOTEIO
+
+typedef struct cfs_plug cfs_plug_t;
+
+struct cfs_operations {
+       int (*getattr) (cfs_plug_t *, const char *, struct stat *);
+       int (*readlink) (cfs_plug_t *, const char *, char *, size_t);
+       int (*mkdir) (cfs_plug_t *, const char *, mode_t);
+       int (*unlink) (cfs_plug_t *, const char *);
+       int (*rmdir) (cfs_plug_t *, const char *);
+       int (*rename) (cfs_plug_t *, const char *, const char *);
+       int (*truncate) (cfs_plug_t *, const char *, off_t);
+       int (*open) (cfs_plug_t *, const char *, struct fuse_file_info *);
+       int (*read) (cfs_plug_t *, const char *, char *, size_t, off_t,
+                    struct fuse_file_info *);
+       int (*write) (cfs_plug_t *, const char *, const char *, size_t, off_t,
+                     struct fuse_file_info *);
+       int (*readdir) (cfs_plug_t *, const char *, void *, fuse_fill_dir_t, off_t,
+                       struct fuse_file_info *);
+       int (*create) (cfs_plug_t *, const char *, mode_t, struct fuse_file_info *);
+       int (*utimens) (cfs_plug_t *, const char *, const struct timespec tv[2]);
+       int (*statfs) (cfs_plug_t *, const char *, struct statvfs *);
+};
+
+struct cfs_plug {
+       struct cfs_operations *ops;
+       cfs_plug_t *(*lookup_plug)(cfs_plug_t *plug, char **path);
+       void (*destroy_plug) (cfs_plug_t *plug);
+       void (*start_workers) (cfs_plug_t *plug);
+       void (*stop_workers) (cfs_plug_t *plug);
+
+       char *name;
+};
+
+typedef struct {
+       cfs_plug_t plug;
+       cfs_plug_t *base;
+       GHashTable *entries;
+} cfs_plug_base_t;
+
+typedef struct {
+       cfs_plug_t plug;
+       char *symlink;
+} cfs_plug_link_t;
+
+typedef char *(*cfs_plug_func_update_data_fn_t)(cfs_plug_t *plug);
+typedef int (*cfs_plug_func_write_data_fn_t)(
+       cfs_plug_t *plug, 
+       const char *buf,
+       size_t size);
+
+typedef struct {
+       cfs_plug_t plug;
+       char *data;
+       GRWLock data_rw_lock;
+       mode_t mode;
+       cfs_plug_func_update_data_fn_t update_callback;
+       cfs_plug_func_write_data_fn_t write_callback;
+} cfs_plug_func_t;
+
+cfs_plug_base_t *cfs_plug_base_new(const char *name, cfs_plug_t *base);
+void cfs_plug_base_insert(cfs_plug_base_t *base, cfs_plug_t *sub);
+
+cfs_plug_link_t *cfs_plug_link_new(const char *name, const char *symlink);
+cfs_plug_func_t *cfs_plug_func_new(
+       const char *name, 
+       mode_t mode,
+       cfs_plug_func_update_data_fn_t update_callback,
+       cfs_plug_func_write_data_fn_t write_callback);
+
+
+#endif /* _PVE_CFS_PLUG_H_ */
diff --git a/src/pmxcfs/cfs-utils.c b/src/pmxcfs/cfs-utils.c
new file mode 100644 (file)
index 0000000..5770833
--- /dev/null
@@ -0,0 +1,336 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <utime.h>
+#include <sys/stat.h>
+#include <glib.h>
+#include <qb/qblog.h>
+
+#include "cfs-utils.h"
+
+static const char * hexchar = "0123456789abcdef";
+
+/* convert utf8 to json and syslog compatible ascii */
+void
+utf8_to_ascii(
+       char *buf, 
+       int bufsize, 
+       const char *msg, 
+       gboolean quotequote)
+{
+       g_return_if_fail(buf != NULL);
+
+       *buf = 0;
+
+       g_return_if_fail(bufsize > 10);
+
+       const char *p = msg;
+       char *d = buf;
+       char *end = buf + bufsize - 7;
+
+       if (!g_utf8_validate(msg, -1, NULL)) {
+               while (*p && d < end) {
+                       char c = *p++;
+                       if (c == 34 && quotequote) {
+                               *d++ = '\\';
+                               *d++ = '"';
+                       } else if (c >= 0 && c < 32) {
+                               *d++ = '#';
+                               *d++ = '0';
+                               *(d+1) = hexchar[c % 10]; c = c / 10;
+                               *d = hexchar[c % 10];
+                               d += 2;
+                       } else if (c >= 32 && c < 127) {
+                               *d++ = c;       
+                       } else {
+                               *d++ = '?';
+                       }
+               }
+               *d = 0;
+               return;
+       }
+
+       while (*p && d < end) {
+               gunichar u = g_utf8_get_char(p);
+               if (u == 34 && quotequote) {
+                       *d++ = '\\';
+                       *d++ = '"';
+               } else if (u < 32 || u == 127) {
+                       *d++ = '#';
+                       *(d+2) = hexchar[u % 10]; u = u / 10;
+                       *(d+1) = hexchar[u % 10]; u = u / 10;
+                       *d = hexchar[u % 10];
+                       d += 3;
+               } else if (u < 127) {
+                       *d++ = u;
+               } else if (u < 65536) {
+                       *d++ = '\\';
+                       *d++ = 'u';
+                       *(d+3) = hexchar[u&0xf]; u = u >> 4;
+                       *(d+2) = hexchar[u&0xf]; u = u >> 4;
+                       *(d+1) = hexchar[u&0xf]; u = u >> 4;
+                       *d = hexchar[u&0xf];
+                       d += 4;
+               } else {
+                       /* we simply ignore this */
+               }
+               p = g_utf8_next_char(p);
+       }
+       *d = 0;
+}
+
+void 
+cfs_log(
+       const gchar *log_domain,
+       GLogLevelFlags log_level,
+       const char *file,
+       int         line,
+       const char  *func,
+       const gchar    *format,
+       ...)
+{
+       gint level; 
+
+       switch (log_level & G_LOG_LEVEL_MASK) { 
+       case G_LOG_LEVEL_ERROR: 
+               level=LOG_ERR; 
+               break; 
+       case G_LOG_LEVEL_CRITICAL: 
+               level=LOG_CRIT; 
+               break; 
+       case G_LOG_LEVEL_WARNING: 
+               level=LOG_WARNING; 
+               break; 
+       case G_LOG_LEVEL_MESSAGE: 
+               level=LOG_NOTICE; 
+               break; 
+       case G_LOG_LEVEL_INFO: 
+               level=LOG_INFO; 
+               break; 
+       case G_LOG_LEVEL_DEBUG: 
+               level=LOG_DEBUG;   
+               if (!cfs.debug)
+                       return;
+               break; 
+       default:  
+               level=LOG_INFO; 
+       } 
+
+       va_list args;
+
+       va_start (args, format);
+       char *orgmsg = g_strdup_vprintf (format, args);
+       va_end (args);
+
+       char msg[8192];
+       utf8_to_ascii(msg, sizeof(msg), orgmsg, FALSE);
+
+       uint32_t tag = g_quark_from_string(log_domain);
+
+       qb_log_from_external_source(func, file, "%s", level, line, tag, msg);
+
+       g_free(orgmsg);
+}
+
+guint64 
+cluster_config_version(
+       const gpointer config_data, 
+       gsize config_length)
+{
+       GRegex *regex;
+       GMatchInfo *match_info;
+       guint64 version = 0;
+
+       regex = g_regex_new ("config_version\\s*:\\s*(\\d+)", 0, 0, NULL);
+       g_regex_match_full(regex, config_data, config_length, 0, 0, &match_info, NULL);
+       if (g_match_info_matches (match_info)) {
+               gchar *word = g_match_info_fetch (match_info, 1);
+               if (strlen(word)) {
+                       version = strtoull(word, NULL, 10);
+               }
+               g_free (word);
+       }
+       g_match_info_free (match_info);
+       g_regex_unref (regex);
+
+       return version;
+}
+
+ssize_t 
+safe_read(
+       int fd, 
+       char *buf, 
+       size_t count)
+{
+  ssize_t n;
+
+  do {
+    n = read(fd, buf, count);
+  } while (n < 0 && errno == EINTR);
+
+  return n;
+}
+
+gboolean 
+full_write(
+       int fd, 
+       const char *buf, 
+       size_t len)
+{
+       size_t total;
+
+       total = 0;
+
+       while (len > 0) {
+               ssize_t n;
+               do {
+                       n = write(fd, buf, len);
+               } while (n < 0 && errno == EINTR);
+               
+               if (n < 0)
+                       break;
+               
+               buf += n;
+               total += n;
+               len -= n;
+       }
+
+       return (len == 0);
+}
+
+gboolean 
+atomic_write_file(
+       const char *filename, 
+       gconstpointer data, 
+       size_t len, 
+       mode_t mode, 
+       gid_t gid)
+{
+       g_return_val_if_fail(filename != NULL, FALSE);
+       g_return_val_if_fail(len == 0 || data != NULL, FALSE);
+
+       gboolean res = TRUE;
+
+       char *tmp_name = g_strdup_printf ("%s.XXXXXX", filename);
+       int fd = mkstemp(tmp_name);
+       if (fd == -1) {
+               cfs_critical("Failed to create file '%s': %s", tmp_name, g_strerror(errno));
+               res = FALSE;
+               goto ret;
+       }
+
+       if (fchown(fd, 0, gid) == -1) {
+               cfs_critical("Failed to change group of file '%s': %s", tmp_name, g_strerror(errno));
+               close(fd);
+               goto fail;
+       }
+
+       if (fchmod(fd, mode) == -1) {
+               cfs_critical("Failed to change mode of file '%s': %s", tmp_name, g_strerror(errno));
+               close(fd);
+               goto fail;
+       }
+
+       if (len && !full_write(fd, data, len)) {
+               cfs_critical("Failed to write file '%s': %s", tmp_name, g_strerror(errno));
+               close(fd);
+               goto fail;
+       }
+
+       if (close(fd) == -1) {
+               cfs_critical("Failed to close file '%s': %s", tmp_name, g_strerror(errno));
+               goto fail;
+       }
+
+       if (rename(tmp_name, filename) == -1) {
+               cfs_critical("Failed to rename file from '%s' to '%s': %s", 
+                          tmp_name, filename, g_strerror(errno));
+               goto fail;
+       }
+ret:
+       g_free (tmp_name);
+
+       return res;
+
+fail:
+       unlink(tmp_name);
+
+       res = FALSE;
+
+       goto ret;
+}
+
+gboolean
+path_is_private(const char *path)
+{
+       while (*path == '/') path++;
+
+       if ((strncmp(path, "priv", 4) == 0) && (path[4] == 0 || path[4] == '/')) {
+               return TRUE;
+       } else {
+               if (strncmp(path, "nodes/", 6) == 0) {
+                       const char *tmp = path + 6;
+                       while(*tmp && *tmp != '/') tmp++;
+                       if (*tmp == '/' && 
+                           (strncmp(tmp, "/priv", 5) == 0) && 
+                           (tmp[5] == 0 || tmp[5] == '/')) {
+                               return TRUE;
+                       }
+               }
+       }
+       return FALSE;
+}
+
+gboolean
+path_is_lxc_conf(const char *path)
+{
+       while (*path == '/') path++;
+
+       if (strncmp(path, "nodes/", 6) == 0) {
+               const char *tmp = path + 6;
+               while(*tmp && *tmp != '/') tmp++;
+               if (*tmp == '/' && 
+                   (strncmp(tmp, "/lxc", 4) == 0) && 
+                   (tmp[4] == 0 || tmp[4] == '/')) {
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+
+gboolean
+path_is_lockdir(const char *path)
+{
+       while (*path == '/') path++;
+
+       return (strncmp(path, "priv/lock/", 10) == 0) && path[10];
+}
diff --git a/src/pmxcfs/cfs-utils.h b/src/pmxcfs/cfs-utils.h
new file mode 100644 (file)
index 0000000..eb86379
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_CFS_UTILS_H_
+#define _PVE_CFS_UTILS_H_
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdint.h>
+#include <glib.h>
+#include <fcntl.h>
+
+#define HOST_CLUSTER_CONF_FN "/etc/corosync/corosync.conf"
+#define CFS_PID_FN "/var/run/pve-cluster.pid"
+#define VARLIBDIR "/var/lib/pve-cluster"
+#define RUNDIR "/run/pve-cluster"
+
+#define CFS_MAX(a, b)          (((a) > (b)) ? (a) : (b))
+#define CFS_MIN(a, b)          (((a) < (b)) ? (a) : (b))
+
+typedef struct {
+       char *nodename;
+       char *ip;
+       gid_t gid;
+       int debug;
+} cfs_t;
+
+extern cfs_t cfs;
+
+void
+utf8_to_ascii(
+       char *buf, 
+       int bufsize, 
+       const char *msg, 
+       gboolean quotequote);
+
+void 
+cfs_log(
+       const gchar *log_domain,
+       GLogLevelFlags log_level,
+       const char *file,
+       int         line,
+       const char  *func,
+       const gchar    *format,
+       ...) G_GNUC_PRINTF (6, 7);
+
+void ipc_log_fn(
+       const char *file,
+       int32_t line, 
+       int32_t severity, 
+       const char *msg);
+
+
+#define cfs_debug(...)  G_STMT_START { \
+       if (cfs.debug) \
+               cfs_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__); \
+       } G_STMT_END
+
+#define cfs_dom_debug(domain, ...)  G_STMT_START {     \
+       if (cfs.debug) \
+               cfs_log(domain, G_LOG_LEVEL_DEBUG, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__); \
+       } G_STMT_END
+
+#define cfs_critical(...)  cfs_log(G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__)
+#define cfs_dom_critical(domain, ...)  cfs_log(domain, G_LOG_LEVEL_CRITICAL, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__)
+#define cfs_message(...)  cfs_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__)
+#define cfs_dom_message(domain, ...)  cfs_log(domain, G_LOG_LEVEL_MESSAGE, __FILE__, __LINE__, G_STRFUNC, __VA_ARGS__)
+
+guint64 
+cluster_config_version(
+       const gpointer config_data, 
+       gsize config_length);
+
+ssize_t 
+safe_read(
+       int fd, 
+       char *buf, 
+       size_t count);
+
+gboolean 
+full_write(
+       int fd, 
+       const char *buf, 
+       size_t len);
+
+gboolean 
+atomic_write_file(
+       const char *filename, 
+       gconstpointer data, 
+       size_t len, 
+       mode_t mode, 
+       gid_t gid);
+
+gboolean
+path_is_private(const char *path);
+
+gboolean
+path_is_lxc_conf(const char *path);
+
+gboolean
+path_is_lockdir(const char *path);
+
+#endif /* _PVE_CFS_UTILS_H_ */
diff --git a/src/pmxcfs/check_memdb.c b/src/pmxcfs/check_memdb.c
new file mode 100644 (file)
index 0000000..d7eb05f
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <errno.h>
+#include <string.h>
+#include <check.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "cfs-utils.h"
+#include "status.h"
+#include "memdb.h"
+
+#if CHECK_MAJOR_VERSION == 0 && CHECK_MINOR_VERSION <= 10
+#define _TEST_TYPE TFun
+#else
+#define _TEST_TYPE const TTest *
+#endif
+
+cfs_t cfs = {
+       .debug = 0,
+       .nodename = "testnode",
+};
+
+#define TESTDB "/tmp/test.db"
+
+static memdb_t *memdb;
+
+static void
+setup(void)
+{
+       unlink(TESTDB);
+       memdb = memdb_open(TESTDB);     
+       ck_assert (memdb != NULL);
+
+       struct statvfs stbuf;
+       ck_assert(memdb_statfs(memdb, &stbuf) == 0);
+
+       int count = stbuf.f_files - stbuf.f_ffree;
+       ck_assert(count == 1);
+}
+       
+void
+teardown(void)
+{
+       ck_assert (memdb != NULL);      
+
+       memdb_close(memdb);
+}
+
+START_TEST(test_indextest1)
+{
+       char namebuf[100];
+
+       time_t ctime = 1234;
+       int testsize = 1024*32;
+       gchar *testdata = g_malloc0(testsize);
+
+       for (int i = 0; i < 100; i++) {
+               sprintf(namebuf, "testfile%d", i);
+
+               ck_assert(memdb_create(memdb, namebuf, 0, ctime) == 0);
+               ck_assert(memdb_write(memdb, namebuf, 0, ctime, testdata, testsize, 0, 0) == testsize);
+       }
+
+       struct statvfs stbuf;
+       ck_assert(memdb_statfs(memdb, &stbuf) == 0);
+
+       int count = stbuf.f_files - stbuf.f_ffree;
+       ck_assert(count == 101);
+
+       memdb_index_t *idx = memdb_encode_index(memdb->index, memdb->root);
+       ck_assert(idx != NULL);
+       
+       ck_assert(idx->version == 201);
+       ck_assert(idx->last_inode == 200);
+       ck_assert(idx->writer == 0);
+       ck_assert(idx->size == 101);
+       ck_assert(idx->bytes == (101*40 + sizeof( memdb_index_t)));
+
+       GChecksum *sha256 = g_checksum_new(G_CHECKSUM_SHA256); 
+       ck_assert(sha256 != NULL);
+       g_checksum_update(sha256, (unsigned char *)idx, idx->bytes);
+       const char *csum = g_checksum_get_string(sha256);
+       ck_assert_msg(
+               strcmp(csum, "913fd95015af9d93f10dd51ba2a7bb11351bcfe040be21e95fcba834adc3ec10") == 0,
+               "wrong idx checksum %s",
+               csum
+       );
+       g_free(idx);
+       g_free(testdata);
+
+}
+END_TEST
+
+START_TEST (test_dirtest1)
+{
+       const char *dn = "/dir1";
+       const char *sdn = "/dir1/sdir1";
+       time_t ctime = 1234;
+
+       ck_assert(memdb_mkdir(memdb, sdn, 0, ctime) == -ENOENT);
+       ck_assert(memdb_delete(memdb, dn, 0, ctime) == -ENOENT);
+
+       ck_assert(memdb_mkdir(memdb, dn, 0, ctime) == 0);
+       ck_assert(memdb_mkdir(memdb, dn, 0, ctime) == -EEXIST);
+       ck_assert(memdb_mkdir(memdb, sdn, 0, ctime) == 0);
+       ck_assert(memdb_mkdir(memdb, sdn, 0, ctime) == -EEXIST);
+       ck_assert(memdb_delete(memdb, dn, 0, ctime) == -ENOTEMPTY);
+       ck_assert(memdb_delete(memdb, sdn, 0, ctime) == 0);
+       ck_assert(memdb_delete(memdb, dn, 0, ctime) == 0);
+}
+END_TEST
+
+START_TEST (test_filetest1)
+{
+       const char *dn = "/dir1";
+       const char *fn = "/dir1/f1";
+       time_t ctime = 1234;
+       gpointer data;
+
+       char buf[1024];
+       memset(buf, 0, sizeof(buf));
+
+       ck_assert(memdb_read(memdb, fn, &data) == -ENOENT);
+
+       ck_assert(memdb_mkdir(memdb, dn, 0, ctime) == 0);
+
+       ck_assert(memdb_read(memdb, fn, &data) == -ENOENT);
+
+       ck_assert(memdb_write(memdb, fn, 0, ctime, buf, sizeof(buf), 0, 0) == -ENOENT);
+
+       ck_assert(memdb_create(memdb, fn, 0, ctime) == 0);
+
+       ck_assert(memdb_write(memdb, fn, 0, ctime, buf, sizeof(buf), 0, 0) == sizeof(buf));
+
+       ck_assert(memdb_read(memdb, fn, &data) == sizeof(buf));
+
+       ck_assert(memcmp(buf, data, sizeof(buf)) == 0);
+
+       g_free(data);
+
+       ck_assert(memdb_write(memdb, fn, 0, ctime, "0123456789", 10, 0, 1) == 10);
+       
+       ck_assert(memdb_read(memdb, fn, &data) == 10);
+       g_free(data);
+
+       ck_assert(memdb_write(memdb, fn, 0, ctime, "X", 1, 3, 0) == 1);
+
+       ck_assert(memdb_write(memdb, fn, 0, ctime, "X", 1, 6, 0) == 1);
+
+       ck_assert(memdb_read(memdb, fn, &data) == 10);
+
+       ck_assert(strncmp(data, "012X45X789", 10) == 0);
+       g_free(data);
+
+       ck_assert(memdb_delete(memdb, fn, 0, ctime) == 0);
+
+       ck_assert(memdb_delete(memdb, fn, 0, ctime) == -ENOENT);
+
+       ck_assert(memdb_delete(memdb, dn, 0, ctime) == 0);
+}
+END_TEST
+
+/* Nornmaly, parent inode number is always less than contained inode,
+ * but this is not allways the case. A simple move can destroy that 
+ * ordering. This code test the placeholder algorithm in 
+ * bdb_backend_load_index()
+ */
+START_TEST (test_loaddb1)
+{
+       time_t ctime = 1234;
+
+       ck_assert(memdb_mkdir(memdb, "dir1", 0, ctime) == 0);
+
+       ck_assert(memdb_create(memdb, "dir1/file1", 0, ctime) == 0);
+
+       ck_assert(memdb_create(memdb, "dir1/file2", 0, ctime) == 0);
+
+       ck_assert(memdb_mkdir(memdb, "dir2", 0, ctime) == 0);
+
+       ck_assert(memdb_rename(memdb, "dir1/file1", "dir2/file1", 0, ctime) == 0);
+
+       ck_assert(memdb_rename(memdb, "dir1/file2", "dir2/file2", 0, ctime) == 0);
+
+       ck_assert(memdb_create(memdb, "dir2/file1", 0, ctime) == -EEXIST);
+
+       ck_assert(memdb_create(memdb, "dir2/file2", 0, ctime) == -EEXIST);
+
+       //memdb_dump(memdb);
+
+       memdb_close(memdb);
+
+       memdb = memdb_open(TESTDB);     
+       ck_assert (memdb != NULL);
+
+       ck_assert(memdb_create(memdb, "dir2/file1", 0, ctime) == -EEXIST);
+
+       ck_assert(memdb_create(memdb, "dir2/file2", 0, ctime) == -EEXIST);
+
+       //memdb_dump(memdb);
+
+}
+END_TEST
+
+START_TEST (test_loaddb2)
+{
+       time_t ctime = 1234;
+
+       ck_assert(memdb_mkdir(memdb, "dir1", 0, ctime) == 0);
+
+       ck_assert(memdb_mkdir(memdb, "dir1/sd1", 0, ctime) == 0);
+
+       ck_assert(memdb_create(memdb, "dir1/file1", 0, ctime) == 0);
+
+       ck_assert(memdb_create(memdb, "dir1/file2", 0, ctime) == 0);
+
+       ck_assert(memdb_mkdir(memdb, "dir2", 0, ctime) == 0);
+
+       ck_assert(memdb_rename(memdb, "dir1/sd1", "dir2/sd1", 0, ctime) == 0);
+
+       ck_assert(memdb_rename(memdb, "dir1/file1", "dir2/sd1/file1", 0, ctime) == 0);
+
+       ck_assert(memdb_rename(memdb, "dir1/file2", "dir2/sd1/file2", 0, ctime) == 0);
+
+       ck_assert(memdb_create(memdb, "dir2/file3", 0, ctime) == 0);
+
+       ck_assert(memdb_mkdir(memdb, "dir2/sd1", 0, ctime) == -EEXIST);
+
+       //memdb_dump(memdb);
+
+       memdb_close(memdb);
+
+       memdb = memdb_open(TESTDB);     
+       ck_assert (memdb != NULL);
+
+       ck_assert(memdb_mkdir(memdb, "dir2/sd1", 0, ctime) == -EEXIST);
+
+       //memdb_dump(memdb);
+
+}
+END_TEST
+
+static void
+add_test(
+       Suite *s,
+       _TEST_TYPE tf,
+       const char *name)
+{
+       TCase *tc = tcase_create (name);
+       tcase_add_checked_fixture (tc, setup, teardown);
+       tcase_add_test (tc, tf);
+       suite_add_tcase (s, tc);
+}
+
+static Suite *
+memdb_suite(void)
+{
+       Suite *s = suite_create ("memdb");
+
+       add_test(s, test_dirtest1, "dirtest1");
+
+       add_test(s, test_filetest1, "filetest1");
+       add_test(s, test_indextest1, "indextest1");
+       add_test(s, test_loaddb1, "loaddb1");
+       add_test(s, test_loaddb2, "loaddb2");
+       return s;
+}
+
+int
+main(void)
+{
+       int number_failed;
+
+       cfs_status_init();
+
+       Suite *s = memdb_suite();
+       SRunner *sr = srunner_create(s);
+       srunner_run_all(sr, CK_NORMAL);
+       number_failed = srunner_ntests_failed(sr);
+       srunner_free(sr);
+
+       return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
diff --git a/src/pmxcfs/confdb.c b/src/pmxcfs/confdb.c
new file mode 100644 (file)
index 0000000..839f576
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+
+/* see "man cmap_overview" and "man cmap_keys" */
+
+#define G_LOG_DOMAIN "confdb"
+
+#define CLUSTER_KEY "cluster"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include <corosync/cmap.h>
+
+#include "cfs-utils.h"
+#include "loop.h"
+#include "status.h"
+
+typedef struct {
+       cmap_handle_t handle;
+       cmap_track_handle_t track_nodelist_handle;
+       cmap_track_handle_t track_version_handle;
+       gboolean changes;
+} cs_private_t;
+
+static cs_error_t
+cmap_read_clusternodes(
+       cmap_handle_t handle,
+       cfs_clinfo_t *clinfo)
+{
+       cs_error_t result;
+        cmap_iter_handle_t iter;
+
+       result = cmap_iter_init(handle, "nodelist.node.", &iter);
+       if (result != CS_OK) {
+               cfs_critical("cmap_iter_init failed %d", result);
+               return result;
+       }
+
+       cmap_value_types_t type;
+       char key_name[CMAP_KEYNAME_MAXLEN + 1];
+       size_t value_len;
+
+       int last_id = -1;
+       uint32_t nodeid = 0;
+       uint32_t votes = 0;
+       char *name = NULL;
+
+       while ((result = cmap_iter_next(handle, iter, key_name, &value_len, &type)) == CS_OK) {
+               int id;
+               char subkey[CMAP_KEYNAME_MAXLEN + 1];
+               if (sscanf(key_name, "nodelist.node.%d.%s", &id, subkey) != 2) continue;
+
+               if (id != last_id) {
+                       if (name && nodeid) {
+                               cfs_clnode_t *clnode = cfs_clnode_new(name, nodeid, votes);
+                               cfs_clinfo_add_node(clinfo, clnode);
+                       }
+                       last_id = id;
+                       free(name);
+                       name = NULL;
+                       nodeid = 0;
+                       votes = 0;
+               }
+
+               if (strcmp(subkey, "nodeid") == 0) {
+                       if ((result = cmap_get_uint32(handle, key_name, &nodeid)) != CS_OK) {
+                               cfs_critical("cmap_get %s failed %d", key_name, result);
+                       }
+               } else if (strcmp(subkey, "quorum_votes") == 0) {
+                       if ((result = cmap_get_uint32(handle, key_name, &votes)) != CS_OK) {
+                               cfs_critical("cmap_get %s failed %d", key_name, result);
+                       }
+               } else if (strcmp(subkey, "ring0_addr") == 0) {
+                       // prefering the 'name' subkey over 'ring0_addr', needed for RRP
+                       // and when using a IP address for ring0_addr
+                       if (name == NULL &&
+                           (result = cmap_get_string(handle, key_name, &name)) != CS_OK) {
+                               cfs_critical("cmap_get %s failed %d", key_name, result);
+                       }
+               } else if (strcmp(subkey, "name") == 0) {
+                       free(name);
+                       name = NULL;
+                       if ((result = cmap_get_string(handle, key_name, &name)) != CS_OK) {
+                               cfs_critical("cmap_get %s failed %d", key_name, result);
+                       }
+               }
+       }
+
+       if (name && nodeid) {
+               cfs_clnode_t *clnode = cfs_clnode_new(name, nodeid, votes);
+               cfs_clinfo_add_node(clinfo, clnode);
+       }
+       free(name);
+
+        result = cmap_iter_finalize(handle, iter);
+       if (result != CS_OK) {
+               cfs_critical("cmap_iter_finalize failed %d", result);
+               return result;
+       }
+
+       return result;
+}
+
+static cs_error_t
+cmap_read_config(cmap_handle_t handle)
+{
+       cs_error_t result;
+
+       uint64_t config_version = 0;
+
+       result = cmap_get_uint64(handle, "totem.config_version", &config_version);
+       if (result != CS_OK) {
+               cfs_critical("cmap_get totem.config_version failed %d", result);
+               // optional, do not throw error
+       }
+
+       char *clustername = NULL;
+       result = cmap_get_string(handle, "totem.cluster_name", &clustername);
+       if (result != CS_OK) {
+               cfs_critical("cmap_get totem.cluster_name failed %d", result);
+               return result;
+       }
+
+       cfs_clinfo_t *clinfo = cfs_clinfo_new(clustername, config_version);
+       g_free(clustername);
+
+       result = cmap_read_clusternodes(handle, clinfo);
+       if (result == CS_OK) {
+               cfs_status_set_clinfo(clinfo);
+       } else {
+               cfs_clinfo_destroy(clinfo);
+       }
+
+       return result;
+}
+
+static gboolean
+service_cmap_finalize(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_val_if_fail(service != NULL, FALSE);
+       g_return_val_if_fail(context != NULL, FALSE);
+
+       cs_private_t *private = (cs_private_t *)context;
+       cmap_handle_t handle = private->handle;
+       cs_error_t result;
+
+        if (private->track_nodelist_handle) {
+            result = cmap_track_delete(handle, private->track_nodelist_handle);
+            if (result != CS_OK) {
+               cfs_critical("cmap_track_delete nodelist failed: %d", result);
+            }
+            private->track_nodelist_handle = 0;
+        }
+       
+        if (private->track_version_handle) {
+            result = cmap_track_delete(handle, private->track_version_handle);
+            if (result != CS_OK) {
+               cfs_critical("cmap_track_delete version failed: %d", result);
+            }
+            private->track_version_handle = 0;
+        }
+
+       result = cmap_finalize(handle);
+       private->handle = 0;
+       if (result != CS_OK) {
+               cfs_critical("cmap_finalize failed: %d", result);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+track_callback(
+    cmap_handle_t cmap_handle,
+    cmap_track_handle_t cmap_track_handle,
+    int32_t event,
+    const char *key_name,
+    struct cmap_notify_value new_value,
+    struct cmap_notify_value old_value,
+    void *context)
+{
+       g_return_if_fail(context != NULL);
+
+       cs_private_t *private = (cs_private_t *)context;
+
+       cfs_debug("track_callback %s %d\n", key_name, event);
+
+       private->changes = TRUE;
+}
+
+
+static int
+service_cmap_initialize(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_val_if_fail(service != NULL, FALSE);
+       g_return_val_if_fail(context != NULL, FALSE);
+
+       cs_private_t *private = (cs_private_t *)context;
+
+        // fixme: do not copy (use pointer)
+       cmap_handle_t handle = private->handle;
+       cs_error_t result;
+
+       if (!private->handle) {
+
+               result = cmap_initialize(&handle);
+               if (result != CS_OK) {
+                       cfs_critical("cmap_initialize failed: %d", result);
+                       private->handle = 0;
+                       return -1;
+               }
+
+               result = cmap_context_set(handle, private);
+               if (result != CS_OK) {
+                       cfs_critical("cmap_context_set failed: %d", result);
+                       cmap_finalize(handle);
+                       private->handle = 0;
+                       return -1;
+               }
+
+               private->handle = handle;
+       }
+
+       
+        result = cmap_track_add(handle, "nodelist.node.",
+                               CMAP_TRACK_PREFIX|CMAP_TRACK_ADD|CMAP_TRACK_DELETE|CMAP_TRACK_MODIFY,
+                                track_callback, context, &private->track_nodelist_handle);
+
+       if (result == CS_OK) {
+               result = cmap_track_add(handle, "totem.config_version",
+                                       CMAP_TRACK_ADD|CMAP_TRACK_DELETE|CMAP_TRACK_MODIFY,
+                                       track_callback, context, &private->track_version_handle);
+       }
+
+       if (result == CS_ERR_LIBRARY || result == CS_ERR_BAD_HANDLE) {
+               cfs_critical("cmap_track_changes failed: %d - closing handle", result);
+               cmap_finalize(handle);
+               private->handle = 0;
+               return -1;
+       } else if (result != CS_OK) {
+                cfs_critical("cmap_track_changes failed: %d - trying again", result);
+               return -1;
+       }
+
+       int cmap_fd = -1;
+       if ((result = cmap_fd_get(handle, &cmap_fd)) != CS_OK) {
+               cfs_critical("confdb_fd_get failed %d - trying again", result);
+               return -1;
+       }
+
+       cmap_read_config(handle);
+
+       return cmap_fd;
+}
+
+static gboolean
+service_cmap_dispatch(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_val_if_fail(service != NULL, FALSE);
+       g_return_val_if_fail(context != NULL, FALSE);
+
+       cs_private_t *private = (cs_private_t *)context;
+       cmap_handle_t handle =  private->handle;
+
+       cs_error_t result;
+
+       private->changes = FALSE;
+       int retries = 0;
+loop:
+       result = cmap_dispatch(handle, CS_DISPATCH_ALL);
+       if (result == CS_ERR_TRY_AGAIN) {
+               usleep(100000);
+               ++retries;
+               if ((retries % 100) == 0)
+                       cfs_message("cmap_dispatch retry %d", retries);
+               goto loop;
+       }
+
+
+       if (result == CS_OK || result == CS_ERR_TRY_AGAIN) {
+
+               if (private->changes) {
+                       result = cmap_read_config(handle);
+
+                       private->changes = FALSE;
+
+                       if (result == CS_OK)
+                               return TRUE;
+               }
+       } else {
+               cfs_critical("cmap_dispatch failed: %d", result);
+       }
+
+       cmap_finalize(handle);
+       private->handle = 0;
+       return FALSE;
+}
+
+static cfs_service_callbacks_t cfs_confdb_callbacks = {
+       .cfs_service_initialize_fn =  service_cmap_initialize,
+       .cfs_service_finalize_fn = service_cmap_finalize,
+       .cfs_service_dispatch_fn = service_cmap_dispatch,
+};
+
+cfs_service_t *
+service_confdb_new(void)
+{
+       cfs_service_t *service;
+
+       cs_private_t *private = g_new0(cs_private_t, 1);
+       if (!private)
+               return NULL;
+
+       service = cfs_service_new(&cfs_confdb_callbacks, G_LOG_DOMAIN, private);
+
+       return service;
+}
+
+void
+service_confdb_destroy(cfs_service_t *service)
+{
+       g_return_if_fail(service != NULL);
+
+       cs_private_t *private =
+               (cs_private_t *)cfs_service_get_context(service);
+
+       g_free(private);
+       g_free(service);
+}
diff --git a/src/pmxcfs/confdb.h b/src/pmxcfs/confdb.h
new file mode 100644 (file)
index 0000000..5c77cc4
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_CONFDB_H_
+#define _PVE_CONFDB_H_
+
+#include <glib.h>
+
+#include "loop.h"
+
+cfs_service_t *service_confdb_new(void);
+
+void service_confdb_destroy(
+       cfs_service_t *service);
+
+#endif /* _PVE_CONFDB_H_ */
diff --git a/src/pmxcfs/create_pmxcfs_db.c b/src/pmxcfs/create_pmxcfs_db.c
new file mode 100644 (file)
index 0000000..b642477
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+  Note: we use this with the installer to create the initial db
+  without starting pmxcfs/fuse
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <errno.h>
+#include <string.h>
+#include <check.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "cfs-utils.h"
+#include "status.h"
+#include "memdb.h"
+
+cfs_t cfs = {
+       .debug = 0,
+       .nodename = "dummy",
+};
+
+static memdb_t *memdb;
+
+static void
+usage_error(void) 
+{
+       fprintf(stderr, "Usage: create_pmxcfs_db /a/dir /a/filename.db\n");
+       exit(-1);
+}
+
+int 
+main(int argc, char *argv[]) 
+{
+       cfs_status_init();
+
+       if (argc != 3) {
+               usage_error();
+       }
+
+       const char *dir_name = argv[1];
+       const char *dbfile = argv[2];
+
+       DIR *dh = opendir(dir_name);
+       if (!dh) {
+               perror("unable to open dir");
+               exit(-1);
+       }
+       memdb = memdb_open(dbfile);     
+
+       struct dirent *de;
+       time_t ctime = time(NULL);
+
+       while((de = readdir(dh))) {
+               if (de->d_type != DT_REG) {
+                       continue;
+               }
+
+               char *cdata = NULL;
+               gsize clen = 0;
+               char *fn = g_strdup_printf("%s/%s", dir_name, de->d_name);
+               if (g_file_get_contents(fn, &cdata, &clen, NULL)) {
+                       //printf("FOUND %ld %s\n", clen, fn);
+                       if (memdb_create(memdb, de->d_name, 0, ctime) != 0) {
+                               fprintf(stderr, "memdb_create '%s' failed\n", de->d_name);
+                               exit(-1);
+                       }
+                       if (memdb_write(memdb, de->d_name, 0, ctime, cdata, clen, 0, 1) != clen) {
+                               fprintf(stderr, "memdb_write '%s' failed\n", de->d_name);
+                               exit(-1);
+                       }
+
+               }
+               g_free(fn);
+       }
+
+       memdb_close(memdb);
+
+       closedir(dh);
+}
diff --git a/src/pmxcfs/database.c b/src/pmxcfs/database.c
new file mode 100644 (file)
index 0000000..d1c15a7
--- /dev/null
@@ -0,0 +1,749 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define G_LOG_DOMAIN "database"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include <sqlite3.h>
+
+#include "cfs-utils.h"
+#include "status.h"
+#include "memdb.h"
+
+struct db_backend {
+       sqlite3 *db;
+       sqlite3_stmt *stmt_insert_entry;
+       sqlite3_stmt *stmt_update_entry;
+       sqlite3_stmt *stmt_replace_entry;
+       sqlite3_stmt *stmt_delete_entry;
+       sqlite3_stmt *stmt_begin;
+       sqlite3_stmt *stmt_commit;
+       sqlite3_stmt *stmt_rollback;
+       sqlite3_stmt *stmt_load_all;
+};
+
+#define VERSIONFILENAME "__version__"
+
+/* colume type "INTEGER PRIMARY KEY" is a special case, because sqlite
+ * uses the internal ROWID. So only real interger are allowed, and
+ * there is no need to add an additionl check
+ */
+static const char *sql_create_db =
+       "CREATE TABLE IF NOT EXISTS tree ("
+       "  inode INTEGER PRIMARY KEY NOT NULL,"
+       "  parent INTEGER NOT NULL CHECK(typeof(parent)=='integer'),"
+       "  version INTEGER NOT NULL CHECK(typeof(version)=='integer'),"
+       "  writer INTEGER NOT NULL CHECK(typeof(writer)=='integer'),"
+       "  mtime INTEGER NOT NULL CHECK(typeof(mtime)=='integer'),"
+       "  type INTEGER NOT NULL CHECK(typeof(type)=='integer'),"
+       "  name TEXT NOT NULL,"
+       "  data BLOB);";
+
+static const char *sql_load_all =
+       "SELECT inode, parent, version, writer, mtime, type, name, data FROM tree;";
+
+static char *sql_insert_entry =
+       "INSERT INTO tree ("
+       "inode, parent, version, writer, mtime, type, name, data) "
+       "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);";
+
+static char *sql_update_entry =
+       "UPDATE tree SET parent = ?2, version = ?3, writer = ?4, mtime = ?5, "
+       "type = ?6, name = ?7, data = ?8 WHERE inode = ?1;";
+
+static char *sql_replace_entry =
+       "REPLACE INTO tree (inode, parent, version, writer, mtime, type, "
+       "name, data) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);";
+
+static char *sql_delete_entry =
+       "DELETE FROM tree WHERE inode = ?1;";
+
+static char *sql_begin = "BEGIN TRANSACTION;";
+static char *sql_commit = "COMMIT TRANSACTION;";
+static char *sql_rollback = "ROLLBACK TRANSACTION;";
+
+static sqlite3 *bdb_create(
+       const char *filename)
+{
+       int rc;
+       sqlite3 *db = NULL;
+
+       int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
+       rc = sqlite3_open_v2(filename, &db, flags, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("splite3_open_v2 failed: %d\n", rc);
+               sqlite3_close(db);
+               return NULL;
+       }
+
+       if (chmod(filename, 0600) == -1) {
+               cfs_critical("chmod failed: %s", strerror(errno));
+               return NULL;
+       }
+
+       /* use WAL mode - to allow concurrent reads */
+       rc = sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("unable to set WAL mode: %s\n", sqlite3_errmsg(db));
+               sqlite3_close(db);
+               return NULL;
+       }
+
+       /* NORMAL is good enough when using WAL */
+       rc = sqlite3_exec(db, "PRAGMA synchronous=NORMAL",  NULL, NULL, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("unable to set synchronous mode: %s\n", sqlite3_errmsg(db));
+               sqlite3_close(db);
+               return NULL;
+       }
+
+       sqlite3_busy_timeout(db, 10000); /* 10 seconds */
+
+       rc = sqlite3_exec(db, sql_create_db, NULL, NULL, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("init database failed: %s\n", sqlite3_errmsg(db));
+               sqlite3_close(db);
+               return NULL;
+       }
+
+       return db;
+}
+
+static int backend_write_inode(
+       sqlite3 *db,
+       sqlite3_stmt *stmt,
+       guint64 inode,
+       guint64 parent,
+       guint64 version,
+       guint32 writer,
+       guint32 mtime,
+       guint32 size,
+       char type,
+       char *name,
+       gpointer value)
+{
+       int rc;
+
+       cfs_debug("enter backend_write_inode %016" PRIX64 " '%s', size %"PRIu32"", inode, name, size);
+
+       if ((rc = sqlite3_bind_int64(stmt, 1, inode)) !=  SQLITE_OK) {
+               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
+               return rc;
+       }
+       if ((rc = sqlite3_bind_int64(stmt, 2, parent)) !=  SQLITE_OK) {
+               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
+               return rc;
+       }
+       if ((rc = sqlite3_bind_int64(stmt, 3, version)) !=  SQLITE_OK) {
+               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
+               return rc;
+       }
+       if ((rc = sqlite3_bind_int64(stmt, 4, writer)) !=  SQLITE_OK) {
+               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
+               return rc;
+       }
+       if ((rc = sqlite3_bind_int64(stmt, 5, mtime)) !=  SQLITE_OK) {
+               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
+               return rc;
+       }
+       if ((rc = sqlite3_bind_int64(stmt, 6, type)) !=  SQLITE_OK) {
+               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
+               return rc;
+       }
+       if ((rc = sqlite3_bind_text(stmt, 7, name, -1, SQLITE_STATIC)) !=  SQLITE_OK) {
+               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
+               return rc;
+       }
+       if ((rc = sqlite3_bind_blob(stmt, 8, value, size, SQLITE_STATIC)) !=  SQLITE_OK) {
+               cfs_critical("sqlite3_bind failed: %s\n", sqlite3_errmsg(db));
+               return rc;
+       }
+
+       if ((rc = sqlite3_step(stmt)) != SQLITE_DONE) {
+               cfs_critical("sqlite3_step failed: %s\n", sqlite3_errmsg(db));
+               sqlite3_reset(stmt);
+               return rc;
+       }
+
+       sqlite3_reset(stmt);
+
+       return SQLITE_OK;
+}
+
+static int bdb_backend_delete_inode(
+       db_backend_t *bdb,
+       guint64 inode)
+{
+       int rc;
+
+       cfs_debug("enter dbd_backend_delete_inode");
+
+       sqlite3_stmt *stmt = bdb->stmt_delete_entry;
+
+       if ((rc = sqlite3_bind_int64(stmt, 1, inode)) !=  SQLITE_OK) {
+               cfs_critical("delete_inode/sqlite3_bind failed: %s\n", sqlite3_errmsg(bdb->db));
+               return rc;
+       }
+
+       if ((rc = sqlite3_step(stmt)) != SQLITE_DONE) {
+               cfs_critical("delete_inode failed: %s\n", sqlite3_errmsg(bdb->db));
+               sqlite3_reset(stmt);
+               return rc;
+       }
+
+       sqlite3_reset(stmt);
+
+       return SQLITE_OK;
+}
+
+int bdb_backend_write(
+       db_backend_t *bdb,
+       guint64 inode,
+       guint64 parent,
+       guint64 version,
+       guint32 writer,
+       guint32 mtime,
+       guint32 size,
+       char type,
+       char *name,
+       gpointer value,
+       guint64 delete_inode)
+{
+       g_return_val_if_fail(bdb != NULL, SQLITE_PERM);
+       g_return_val_if_fail(inode == 0 || (name != NULL && name[0]), SQLITE_PERM);
+       g_return_val_if_fail(type == DT_REG || type == DT_DIR, SQLITE_PERM);
+       int rc;
+
+       gboolean need_txn =  (inode != 0 || delete_inode != 0);
+
+       if (need_txn) {
+               rc = sqlite3_step(bdb->stmt_begin);
+               sqlite3_reset(bdb->stmt_begin);
+               if (rc != SQLITE_DONE) {
+                       cfs_critical("begin transaction failed: %s\n", sqlite3_errmsg(bdb->db));
+                       return rc;
+               }
+       }
+
+       if (delete_inode != 0) {
+               if ((rc = bdb_backend_delete_inode(bdb, delete_inode)) != SQLITE_OK)
+                       goto rollback;
+       }
+
+       if (inode != 0) {
+
+               sqlite3_stmt *stmt = (inode > version) ?
+                       bdb->stmt_insert_entry : bdb->stmt_replace_entry;
+
+               rc = backend_write_inode(bdb->db, stmt, inode, parent, version,
+                                        writer, mtime, size, type, name, value);
+               if (rc != SQLITE_OK)
+                       goto rollback;
+
+               if (sqlite3_changes(bdb->db) != 1) {
+                       cfs_critical("no such inode %016" PRIX64, inode);
+                       goto rollback;
+               }
+       }
+
+       rc = backend_write_inode(bdb->db, bdb->stmt_replace_entry, 0, 0, version,
+                                writer, mtime, 0, DT_REG, VERSIONFILENAME, NULL);
+
+       if (rc != SQLITE_OK)
+               goto rollback;
+
+
+       if (need_txn) {
+               rc = sqlite3_step(bdb->stmt_commit);
+               sqlite3_reset(bdb->stmt_commit);
+               if (rc != SQLITE_DONE) {
+                       cfs_critical("commit transaction failed: %s\n", sqlite3_errmsg(bdb->db));
+                       goto rollback;
+               }
+       }
+
+       return SQLITE_OK;
+
+rollback:
+
+       if (!need_txn)
+               return rc;
+
+       int rbrc = sqlite3_step(bdb->stmt_rollback);
+       sqlite3_reset(bdb->stmt_rollback);
+       if (rbrc != SQLITE_DONE) {
+               cfs_critical("rollback transaction failed: %s\n", sqlite3_errmsg(bdb->db));
+               return rc;
+       }
+
+       return rc;
+}
+
+static gboolean bdb_backend_load_index(
+       db_backend_t *bdb,
+       memdb_tree_entry_t *root,
+       GHashTable *index)
+{
+       g_return_val_if_fail(bdb != NULL, FALSE);
+       g_return_val_if_fail(root != NULL, FALSE);
+       g_return_val_if_fail(index != NULL, FALSE);
+       g_return_val_if_fail(root->version == 0, FALSE);
+       g_return_val_if_fail(g_hash_table_size(index) == 1, FALSE);
+
+       sqlite3_stmt *stmt = bdb->stmt_load_all;
+
+       int rc;
+       while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
+
+               memdb_tree_entry_t *te;
+
+               guint64 inode = sqlite3_column_int64(stmt, 0);
+               const char *name = (const char *)sqlite3_column_text(stmt, 6);
+               int namelen = sqlite3_column_bytes(stmt, 6);
+               if (name == NULL || namelen == 0) {
+                       cfs_critical("inode has no name (inode = %016" PRIX64 ")", inode);
+                       goto fail;
+               }
+               te = g_malloc0(sizeof(memdb_tree_entry_t) + namelen + 1);
+               strcpy(te->name, name);
+
+               te->inode = inode;
+               te->parent = sqlite3_column_int64(stmt, 1);
+               te->version = sqlite3_column_int64(stmt, 2);
+               te->writer = sqlite3_column_int64(stmt, 3) & 0x0ffffffff;
+               te->mtime = sqlite3_column_int64(stmt, 4) & 0x0ffffffff;
+               te->type = sqlite3_column_int64(stmt, 5) & 255;
+
+               gconstpointer value = sqlite3_column_blob(stmt, 7);
+
+               int size = sqlite3_column_bytes(stmt, 7);
+               te->size = size;
+
+               if (te->type == DT_REG) {
+                       if (size > 0)
+                               te->data.value = g_memdup2(value, size);
+               } else if (te->type == DT_DIR) {
+                       if (size) {
+                               cfs_critical("directory inode contains data (inode = %016" PRIX64 ")",
+                                          te->inode);
+                               g_free(te);
+                               goto fail;
+                       }
+                       te->data.entries = NULL;
+               } else {
+                       cfs_critical("inode has unknown type (inode = %016" PRIX64 ", type = %d)",
+                                  te->inode, te->type);
+                       g_free(te);
+                       goto fail;
+               }
+
+               cfs_debug("name %s (inode = %016" PRIX64 ", parent = %016" PRIX64 ")",
+                       te->name, te->inode, te->parent);
+
+               if (te->inode == 0) {
+                       if (!strcmp(te->name, VERSIONFILENAME)) {
+                               root->version = te->version;
+                               root->writer = te->writer;
+                               root->mtime = te->mtime;
+                               memdb_tree_entry_free(te);
+                       } else {
+                               cfs_critical("root inode has unexpected name '%s'", te->name);
+                               memdb_tree_entry_free(te);
+                               goto fail;
+                       }
+               } else {
+                       memdb_tree_entry_t *pte;
+
+                       if (!(pte = g_hash_table_lookup(index, &te->parent))) {
+                               /* allocate placeholder (type == 0)
+                                * this is simply replaced if we find a real inode later
+                                */
+                               pte = g_malloc0(sizeof(memdb_tree_entry_t));
+                               pte->inode = te->parent;
+                               pte->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
+                               g_hash_table_replace(index, &pte->inode, pte);
+
+                       } else if (!(pte->type == DT_DIR || pte->type == 0)) {
+                               cfs_critical("parent is not a directory "
+                                            "(inode = %016" PRIX64 ", parent = %016" PRIX64 ", name = '%s')",
+                                            te->inode, te->parent, te->name);
+                               memdb_tree_entry_free(te);
+                               goto fail;
+                       }
+
+                       if (te->type == DT_DIR) {
+                               memdb_tree_entry_t *tmpte;
+                               /* test if there is a placeholder entry */
+                               if ((tmpte = g_hash_table_lookup(index, &te->inode))) {
+                                       if (tmpte->type != 0) {
+                                               cfs_critical("found strange placeholder for "
+                                                            "(inode = %016" PRIX64 ", parent = %016" PRIX64 ", name = '%s', type = '%d')",
+                                                            te->inode, te->parent, te->name, tmpte->type);
+                                               memdb_tree_entry_free(te);
+                                               goto fail;
+                                       }
+                                       /* copy entries from placeholder */
+                                       te->data.entries = tmpte->data.entries;
+                                       tmpte->data.entries = NULL;
+                               } else {
+                                       te->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
+                               }
+                       }
+
+                       memdb_tree_entry_t *existing;
+                       if ((existing = g_hash_table_lookup(pte->data.entries, te->name))) {
+                               cfs_critical(
+                                   "found entry with duplicate name '%s' - "
+                                   "A:(inode = 0x%016"PRIX64", parent = 0x%016"PRIX64", v./mtime = 0x%"PRIX64"/0x%"PRIi32")"
+                                   " vs. "
+                                   "B:(inode = 0x%016"PRIX64", parent = 0x%016"PRIX64", v./mtime = 0x%"PRIX64"/0x%"PRIi32")",
+                                    te->name,
+                                    existing->inode, existing->parent, existing->version, existing->mtime,
+                                    te->inode, te->parent, te->version, te->mtime
+                               );
+                               goto fail;
+                       }
+
+                       g_hash_table_replace(pte->data.entries, te->name, te);
+                       g_hash_table_replace(index, &te->inode, te);
+               }
+       }
+       if (rc != SQLITE_DONE) {
+               cfs_critical("select returned error: %s", sqlite3_errmsg(bdb->db));
+               goto fail;
+       }
+
+       /* check if all inodes have parents (there must be no placeholders) */
+       GHashTableIter iter;
+       gpointer key, value;
+       g_hash_table_iter_init (&iter, index);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               memdb_tree_entry_t *te = (memdb_tree_entry_t *)value;
+               if (te->type == 0) {
+                       cfs_critical("missing directory inode (inode = %016" PRIX64 ")", te->inode);
+                       goto fail;
+               }
+       }
+
+       sqlite3_reset(stmt);
+
+       return TRUE;
+
+fail:
+       sqlite3_reset(stmt);
+
+       cfs_critical("DB load failed");
+
+       return FALSE;
+}
+
+gboolean bdb_backend_commit_update(
+       memdb_t *memdb,
+       memdb_index_t *master,
+       memdb_index_t *slave,
+       GList *inodes)
+{
+       g_return_val_if_fail(memdb != NULL, FALSE);
+       g_return_val_if_fail(memdb->bdb != NULL, FALSE);
+       g_return_val_if_fail(master != NULL, FALSE);
+       g_return_val_if_fail(slave != NULL, FALSE);
+
+       cfs_debug("enter bdb_backend_commit_update");
+
+       memdb_tree_entry_t *root = NULL;
+       GHashTable *index = NULL;
+
+       db_backend_t *bdb = (db_backend_t *)memdb->bdb;
+       gboolean result = FALSE;
+
+       int rc;
+
+       rc = sqlite3_step(bdb->stmt_begin);
+       sqlite3_reset(bdb->stmt_begin);
+       if (rc != SQLITE_DONE) {
+               cfs_critical("begin transaction failed: %s\n", sqlite3_errmsg(bdb->db));
+               return rc;
+       }
+
+       g_mutex_lock (&memdb->mutex);
+
+       /* first, delete anything not found in master index) */
+
+       int i = 0;
+       int j = 0;
+
+       for (i = 0; i < master->size; i++) {
+               guint64 inode =  master->entries[i].inode;
+               guint64 slave_inode;
+               while (j < slave->size && (slave_inode = slave->entries[j].inode) <= inode) {
+
+                       if (slave_inode < inode) {
+                               if (bdb_backend_delete_inode(bdb, slave_inode) != SQLITE_OK)
+                                       goto abort;
+
+                               cfs_debug("deleted inode %016" PRIX64, slave_inode);
+                       }
+                       j++;
+               }
+               if (j >= slave->size)
+                       break;
+       }
+
+       while (j < slave->size) {
+               guint64 slave_inode = slave->entries[j].inode;
+
+               if (bdb_backend_delete_inode(bdb, slave_inode) != SQLITE_OK)
+                       goto abort;
+
+               cfs_debug("deleted inode %016" PRIX64, slave_inode);
+
+               j++;
+       }
+
+       /* now add all updates */
+
+       GList *l = inodes;
+       while (l) {
+               memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
+
+               tree_entry_debug(te);
+
+               if (backend_write_inode(
+                           bdb->db, bdb->stmt_replace_entry, te->inode, te->parent, te->version,
+                           te->writer, te->mtime, te->size, te->type,
+                           te->inode ? te->name : VERSIONFILENAME, te->data.value) !=  SQLITE_OK) {
+                       goto abort;
+               }
+
+               l = g_list_next(l);
+       }
+
+       /* now try to reload */
+       root = memdb_tree_entry_new("");
+       root->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
+       root->type = DT_DIR;
+
+       index = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL,
+                                     (GDestroyNotify)memdb_tree_entry_free);
+
+       g_hash_table_replace(index, &root->inode, root);
+
+       if (!bdb_backend_load_index(bdb, root, index))
+               goto abort;
+
+       if (!memdb->root->version) {
+               cfs_critical("new index has version 0 - internal error");
+               goto abort;
+       }
+
+       memdb_index_t *new_idx = memdb_encode_index(index, root);
+       if (!new_idx) {
+               cfs_critical("cant encode new index - internal error");
+               goto abort;
+       }
+
+       int idx_equal = (new_idx->bytes == master->bytes &&
+                        (memcmp(master, new_idx, new_idx->bytes) == 0));
+
+       g_free (new_idx);
+
+       if (!idx_equal) {
+               cfs_critical("new index does not match master index - internal error");
+               goto abort;
+       }
+
+       rc = sqlite3_step(bdb->stmt_commit);
+       sqlite3_reset(bdb->stmt_commit);
+       if (rc != SQLITE_DONE) {
+               cfs_critical("commit transaction failed: %s\n", sqlite3_errmsg(bdb->db));
+               goto abort;
+       }
+
+       g_hash_table_destroy(memdb->index);
+       memdb->index = index;
+       memdb->root = root;
+       index = NULL;
+       root = NULL;
+
+       record_memdb_reload();
+
+       if (!memdb_recreate_vmlist(memdb)) {
+               cfs_critical("memdb_recreate_vmlist failed");
+               memdb->errors = 1;
+               result = FALSE;
+               goto ret;
+       }
+
+       memdb_update_locks(memdb);
+
+       result = TRUE;
+
+ret:
+       g_mutex_unlock (&memdb->mutex);
+
+       if (index)
+               g_hash_table_destroy(index);
+
+       cfs_debug("leave bdb_backend_commit_update (%d)", result);
+
+       return result;
+
+abort:
+
+       memdb->errors = 1;
+
+       rc = sqlite3_step(bdb->stmt_rollback);
+       sqlite3_reset(bdb->stmt_rollback);
+       if (rc != SQLITE_DONE)
+               cfs_critical("rollback transaction failed: %s\n", sqlite3_errmsg(bdb->db));
+
+       result = FALSE;
+
+       goto ret;
+}
+
+void bdb_backend_close(db_backend_t *bdb)
+{
+       g_return_if_fail(bdb != NULL);
+
+       sqlite3_finalize(bdb->stmt_insert_entry);
+       sqlite3_finalize(bdb->stmt_replace_entry);
+       sqlite3_finalize(bdb->stmt_update_entry);
+       sqlite3_finalize(bdb->stmt_delete_entry);
+       sqlite3_finalize(bdb->stmt_begin);
+       sqlite3_finalize(bdb->stmt_commit);
+       sqlite3_finalize(bdb->stmt_rollback);
+       sqlite3_finalize(bdb->stmt_load_all);
+
+       int rc;
+       if ((rc = sqlite3_close(bdb->db)) != SQLITE_OK) {
+               cfs_critical("sqlite3_close failed: %d\n", rc);
+       }
+
+       sqlite3_shutdown();
+
+       g_free(bdb);
+}
+
+db_backend_t *bdb_backend_open(
+       const char *filename,
+       memdb_tree_entry_t *root,
+       GHashTable *index)
+{
+       g_return_val_if_fail(filename != NULL, NULL);
+       g_return_val_if_fail(root != NULL, NULL);
+       g_return_val_if_fail(index != NULL, NULL);
+
+       db_backend_t *bdb = g_new0(db_backend_t, 1);
+       g_return_val_if_fail(bdb != NULL, NULL);
+
+       int rc;
+
+       sqlite3_initialize();
+
+       if (!(bdb->db = bdb_create(filename)))
+               goto fail;
+
+       // tell the query planner that the prepared statement will be retained for a long time and
+       // probably reused many times
+       const unsigned int flags = SQLITE_PREPARE_PERSISTENT;
+
+       rc = sqlite3_prepare_v3(bdb->db, sql_insert_entry, -1, flags, &bdb->stmt_insert_entry, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("sqlite3_prepare 'sql_insert_entry' failed: %s\n",
+                            sqlite3_errmsg(bdb->db));
+               goto fail;
+       }
+       rc = sqlite3_prepare_v3(bdb->db, sql_update_entry, -1, flags, &bdb->stmt_update_entry, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("sqlite3_prepare 'sql_update_entry' failed: %s\n",
+                            sqlite3_errmsg(bdb->db));
+               goto fail;
+       }
+       rc = sqlite3_prepare_v3(bdb->db, sql_replace_entry, -1, flags, &bdb->stmt_replace_entry, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("sqlite3_prepare 'sql_replace_entry' failed: %s\n",
+                            sqlite3_errmsg(bdb->db));
+               goto fail;
+       }
+       rc = sqlite3_prepare_v3(bdb->db, sql_delete_entry, -1, flags, &bdb->stmt_delete_entry, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("sqlite3_prepare 'sql_delete_entry' failed: %s\n",
+                            sqlite3_errmsg(bdb->db));
+               goto fail;
+       }
+       rc = sqlite3_prepare_v3(bdb->db, sql_begin, -1, flags, &bdb->stmt_begin, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("sqlite3_prepare 'sql_begin' failed: %s\n",
+                            sqlite3_errmsg(bdb->db));
+               goto fail;
+       }
+       rc = sqlite3_prepare_v3(bdb->db, sql_commit, -1, flags, &bdb->stmt_commit, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("sqlite3_prepare 'sql_commit' failed: %s\n",
+                            sqlite3_errmsg(bdb->db));
+               goto fail;
+       }
+       rc = sqlite3_prepare_v3(bdb->db, sql_rollback, -1, flags, &bdb->stmt_rollback, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("sqlite3_prepare 'sql_rollback' failed: %s\n",
+                            sqlite3_errmsg(bdb->db));
+               goto fail;
+       }
+       rc = sqlite3_prepare_v3(bdb->db, sql_load_all, -1, flags, &bdb->stmt_load_all, NULL);
+       if (rc != SQLITE_OK) {
+               cfs_critical("sqlite3_prepare 'sql_load_all' failed: %s\n",
+                            sqlite3_errmsg(bdb->db));
+               goto fail;
+       }
+
+       if (!bdb_backend_load_index(bdb, root, index))
+               goto fail;
+
+       if (!root->version) {
+               root->version++;
+
+               guint32 mtime = time(NULL);
+
+               if (bdb_backend_write(bdb, 0, 0, root->version, 0, mtime,
+                                     0, DT_REG, NULL, NULL, 0) != SQLITE_OK)
+                       goto fail;
+       }
+
+       return bdb;
+
+fail:
+       bdb_backend_close(bdb);
+
+       return NULL;
+}
diff --git a/src/pmxcfs/dcdb.c b/src/pmxcfs/dcdb.c
new file mode 100644 (file)
index 0000000..28e9ae6
--- /dev/null
@@ -0,0 +1,960 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define G_LOG_DOMAIN "dcdb"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <sys/epoll.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include "cfs-utils.h"
+#include "loop.h"
+#include "dcdb.h"
+#include "status.h"
+
+typedef struct {
+       memdb_index_t *master;
+       memdb_index_t *idx;
+       GList *updates;
+} dcdb_sync_info_t;
+
+void
+dcdb_send_unlock(
+       dfsm_t *dfsm,
+       const char *path,
+       const guchar csum[32],
+       gboolean request)
+{
+       g_return_if_fail(dfsm != NULL);
+       g_return_if_fail(path != NULL);
+       g_return_if_fail(csum != NULL);
+
+       struct iovec iov[2];
+
+       iov[0].iov_base = (char *)csum;
+       iov[0].iov_len = 32;
+
+       iov[1].iov_base = (char *)path;
+       iov[1].iov_len = strlen(path) + 1;
+
+       if (!cfs_is_quorate())
+               return;
+
+       dcdb_message_t msg_type = request ? 
+               DCDB_MESSAGE_CFS_UNLOCK_REQUEST : DCDB_MESSAGE_CFS_UNLOCK;
+       
+       dfsm_send_message_sync(dfsm, msg_type, iov, 2, NULL);
+}
+
+static gboolean 
+dcdb_parse_unlock_request(
+       const void *msg,
+       size_t msg_len,
+       const char **path,
+       const guchar **csum)
+
+{
+       g_return_val_if_fail(msg != NULL, FALSE);
+       g_return_val_if_fail(path != NULL, FALSE);
+       g_return_val_if_fail(csum != NULL, FALSE);
+
+       if (msg_len < 33) {
+               cfs_critical("received short unlock message (%zu < 33)", msg_len);
+               return FALSE;
+       }
+
+       char *msg_str = (char *) msg;
+
+       *csum = (guchar *) msg_str;
+       msg_str += 32; msg_len -= 32;
+
+       *path = msg_str;
+       if ((*path)[msg_len - 1] != 0) {
+               cfs_critical("received mailformed unlock message - 'path' not terminated");
+               *path = NULL;
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+int 
+dcdb_send_fuse_message(
+       dfsm_t *dfsm,
+       dcdb_message_t msg_type,
+       const char *path,
+       const char *to,
+       const char *buf,
+       guint32 size,
+       guint32 offset,
+       guint32 flags)
+{
+       struct iovec iov[8];
+
+       iov[0].iov_base = (char *)&size;
+       iov[0].iov_len = sizeof(size);
+
+       iov[1].iov_base = (char *)&offset;
+       iov[1].iov_len = sizeof(offset);
+
+       guint32 pathlen = path ? strlen(path) + 1 : 0;
+       iov[2].iov_base = (char *)&pathlen;
+       iov[2].iov_len = sizeof(pathlen);
+
+       guint32 tolen = to ? strlen(to) + 1 : 0;
+       iov[3].iov_base = (char *)&tolen;
+       iov[3].iov_len = sizeof(tolen);
+
+       iov[4].iov_base = (char *)&flags;
+       iov[4].iov_len = sizeof(flags);
+
+       iov[5].iov_base = (char *)path;
+       iov[5].iov_len = pathlen;
+
+       iov[6].iov_base = (char *)to;
+       iov[6].iov_len = tolen;
+
+       iov[7].iov_base = (char *)buf;
+       iov[7].iov_len = size;
+
+       dfsm_result_t rc;
+       memset(&rc, 0, sizeof(rc));
+       rc.result = -EBUSY;
+
+       if (!cfs_is_quorate())
+               return -EACCES;
+
+       if (dfsm_send_message_sync(dfsm, msg_type, iov, 8, &rc))
+               return rc.result;
+
+       return -EACCES;
+}
+
+static gboolean 
+dcdb_parse_fuse_message(
+       const void *msg,
+       size_t msg_len,
+       const char **path,
+       const char **to,
+       const char **buf,
+       guint32 *size,
+       guint32 *offset,
+       guint32 *flags)
+
+{
+       g_return_val_if_fail(msg != NULL, FALSE);
+       g_return_val_if_fail(path != NULL, FALSE);
+       g_return_val_if_fail(to != NULL, FALSE);
+       g_return_val_if_fail(buf != NULL, FALSE);
+       g_return_val_if_fail(size != NULL, FALSE);
+       g_return_val_if_fail(offset != NULL, FALSE);
+       g_return_val_if_fail(flags != NULL, FALSE);
+
+       if (msg_len < 20) {
+               cfs_critical("received short fuse message (%zu < 20)", msg_len);
+               return FALSE;
+       }
+
+       const uint8_t *msg_ptr = msg;
+
+       *size = *((guint32 *)msg_ptr);
+       msg_ptr += 4; msg_len -= 4;
+
+       *offset = *((guint32 *)msg_ptr);
+       msg_ptr += 4; msg_len -= 4;
+
+       guint32 pathlen = *((guint32 *)msg_ptr);
+       msg_ptr += 4; msg_len -= 4;
+
+       guint32 tolen = *((guint32 *)msg_ptr);
+       msg_ptr += 4; msg_len -= 4;
+
+       *flags = *((guint32 *)msg_ptr);
+       msg_ptr += 4; msg_len -= 4;
+
+       if (msg_len != ((*size) + pathlen + tolen)) {
+               cfs_critical("received mailformed fuse message");
+               return FALSE;
+       }
+
+       *path = (char *)msg_ptr;
+       msg_ptr += pathlen; msg_len -= pathlen;
+
+       if (pathlen) {
+               if ((*path)[pathlen - 1] != 0) {
+                       cfs_critical("received mailformed fuse message - 'path' not terminated");
+                       *path = NULL;
+                       return FALSE;
+               }
+       } else {
+               *path = NULL;
+       }
+
+       *to = (char *)msg_ptr;
+       msg_ptr += tolen; msg_len -= tolen;
+
+       if (tolen) {
+               if ((*to)[tolen - 1] != 0) {
+                       cfs_critical("received mailformed fuse message - 'to' not terminated");
+                       *to = NULL;
+                       return FALSE;
+               }
+       } else {
+               *to = NULL;
+       }
+
+       *buf = (*size) ? (const char*)msg_ptr : NULL;
+
+       return TRUE;
+}
+
+static gboolean 
+dcdb_send_update_inode(
+       dfsm_t *dfsm, 
+       memdb_tree_entry_t *te)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+       g_return_val_if_fail(te != NULL, FALSE);
+
+       int len;
+       struct iovec iov[20];
+
+       uint32_t namelen = strlen(te->name) + 1;
+
+       iov[0].iov_base = (char *)&te->parent;
+       iov[0].iov_len = sizeof(te->parent);
+       iov[1].iov_base = (char *)&te->inode;
+       iov[1].iov_len = sizeof(te->inode);
+       iov[2].iov_base = (char *)&te->version;
+       iov[2].iov_len = sizeof(te->version);
+       iov[3].iov_base = (char *)&te->writer;
+       iov[3].iov_len = sizeof(te->writer);
+       iov[4].iov_base = (char *)&te->mtime;
+       iov[4].iov_len = sizeof(te->mtime);
+       iov[5].iov_base = (char *)&te->size;
+       iov[5].iov_len = sizeof(te->size);
+       iov[6].iov_base = (char *)&namelen;
+       iov[6].iov_len = sizeof(namelen);
+       iov[7].iov_base = (char *)&te->type;
+       iov[7].iov_len = sizeof(te->type);
+       iov[8].iov_base = (char *)te->name;
+       iov[8].iov_len = namelen;
+
+       len = 9;
+       if (te->type == DT_REG && te->size) {
+               iov[9].iov_base = (char *)te->data.value;
+               iov[9].iov_len = te->size;
+               len++;
+       }
+
+       if (dfsm_send_update(dfsm, iov, len) != CS_OK)
+               return FALSE;
+
+       return TRUE;
+}
+
+memdb_tree_entry_t *
+dcdb_parse_update_inode(
+       const void *msg, 
+       size_t msg_len)
+{
+       if (msg_len < 40) {
+               cfs_critical("received short message (msg_len < 40)");
+               return NULL;
+       }
+
+       char *msg_ptr = (char *) msg;
+
+       guint64 parent = *((guint64 *)msg_ptr);
+       msg_ptr += 8; msg_len -= 8;
+       guint64 inode = *((guint64 *)msg_ptr);
+       msg_ptr += 8; msg_len -= 8;
+       guint64 version = *((guint64 *)msg_ptr);
+       msg_ptr += 8; msg_len -= 8;
+
+       guint32 writer = *((guint32 *)msg_ptr);
+       msg_ptr += 4; msg_len -= 4;
+       guint32 mtime = *((guint32 *)msg_ptr);
+       msg_ptr += 4; msg_len -= 4;
+       guint32 size = *((guint32 *)msg_ptr);
+       msg_ptr += 4; msg_len -= 4;
+       guint32 namelen = *((guint32 *)msg_ptr);
+       msg_ptr += 4; msg_len -= 4;
+
+       char type = *((char *)msg_ptr);
+       msg_ptr += 1; msg_len -= 1;
+
+       if (!(type == DT_REG || type == DT_DIR)) {
+               cfs_critical("received mailformed message (unknown inode type %d)", type);
+               return NULL;
+       }
+
+       if (msg_len != (size + namelen)) {
+               cfs_critical("received mailformed message (msg_len != (size + namelen))");
+               return NULL;
+       }
+
+       char *name = msg_ptr;
+       msg_ptr += namelen; msg_len -= namelen;
+
+       const void *data = msg_ptr;
+       
+       if (name[namelen - 1] != 0) {
+               cfs_critical("received mailformed message (name[namelen-1] != 0)");
+               return NULL;
+       }
+
+       memdb_tree_entry_t *te = memdb_tree_entry_new(name);
+       if (!te)
+               return NULL;
+
+       te->parent = parent;
+       te->version = version;
+       te->inode = inode;
+       te->writer = writer;
+       te->mtime = mtime;
+       te->size = size;
+       te->type = type;
+
+       if (te->type == DT_REG && te->size) {
+               te->data.value = g_memdup2(data, te->size);
+               if (!te->data.value) {
+                       memdb_tree_entry_free(te);
+                       return NULL;
+               }
+       }
+
+       return te;
+}
+
+void 
+dcdb_sync_corosync_conf(
+       memdb_t *memdb, 
+       gboolean notify_corosync)
+{
+       g_return_if_fail(memdb != NULL);
+
+       int len;
+       gpointer data = NULL;
+
+       len = memdb_read(memdb, "corosync.conf", &data);
+       if (len <= 0)
+               return;
+
+       guint64 new_version = cluster_config_version(data, len);
+       if (!new_version) {
+               cfs_critical("unable to parse cluster config_version");
+               return;
+       }
+
+       char *old_data = NULL;
+       gsize old_length = 0;
+       guint64 old_version = 0;
+
+       GError *err = NULL;
+       if (!g_file_get_contents(HOST_CLUSTER_CONF_FN, &old_data, &old_length, &err)) {
+               if (!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
+                       cfs_critical("unable to read cluster config file '%s' - %s", 
+                                    HOST_CLUSTER_CONF_FN, err->message);
+               }
+               g_error_free (err);
+       } else {
+               if (old_length)
+                       old_version = cluster_config_version(old_data, old_length);
+       }
+
+       /* test if something changed - return if no changes */
+       if (data && old_data && (old_length == len) && 
+           !memcmp(data, old_data, len))
+               goto ret;
+
+       if (new_version < old_version) {
+               cfs_critical("local corosync.conf is newer");
+               goto ret;
+       }
+
+       if (!atomic_write_file(HOST_CLUSTER_CONF_FN, data, len, 0644, 0))
+               goto ret;
+
+       cfs_message("wrote new corosync config '%s' (version = %" G_GUINT64_FORMAT ")",
+                   HOST_CLUSTER_CONF_FN, new_version);
+       
+       if (notify_corosync && old_version) {
+               /*
+                * sleep for 1s to hopefully allow new config to propagate
+                * FIXME: actually query the status somehow?
+                */
+               sleep(1);
+
+               /* tell corosync that there is a new config file */
+               cfs_debug ("run corosync-cfgtool -R");
+               int status = system("corosync-cfgtool -R >/dev/null 2>&1");
+               if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+                       cfs_critical("corosync-cfgtool -R failed with exit code %d\n", WEXITSTATUS(status));
+               }
+               cfs_debug ("end corosync-cfgtool -R");
+       }
+
+ret:
+
+       if (data)
+               g_free(data);
+       
+       if (old_data)
+               g_free(old_data);
+}
+
+static gpointer
+dcdb_get_state(        
+       dfsm_t *dfsm, 
+       gpointer data,
+       unsigned int *res_len)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+       g_return_val_if_fail(data != NULL, FALSE);
+
+       memdb_t *memdb = (memdb_t *)data;
+
+       g_return_val_if_fail(memdb->root != NULL, FALSE);
+
+       cfs_debug("enter %s %016" PRIX64 " %08X", __func__, (uint64_t) memdb->root->version, memdb->root->mtime);
+
+       g_mutex_lock (&memdb->mutex);
+       memdb_index_t *idx = memdb_encode_index(memdb->index, memdb->root);
+       g_mutex_unlock (&memdb->mutex);
+
+       if (idx) {
+               *res_len = idx->bytes;
+       }
+
+       return idx;
+}
+
+static int
+dcdb_select_leader(
+       int node_count,
+       memdb_index_t *idx[])
+{
+       g_return_val_if_fail(idx != NULL, -1);
+
+       cfs_debug("enter %s", __func__);
+
+       int leader = -1;
+
+       /* try select most actual data - compare 'version' an 'time of last write'
+        * NOTE: syncinfo members are sorted 
+        */
+       for (int i = 0; i < node_count; i++) {
+               if (leader < 0) {
+                       leader = i;
+               } else {
+                       memdb_index_t *leaderidx = idx[leader];
+                               
+                       if (idx[i]->version == leaderidx->version &&
+                           idx[i]->mtime > leaderidx->mtime) {
+                               leader = i;
+                       } else if (idx[i]->version > leaderidx->version) {
+                               leader = i;
+                       }
+               }
+       }
+
+       cfs_debug ("leave %s (%d)", __func__, leader);
+
+       return leader;
+}
+
+static gboolean 
+dcdb_create_and_send_updates(
+       dfsm_t *dfsm,
+       memdb_t *memdb, 
+       memdb_index_t *master,
+       int node_count,
+       memdb_index_t *idx[])
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+       g_return_val_if_fail(memdb != NULL, FALSE);
+       g_return_val_if_fail(master != NULL, FALSE);
+
+       cfs_debug("enter %s", __func__);
+
+       gboolean res = FALSE;
+
+       GHashTable *updates = g_hash_table_new(g_int64_hash, g_int64_equal);
+       if (!updates)
+               goto ret;
+
+       g_mutex_lock (&memdb->mutex);
+
+       for (int n = 0; n < node_count; n++) {
+               memdb_index_t *slave = idx[n];
+
+               if (slave == master)
+                       continue;
+
+               int j = 0;
+
+               for (int i = 0; i < master->size; i++) {
+                       guint64 inode =  master->entries[i].inode;
+                       while (j < slave->size && slave->entries[j].inode < inode)
+                               j++;
+
+                       if (memcmp(&slave->entries[j], &master->entries[i], 
+                                   sizeof(memdb_index_extry_t)) == 0) {
+                               continue;
+                       }
+
+                       if (g_hash_table_lookup(updates, &inode))
+                               continue;
+                       
+                       cfs_debug("found different inode %d %016" PRIX64, i, (uint64_t) inode);
+                       
+                       memdb_tree_entry_t *te, *cpy;
+
+                       if (!(te = g_hash_table_lookup(memdb->index, &inode))) {
+                               cfs_critical("can get inode data for inode %016" PRIX64, (uint64_t) inode);
+                               goto ret;
+                       }
+                       
+                       cpy = memdb_tree_entry_copy(te, 1);
+                       g_hash_table_replace(updates, &cpy->inode, cpy);
+               }
+       }
+
+       g_mutex_unlock (&memdb->mutex);
+
+       /* send updates */
+
+       GHashTableIter iter;
+       gpointer key, value;
+       int count = 0;
+
+       cfs_message("start sending inode updates");
+
+       g_hash_table_iter_init (&iter, updates);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               memdb_tree_entry_t *te = (memdb_tree_entry_t *)value;
+               count++;
+
+               if (!dcdb_send_update_inode(dfsm, te)) {
+                       /* tolerate error here */
+                       cfs_critical("sending update inode failed %016" PRIX64, (uint64_t) te->inode);
+               } else {
+                       cfs_debug("sent update inode %016" PRIX64, (uint64_t) te->inode);
+               }
+                       
+               memdb_tree_entry_free(te);
+       }
+
+       cfs_message("sent all (%d) updates", count);
+
+       if (dfsm_send_update_complete(dfsm) != CS_OK) {
+               cfs_critical("failed to send UPDATE_COMPLETE message");
+               goto ret;
+       }
+
+       res = TRUE;
+
+ ret:
+       if (updates)
+               g_hash_table_destroy(updates);
+
+       cfs_debug("leave %s (%d)", __func__, res);
+
+       return res;
+}
+
+static int
+dcdb_process_state_update(
+       dfsm_t *dfsm, 
+       gpointer data,
+       dfsm_sync_info_t *syncinfo)
+{
+       g_return_val_if_fail(dfsm != NULL, -1);
+       g_return_val_if_fail(data != NULL, -1);
+       g_return_val_if_fail(syncinfo != NULL, -1);
+
+       memdb_t *memdb = (memdb_t *)data;
+
+       cfs_debug("enter %s", __func__);
+
+       dcdb_sync_info_t *localsi = g_new0(dcdb_sync_info_t, 1);
+       if (!localsi)
+               return -1;
+
+       syncinfo->data = localsi;
+
+       memdb_index_t *idx[syncinfo->node_count];
+
+       for (int i = 0; i < syncinfo->node_count; i++) {
+               dfsm_node_info_t *ni = &syncinfo->nodes[i];
+
+               if (ni->state_len < sizeof(memdb_index_t)) {
+                       cfs_critical("received short memdb index (len < sizeof(memdb_index_t))");
+                       return -1;
+               }
+
+               idx[i] = (memdb_index_t *)ni->state;
+
+               if (ni->state_len != idx[i]->bytes) {
+                       cfs_critical("received mailformed memdb index (len != idx->bytes)");
+                       return -1;
+               }
+       }
+       
+       /* select leader - set mode */
+       int leader = dcdb_select_leader(syncinfo->node_count, idx);
+       if (leader < 0) {
+               cfs_critical("unable to select leader failed");
+               return -1;
+       }
+
+       cfs_message("leader is %d/%d", syncinfo->nodes[leader].nodeid, syncinfo->nodes[leader].pid);
+
+       memdb_index_t *leaderidx = idx[leader];
+       localsi->master = leaderidx;
+
+       GString *synced_member_ids = g_string_new(NULL);
+       g_string_append_printf(synced_member_ids, "%d/%d", syncinfo->nodes[leader].nodeid, syncinfo->nodes[leader].pid);
+
+       for (int i = 0; i < syncinfo->node_count; i++) {
+               dfsm_node_info_t *ni = &syncinfo->nodes[i];
+               if (i == leader) {
+                       ni->synced = 1;
+               } else {
+                       if (leaderidx->bytes == idx[i]->bytes &&
+                           memcmp(leaderidx, idx[i], leaderidx->bytes) == 0) {
+                               ni->synced = 1;
+                               g_string_append_printf(synced_member_ids, ", %d/%d", ni->nodeid, ni->pid);
+                       }
+               }
+               if (dfsm_nodeid_is_local(dfsm, ni->nodeid, ni->pid)) 
+                       localsi->idx = idx[i];
+       }
+       cfs_message("synced members: %s", synced_member_ids->str);
+       g_string_free(synced_member_ids, 1);
+
+       /* send update */
+       if (dfsm_nodeid_is_local(dfsm, syncinfo->nodes[leader].nodeid, syncinfo->nodes[leader].pid)) {
+               if (!dcdb_create_and_send_updates(dfsm, memdb, leaderidx, syncinfo->node_count, idx))
+                       return -1;
+       }
+
+       return 0;
+}
+
+static int 
+dcdb_process_update(
+       dfsm_t *dfsm, 
+       gpointer data,
+       dfsm_sync_info_t *syncinfo,
+       uint32_t nodeid,
+       uint32_t pid,
+       const void *msg,
+       size_t msg_len)
+{
+       g_return_val_if_fail(dfsm != NULL, -1);
+       g_return_val_if_fail(data != NULL, -1);
+       g_return_val_if_fail(msg != NULL, -1);
+       g_return_val_if_fail(syncinfo != NULL, -1);
+       g_return_val_if_fail(syncinfo->data != NULL, -1);
+       
+       cfs_debug("enter %s", __func__);
+
+       memdb_tree_entry_t *te;
+
+       if (!(te = dcdb_parse_update_inode(msg, msg_len)))
+               return -1;
+
+       cfs_debug("received inode update %016" PRIX64 " from node %d",
+                 (uint64_t) te->inode, nodeid);
+
+       dcdb_sync_info_t *localsi = (dcdb_sync_info_t *)syncinfo->data;
+
+       localsi->updates = g_list_append(localsi->updates, te);
+
+       return 0;
+}
+
+static int
+dcdb_commit(
+       dfsm_t *dfsm, 
+       gpointer data,
+       dfsm_sync_info_t *syncinfo)
+{
+       g_return_val_if_fail(dfsm != NULL, -1);
+       g_return_val_if_fail(data != NULL, -1);
+       g_return_val_if_fail(syncinfo != NULL, -1);
+       g_return_val_if_fail(syncinfo->data != NULL, -1);
+       
+       memdb_t *memdb = (memdb_t *)data;
+
+       cfs_debug("enter %s", __func__);
+
+       dcdb_sync_info_t *localsi = (dcdb_sync_info_t *)syncinfo->data;
+
+       guint count = g_list_length(localsi->updates); 
+
+       cfs_message("update complete - trying to commit (got %u inode updates)", count);
+
+       if (!bdb_backend_commit_update(memdb, localsi->master, localsi->idx, localsi->updates)) 
+               return -1;
+
+       dcdb_sync_corosync_conf(memdb, FALSE);
+
+       return 0;
+}
+
+static int 
+dcdb_cleanup(
+       dfsm_t *dfsm, 
+       gpointer data,
+       dfsm_sync_info_t *syncinfo)
+{
+       g_return_val_if_fail(dfsm != NULL, -1);
+       g_return_val_if_fail(data != NULL, -1);
+       g_return_val_if_fail(syncinfo != NULL, -1);
+       g_return_val_if_fail(syncinfo->data != NULL, -1);
+
+       cfs_debug("enter %s", __func__);
+
+       dcdb_sync_info_t *localsi = (dcdb_sync_info_t *)syncinfo->data;
+
+       GList *iter = localsi->updates;
+       while (iter) {
+               memdb_tree_entry_t *te = (memdb_tree_entry_t *)iter->data;
+               memdb_tree_entry_free(te);
+               iter = g_list_next(iter);
+       }
+       g_list_free(localsi->updates);
+
+       g_free(localsi);
+
+       return 0;
+}
+
+gboolean 
+dcdb_checksum(
+       dfsm_t *dfsm, 
+       gpointer data,
+       unsigned char *csum,
+       size_t csum_len)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+       g_return_val_if_fail(csum != NULL, FALSE);
+
+       memdb_t *memdb = (memdb_t *)data;
+
+       g_return_val_if_fail(memdb != NULL, FALSE);
+
+       cfs_debug("enter %s %016" PRIX64 " %08X", __func__, memdb->root->version, memdb->root->mtime);
+
+       g_mutex_lock (&memdb->mutex);
+       gboolean res = memdb_compute_checksum(memdb->index, memdb->root, csum, csum_len);
+       g_mutex_unlock (&memdb->mutex);
+
+       cfs_debug("leave %s %016" PRIX64 " (%d)", __func__, *( (uint64_t *) csum), res);
+
+       return res;
+}
+
+static int
+dcdb_deliver(
+       dfsm_t *dfsm,
+       gpointer data,
+       int *res_ptr,
+       uint32_t nodeid,
+       uint32_t pid,
+       uint16_t msg_type,
+       uint32_t msg_time,
+       const void *msg,
+       size_t msg_len)
+{
+       g_return_val_if_fail(dfsm != NULL, -1);
+       g_return_val_if_fail(msg != NULL, -1);
+
+       memdb_t *memdb = (memdb_t *)data;
+
+       g_return_val_if_fail(memdb != NULL, -1);
+       g_return_val_if_fail(res_ptr != NULL, -1);
+
+       int res = 1;
+
+       int msg_result = -ENOTSUP;
+
+       if (!DCDB_VALID_MESSAGE_TYPE(msg_type)) 
+               goto unknown;
+
+       cfs_debug("process message %u (length = %zd)", msg_type, msg_len);
+       
+       if (!cfs_is_quorate()) {
+               cfs_critical("received write while not quorate - trigger resync");
+               msg_result = -EACCES;
+               goto leave;
+       }
+
+       const char *path, *to, *buf; 
+       guint32 size, offset, flags;
+       const guchar *csum;
+
+       if (msg_type == DCDB_MESSAGE_CFS_UNLOCK_REQUEST ||
+           msg_type == DCDB_MESSAGE_CFS_UNLOCK) {
+               msg_result = 0; /* ignored anyways */ 
+               
+               if (!dcdb_parse_unlock_request(msg, msg_len, &path, &csum))
+                       goto leave;
+
+               guchar cur_csum[32];
+               memdb_tree_entry_t *te = memdb_getattr(memdb, path);
+
+               if (te &&  te->type == DT_DIR &&
+                   path_is_lockdir(path) && memdb_tree_entry_csum(te, cur_csum) &&
+                   (memcmp(csum, cur_csum, 32) == 0)) {
+
+                       if (msg_type == DCDB_MESSAGE_CFS_UNLOCK) {
+
+                               cfs_debug("got valid unlock message");
+
+                               msg_result = memdb_delete(memdb, path, nodeid, msg_time);
+
+                       } else if (dfsm_lowest_nodeid(dfsm)) {
+
+                               cfs_debug("got valid unlock request message");
+                       
+                               if (memdb_lock_expired(memdb, path, csum)) {
+                                       cfs_debug("sending unlock message");
+                                       dcdb_send_unlock(dfsm, path, csum, FALSE);
+                               }
+                       }
+               }
+               memdb_tree_entry_free(te);
+
+       } else if (msg_type == DCDB_MESSAGE_CFS_WRITE) {
+
+               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
+                                            &size, &offset, &flags))
+                       goto leave;
+
+               msg_result = memdb_write(memdb, path, nodeid, msg_time,
+                                        buf, size, offset, flags);
+
+               if ((msg_result >= 0) && !strcmp(path, "corosync.conf"))
+                       dcdb_sync_corosync_conf(memdb, dfsm_nodeid_is_local(dfsm, nodeid, pid));
+
+       } else if (msg_type == DCDB_MESSAGE_CFS_CREATE) {
+
+               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
+                                            &size, &offset, &flags))
+                       goto leave;
+               
+               msg_result = memdb_create(memdb, path, nodeid, msg_time);
+
+               if ((msg_result >= 0) && !strcmp(path, "corosync.conf"))
+                       dcdb_sync_corosync_conf(memdb, dfsm_nodeid_is_local(dfsm, nodeid, pid));
+               
+       } else if (msg_type == DCDB_MESSAGE_CFS_MKDIR) {
+
+               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
+                                            &size, &offset, &flags))
+                       goto leave;
+               
+               msg_result = memdb_mkdir(memdb, path, nodeid, msg_time);
+               
+       } else if (msg_type == DCDB_MESSAGE_CFS_DELETE) {
+
+               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
+                                            &size, &offset, &flags))
+                       goto leave;
+
+               msg_result = memdb_delete(memdb, path, nodeid, msg_time);
+                                               
+       } else if (msg_type == DCDB_MESSAGE_CFS_RENAME) {
+
+               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
+                                            &size, &offset, &flags))
+                       goto leave;
+
+               msg_result = memdb_rename(memdb, path, to, nodeid, msg_time);
+               
+               if ((msg_result >= 0) && !strcmp(to, "corosync.conf"))
+                       dcdb_sync_corosync_conf(memdb, dfsm_nodeid_is_local(dfsm, nodeid, pid));
+                       
+       } else if (msg_type == DCDB_MESSAGE_CFS_MTIME) {
+
+               if (!dcdb_parse_fuse_message(msg, msg_len, &path, &to, &buf, 
+                                            &size, &offset, &flags))
+                       goto leave;
+               
+               /* Note: mtime is sent via offset field */
+               msg_result = memdb_mtime(memdb, path, nodeid, offset);
+               
+       } else {
+               goto unknown;
+       }
+
+       *res_ptr = msg_result;
+ret:
+       if (memdb->errors) {
+               dfsm_set_errormode(dfsm);
+               res = -1;
+       }
+
+       cfs_debug("leave %s (%d)", __func__, res);
+
+       return res;
+
+unknown:
+       cfs_critical("received unknown message type (msg_type == %u)", msg_type);
+leave:
+       res = -1;
+       goto ret;
+
+}
+
+static dfsm_callbacks_t dcdb_dfsm_callbacks = {
+       .dfsm_deliver_fn = dcdb_deliver,
+       .dfsm_get_state_fn = dcdb_get_state,
+       .dfsm_process_state_update_fn = dcdb_process_state_update,
+       .dfsm_process_update_fn = dcdb_process_update,
+       .dfsm_commit_fn = dcdb_commit,
+       .dfsm_cleanup_fn = dcdb_cleanup,
+       .dfsm_checksum_fn = dcdb_checksum,
+};
+
+dfsm_t *dcdb_new(memdb_t *memdb)
+{
+       g_return_val_if_fail(memdb != NULL, NULL);
+       return dfsm_new(memdb, DCDB_CPG_GROUP_NAME, G_LOG_DOMAIN, 
+                       DCDB_PROTOCOL_VERSION, &dcdb_dfsm_callbacks);
+}
diff --git a/src/pmxcfs/dcdb.h b/src/pmxcfs/dcdb.h
new file mode 100644 (file)
index 0000000..6de240a
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_DCDB_H_
+#define _PVE_DCDB_H_
+
+#include <glib.h>
+
+#include "dfsm.h"
+#include "memdb.h"
+
+#define DCDB_CPG_GROUP_NAME "pve_dcdb_v1"
+/* please increase protocol version if you want to stop older nodes */
+#define DCDB_PROTOCOL_VERSION 1
+#define DCDB_VERIFY_TIME (60*60)
+
+typedef enum {
+       DCDB_MESSAGE_CFS_WRITE = 1,
+       DCDB_MESSAGE_CFS_MKDIR = 2,
+       DCDB_MESSAGE_CFS_DELETE = 3,
+       DCDB_MESSAGE_CFS_RENAME = 4,
+       DCDB_MESSAGE_CFS_CREATE = 5,
+       DCDB_MESSAGE_CFS_MTIME = 6,
+       DCDB_MESSAGE_CFS_UNLOCK_REQUEST = 7,
+       DCDB_MESSAGE_CFS_UNLOCK = 8,
+} dcdb_message_t;
+
+#define DCDB_VALID_MESSAGE_TYPE(mt) (mt >= DCDB_MESSAGE_CFS_WRITE && mt <= DCDB_MESSAGE_CFS_UNLOCK)
+
+dfsm_t *dcdb_new(memdb_t *memdb);
+
+void dcdb_sync_corosync_conf(
+       memdb_t *memdb, 
+       gboolean notify_corosync);
+
+int dcdb_send_fuse_message(
+       dfsm_t *dfsm, 
+       dcdb_message_t msg_type,
+       const char *path, 
+       const char *to, 
+       const char *buf,
+       guint32 size, 
+       guint32 offset, 
+       guint32 flags);
+
+void
+dcdb_send_unlock(
+       dfsm_t *dfsm,
+       const char *path,
+       const guchar csum[32],
+       gboolean request);
+
+#endif /* _PVE_DCDB_H_ */
diff --git a/src/pmxcfs/dfsm.c b/src/pmxcfs/dfsm.c
new file mode 100644 (file)
index 0000000..84f7df7
--- /dev/null
@@ -0,0 +1,1697 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+
+/* NOTE: we try to keep the CPG handle as long as possible, because
+ * calling cpg_initialize/cpg_finalize multiple times from the 
+ * same process confuses corosync.
+ * Note: CS_ERR_LIBRARY is returned when corosync died
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <corosync/corotypes.h>
+#include <corosync/cpg.h>
+#include <glib.h>
+
+#include "cfs-utils.h"
+#include "dfsm.h"
+
+static cpg_callbacks_t cpg_callbacks;
+
+typedef enum {
+       DFSM_MODE_START = 0,
+       DFSM_MODE_START_SYNC = 1,
+       DFSM_MODE_SYNCED = 2,
+       DFSM_MODE_UPDATE = 3,
+
+       /* values >= 128 indicates abnormal/error conditions */
+       DFSM_ERROR_MODE_START = 128,
+       DFSM_MODE_LEAVE = 253,
+       DFSM_MODE_VERSION_ERROR = 254,
+       DFSM_MODE_ERROR = 255,
+} dfsm_mode_t;
+
+typedef enum {
+       DFSM_MESSAGE_NORMAL = 0,
+       DFSM_MESSAGE_SYNC_START = 1,
+       DFSM_MESSAGE_STATE = 2,
+       DFSM_MESSAGE_UPDATE = 3,
+       DFSM_MESSAGE_UPDATE_COMPLETE = 4,
+       DFSM_MESSAGE_VERIFY_REQUEST = 5,
+       DFSM_MESSAGE_VERIFY = 6,
+} dfsm_message_t;
+
+#define DFSM_VALID_STATE_MESSAGE(mt) (mt >= DFSM_MESSAGE_SYNC_START && mt <= DFSM_MESSAGE_VERIFY)
+
+typedef struct {
+       uint16_t type;
+       uint16_t subtype;
+       uint32_t protocol_version;
+       uint32_t time;
+       uint32_t reserved;
+} dfsm_message_header_t;
+
+typedef struct {
+       uint32_t epoch; // per process (not globally unique) 
+       uint32_t time;
+       uint32_t nodeid;
+       uint32_t pid;
+} dfsm_sync_epoch_t;
+
+typedef struct {
+       dfsm_message_header_t base;
+       dfsm_sync_epoch_t epoch;
+} dfsm_message_state_header_t;
+
+typedef struct {
+       dfsm_message_header_t base;
+       uint64_t count;
+} dfsm_message_normal_header_t;
+
+typedef struct {
+       uint32_t nodeid;
+       uint32_t pid;
+       uint64_t msg_count;
+       void *msg;
+       int msg_len; // fixme: unsigned?
+} dfsm_queued_message_t;
+
+struct dfsm {
+       const char *log_domain;
+       cpg_callbacks_t *cpg_callbacks;
+       dfsm_callbacks_t *dfsm_callbacks;
+       cpg_handle_t cpg_handle;
+       GMutex cpg_mutex;
+       struct cpg_name cpg_group_name;
+       uint32_t nodeid;
+       uint32_t pid;
+       int we_are_member;
+
+       guint32 protocol_version;
+       gpointer data;
+
+       gboolean joined;
+
+       /* mode is protected with mode_mutex */
+       GMutex mode_mutex;
+       dfsm_mode_t mode;
+
+       GHashTable *members; /* contains dfsm_node_info_t pointers  */
+       dfsm_sync_info_t *sync_info;
+       uint32_t local_epoch_counter;
+       dfsm_sync_epoch_t sync_epoch;
+       uint32_t lowest_nodeid; 
+       GSequence *msg_queue; 
+       GList *sync_queue;
+
+       /* synchrounous message transmission, protected with sync_mutex */
+       GMutex sync_mutex;
+       GCond sync_cond;
+       GHashTable *results;
+       uint64_t msgcount;
+       uint64_t msgcount_rcvd;
+
+       /* state verification */
+       guchar csum[32];
+       dfsm_sync_epoch_t csum_epoch;
+       uint64_t csum_id;
+       uint64_t csum_counter;
+};
+
+static gboolean dfsm_deliver_queue(dfsm_t *dfsm);
+static gboolean dfsm_deliver_sync_queue(dfsm_t *dfsm);
+
+gboolean 
+dfsm_nodeid_is_local(
+       dfsm_t *dfsm, 
+       uint32_t nodeid, 
+       uint32_t pid)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+
+       return (nodeid == dfsm->nodeid && pid == dfsm->pid); 
+}
+
+
+static void 
+dfsm_send_sync_message_abort(dfsm_t *dfsm)
+{
+       g_return_if_fail(dfsm != NULL);
+
+       g_mutex_lock (&dfsm->sync_mutex);
+       dfsm->msgcount_rcvd = dfsm->msgcount;
+       g_cond_broadcast (&dfsm->sync_cond);
+       g_mutex_unlock (&dfsm->sync_mutex);
+}
+
+static void 
+dfsm_record_local_result(
+       dfsm_t *dfsm,
+       uint64_t msg_count,
+       int msg_result,
+       gboolean processed)
+{
+       g_return_if_fail(dfsm != NULL);
+       g_return_if_fail(dfsm->results != NULL);
+
+       g_mutex_lock (&dfsm->sync_mutex);
+       dfsm_result_t *rp = (dfsm_result_t *)g_hash_table_lookup(dfsm->results, &msg_count);
+       if (rp) {
+               rp->result = msg_result;
+               rp->processed = processed;
+       }
+       dfsm->msgcount_rcvd = msg_count;
+       g_cond_broadcast (&dfsm->sync_cond);
+       g_mutex_unlock (&dfsm->sync_mutex);
+}
+
+static cs_error_t 
+dfsm_send_message_full(
+       dfsm_t *dfsm,
+       struct iovec *iov, 
+       unsigned int len,
+       int retry)
+{
+       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
+       g_return_val_if_fail(!len || iov != NULL, CS_ERR_INVALID_PARAM);
+
+       struct timespec tvreq = { .tv_sec = 0, .tv_nsec = 100000000 };
+       cs_error_t result;
+       int retries = 0;
+loop:
+       g_mutex_lock (&dfsm->cpg_mutex);
+       result = cpg_mcast_joined(dfsm->cpg_handle, CPG_TYPE_AGREED, iov, len);
+       g_mutex_unlock (&dfsm->cpg_mutex);
+       if (retry && result == CS_ERR_TRY_AGAIN) {
+               nanosleep(&tvreq, NULL);
+               ++retries;
+               if ((retries % 10) == 0)
+                       cfs_dom_message(dfsm->log_domain, "cpg_send_message retry %d", retries);
+               if (retries < 100)
+                       goto loop;
+       }
+
+       if (retries)
+               cfs_dom_message(dfsm->log_domain, "cpg_send_message retried %d times", retries);
+
+       if (result != CS_OK &&
+           (!retry || result != CS_ERR_TRY_AGAIN))
+               cfs_dom_critical(dfsm->log_domain, "cpg_send_message failed: %d", result);
+
+       return result;
+}
+
+static cs_error_t 
+dfsm_send_state_message_full(
+       dfsm_t *dfsm,
+       uint16_t type,
+       struct iovec *iov, 
+       unsigned int len) 
+{
+       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
+       g_return_val_if_fail(DFSM_VALID_STATE_MESSAGE(type), CS_ERR_INVALID_PARAM);
+       g_return_val_if_fail(!len || iov != NULL, CS_ERR_INVALID_PARAM);
+
+       dfsm_message_state_header_t header;
+       header.base.type = type;
+       header.base.subtype = 0;
+       header.base.protocol_version = dfsm->protocol_version;
+       header.base.time = time(NULL);
+       header.base.reserved = 0;
+
+       header.epoch = dfsm->sync_epoch;
+
+       struct iovec real_iov[len + 1];
+
+       real_iov[0].iov_base = (char *)&header;
+       real_iov[0].iov_len = sizeof(header);
+
+       for (int i = 0; i < len; i++)
+               real_iov[i + 1] = iov[i];
+
+       return dfsm_send_message_full(dfsm, real_iov, len + 1, 1);
+}
+
+cs_error_t 
+dfsm_send_update(
+       dfsm_t *dfsm,
+       struct iovec *iov, 
+       unsigned int len)
+{
+       return dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_UPDATE, iov, len);
+}
+
+cs_error_t 
+dfsm_send_update_complete(dfsm_t *dfsm)
+{
+       return dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_UPDATE_COMPLETE, NULL, 0);
+}
+
+
+cs_error_t 
+dfsm_send_message(
+       dfsm_t *dfsm,
+       uint16_t msgtype,
+       struct iovec *iov, 
+       int len)
+{
+       return dfsm_send_message_sync(dfsm, msgtype, iov, len, NULL);
+}
+
+cs_error_t 
+dfsm_send_message_sync(
+       dfsm_t *dfsm,
+       uint16_t msgtype,
+       struct iovec *iov, 
+       int len,
+       dfsm_result_t *rp)
+{
+       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
+       g_return_val_if_fail(!len || iov != NULL, CS_ERR_INVALID_PARAM);
+
+       g_mutex_lock (&dfsm->sync_mutex);
+       /* note: hold lock until message is sent - to guarantee ordering */
+       uint64_t msgcount = ++dfsm->msgcount;
+       if (rp) {
+               rp->msgcount = msgcount;
+               rp->processed = 0;
+               g_hash_table_replace(dfsm->results, &rp->msgcount, rp);
+       }
+
+       dfsm_message_normal_header_t header;
+       header.base.type = DFSM_MESSAGE_NORMAL;
+       header.base.subtype = msgtype;
+       header.base.protocol_version = dfsm->protocol_version;
+       header.base.time = time(NULL);
+       header.base.reserved = 0;
+       header.count = msgcount;
+
+       struct iovec real_iov[len + 1];
+
+       real_iov[0].iov_base = (char *)&header;
+       real_iov[0].iov_len = sizeof(header);
+
+       for (int i = 0; i < len; i++)
+               real_iov[i + 1] = iov[i];
+
+       cs_error_t result = dfsm_send_message_full(dfsm, real_iov, len + 1, 1);
+
+       g_mutex_unlock (&dfsm->sync_mutex);
+
+       if (result != CS_OK) {
+               cfs_dom_critical(dfsm->log_domain, "cpg_send_message failed: %d", result);
+
+               if (rp) {
+                       g_mutex_lock (&dfsm->sync_mutex);
+                       g_hash_table_remove(dfsm->results, &rp->msgcount);
+                       g_mutex_unlock (&dfsm->sync_mutex);
+               }
+               return result;
+       }
+
+       if (rp) {
+               g_mutex_lock (&dfsm->sync_mutex);
+
+               while (dfsm->msgcount_rcvd < msgcount)
+                       g_cond_wait (&dfsm->sync_cond, &dfsm->sync_mutex);
+
+      
+               g_hash_table_remove(dfsm->results, &rp->msgcount);
+               
+               g_mutex_unlock (&dfsm->sync_mutex);
+
+               return rp->processed ? CS_OK : CS_ERR_FAILED_OPERATION;
+       }
+
+       return CS_OK;
+}
+
+static gboolean 
+dfsm_send_checksum(dfsm_t *dfsm)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+
+       int len = 2;
+       struct iovec iov[len];
+
+       iov[0].iov_base = (char *)&dfsm->csum_id;
+       iov[0].iov_len = sizeof(dfsm->csum_id);
+       iov[1].iov_base = dfsm->csum;
+       iov[1].iov_len = sizeof(dfsm->csum);
+       
+       gboolean res = (dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_VERIFY, iov, len) == CS_OK);
+
+       return res;
+}
+
+static void 
+dfsm_free_queue_entry(gpointer data)
+{
+       dfsm_queued_message_t *qm = (dfsm_queued_message_t *)data;
+       g_free (qm->msg);
+       g_free (qm);
+}
+
+static void 
+dfsm_free_message_queue(dfsm_t *dfsm) 
+{
+       g_return_if_fail(dfsm != NULL);
+       g_return_if_fail(dfsm->msg_queue != NULL);
+
+       GSequenceIter *iter = g_sequence_get_begin_iter(dfsm->msg_queue);
+       GSequenceIter *end = g_sequence_get_end_iter(dfsm->msg_queue);
+       while (iter != end) {
+               GSequenceIter *cur = iter; 
+               iter = g_sequence_iter_next(iter);
+               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)
+                       g_sequence_get(cur);
+               dfsm_free_queue_entry(qm);
+               g_sequence_remove(cur);
+       }
+}
+
+static void 
+dfsm_free_sync_queue(dfsm_t *dfsm) 
+{
+       g_return_if_fail(dfsm != NULL);
+
+       GList *iter = dfsm->sync_queue;
+       while (iter) {
+               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)iter->data;
+               iter = g_list_next(iter);
+               dfsm_free_queue_entry(qm);
+       }
+
+       g_list_free(dfsm->sync_queue);
+       dfsm->sync_queue = NULL;
+}
+
+static gint 
+message_queue_sort_fn(
+       gconstpointer a,
+       gconstpointer b,
+       gpointer user_data)
+{
+       return ((dfsm_queued_message_t *)a)->msg_count - 
+               ((dfsm_queued_message_t *)b)->msg_count;
+}
+
+static dfsm_node_info_t *
+dfsm_node_info_lookup(
+       dfsm_t *dfsm,
+       uint32_t nodeid, 
+       uint32_t pid)
+{
+       g_return_val_if_fail(dfsm != NULL, NULL);
+       g_return_val_if_fail(dfsm->members != NULL, NULL);
+
+       dfsm_node_info_t info = { .nodeid = nodeid, .pid = pid };
+
+       return (dfsm_node_info_t *)g_hash_table_lookup(dfsm->members, &info);
+}
+
+static dfsm_queued_message_t *
+dfsm_queue_add_message(
+       dfsm_t *dfsm,
+       uint32_t nodeid,
+       uint32_t pid,
+       uint64_t msg_count,
+       const void *msg,
+       size_t msg_len)
+{
+       g_return_val_if_fail(dfsm != NULL, NULL);
+       g_return_val_if_fail(msg != NULL, NULL);
+       g_return_val_if_fail(msg_len != 0, NULL);
+
+       dfsm_node_info_t *ni = dfsm_node_info_lookup(dfsm, nodeid, pid);
+       if (!ni) {
+               cfs_dom_critical(dfsm->log_domain, "dfsm_node_info_lookup failed");
+               return NULL;
+       }
+
+       dfsm_queued_message_t *qm = g_new0(dfsm_queued_message_t, 1);
+       g_return_val_if_fail(qm != NULL, NULL);
+               
+       qm->nodeid = nodeid;
+       qm->pid = pid;
+       qm->msg = g_memdup2 (msg, msg_len);
+       qm->msg_len = msg_len;
+       qm->msg_count =  msg_count;
+
+       if (dfsm->mode == DFSM_MODE_UPDATE && ni->synced) {
+               dfsm->sync_queue = g_list_append(dfsm->sync_queue, qm);
+       } else {
+               /* NOTE: we only need to sort the queue because we resend all
+                * queued messages sometimes.   
+                */
+               g_sequence_insert_sorted(dfsm->msg_queue, qm, message_queue_sort_fn, NULL);
+       }
+
+       return qm;
+}
+
+static guint 
+dfsm_sync_info_hash(gconstpointer key)
+{
+       dfsm_node_info_t *info = (dfsm_node_info_t *)key;
+
+       return g_int_hash(&info->nodeid) + g_int_hash(&info->pid);
+}
+
+static gboolean 
+dfsm_sync_info_equal(
+       gconstpointer v1, 
+       gconstpointer v2)
+{
+       dfsm_node_info_t *info1 = (dfsm_node_info_t *)v1;
+       dfsm_node_info_t *info2 = (dfsm_node_info_t *)v2;
+
+       if (info1->nodeid == info2->nodeid &&
+           info1->pid == info2->pid)
+               return TRUE;
+
+       return FALSE;
+}
+
+static int 
+dfsm_sync_info_compare(
+       gconstpointer v1, 
+       gconstpointer v2)
+{
+       dfsm_node_info_t *info1 = (dfsm_node_info_t *)v1;
+       dfsm_node_info_t *info2 = (dfsm_node_info_t *)v2;
+
+       if (info1->nodeid != info2->nodeid)
+               return info1->nodeid - info2->nodeid;
+
+       return info1->pid - info2->pid;
+}
+
+static void 
+dfsm_set_mode(
+       dfsm_t *dfsm, 
+       dfsm_mode_t new_mode)
+{
+       g_return_if_fail(dfsm != NULL);
+
+       cfs_debug("dfsm_set_mode - set mode to %d", new_mode);
+
+       int changed = 0;
+       g_mutex_lock (&dfsm->mode_mutex);
+       if (dfsm->mode != new_mode) {
+               if (new_mode < DFSM_ERROR_MODE_START ||
+                   (dfsm->mode < DFSM_ERROR_MODE_START || new_mode >= dfsm->mode)) {
+                       dfsm->mode = new_mode;
+                       changed = 1;
+               }
+       }
+       g_mutex_unlock (&dfsm->mode_mutex);
+
+       if (!changed)
+               return;
+
+       if (new_mode == DFSM_MODE_START) {
+               cfs_dom_message(dfsm->log_domain, "start cluster connection");
+       } else if (new_mode == DFSM_MODE_START_SYNC) {
+               cfs_dom_message(dfsm->log_domain, "starting data syncronisation");
+       } else if (new_mode == DFSM_MODE_SYNCED) {
+               cfs_dom_message(dfsm->log_domain, "all data is up to date");
+               if (dfsm->dfsm_callbacks->dfsm_synced_fn)
+                       dfsm->dfsm_callbacks->dfsm_synced_fn(dfsm);
+       } else if (new_mode == DFSM_MODE_UPDATE) {
+               cfs_dom_message(dfsm->log_domain, "waiting for updates from leader");   
+       } else if (new_mode == DFSM_MODE_LEAVE) {
+               cfs_dom_critical(dfsm->log_domain, "leaving CPG group");        
+       } else if (new_mode == DFSM_MODE_ERROR) {
+               cfs_dom_critical(dfsm->log_domain, "serious internal error - stop cluster connection"); 
+       } else if (new_mode == DFSM_MODE_VERSION_ERROR) {
+               cfs_dom_critical(dfsm->log_domain, "detected newer protocol - please update this node"); 
+       }
+}
+
+static dfsm_mode_t 
+dfsm_get_mode(dfsm_t *dfsm)
+{
+       g_return_val_if_fail(dfsm != NULL, DFSM_MODE_ERROR);
+
+       g_mutex_lock (&dfsm->mode_mutex);
+       dfsm_mode_t mode = dfsm->mode;
+       g_mutex_unlock (&dfsm->mode_mutex);
+
+       return mode;
+}
+
+gboolean 
+dfsm_restartable(dfsm_t *dfsm)
+{
+       dfsm_mode_t mode = dfsm_get_mode(dfsm);
+
+       return !(mode == DFSM_MODE_ERROR || 
+                mode == DFSM_MODE_VERSION_ERROR);
+}
+
+void
+dfsm_set_errormode(dfsm_t *dfsm)
+{
+       dfsm_set_mode(dfsm, DFSM_MODE_ERROR);
+}
+
+static void 
+dfsm_release_sync_resources(
+       dfsm_t *dfsm,
+       const struct cpg_address *member_list, 
+       size_t member_list_entries)
+{
+       g_return_if_fail(dfsm != NULL);
+       g_return_if_fail(dfsm->members != NULL);
+       g_return_if_fail(!member_list_entries || member_list != NULL);
+
+       cfs_debug("enter dfsm_release_sync_resources");
+
+       if (dfsm->sync_info) {
+
+               if (dfsm->sync_info->data && dfsm->dfsm_callbacks->dfsm_cleanup_fn) {
+                       dfsm->dfsm_callbacks->dfsm_cleanup_fn(dfsm, dfsm->data, dfsm->sync_info);
+                       dfsm->sync_info->data = NULL;
+               }
+               
+               for (int i = 0; i < dfsm->sync_info->node_count; i++) {
+                       if (dfsm->sync_info->nodes[i].state) {
+                               g_free(dfsm->sync_info->nodes[i].state);
+                               dfsm->sync_info->nodes[i].state = NULL;
+                               dfsm->sync_info->nodes[i].state_len = 0;
+                       }                       
+               }
+       }
+
+       if (member_list) {
+
+               g_hash_table_remove_all(dfsm->members);
+
+               if (dfsm->sync_info)
+                       g_free(dfsm->sync_info);
+
+               int size = sizeof(dfsm_sync_info_t) + 
+                       member_list_entries*sizeof(dfsm_sync_info_t);
+               dfsm_sync_info_t *sync_info = dfsm->sync_info = g_malloc0(size); 
+               sync_info->node_count = member_list_entries;
+
+               for (int i = 0; i < member_list_entries; i++) {
+                       sync_info->nodes[i].nodeid = member_list[i].nodeid;
+                       sync_info->nodes[i].pid = member_list[i].pid;
+               }
+
+               qsort(sync_info->nodes, member_list_entries, sizeof(dfsm_node_info_t),
+                     dfsm_sync_info_compare);
+
+               for (int i = 0; i < member_list_entries; i++) {
+                       dfsm_node_info_t *info = &sync_info->nodes[i];
+                       g_hash_table_insert(dfsm->members, info, info);
+                       if (info->nodeid == dfsm->nodeid && info->pid == dfsm->pid)
+                               sync_info->local = info;
+               }
+       } 
+}
+
+static void 
+dfsm_cpg_deliver_callback(
+       cpg_handle_t handle,
+       const struct cpg_name *group_name,
+       uint32_t nodeid,
+       uint32_t pid,
+       void *msg,
+       size_t msg_len)
+{
+       cs_error_t result;
+
+       dfsm_t *dfsm = NULL;
+       result = cpg_context_get(handle, (gpointer *)&dfsm);
+       if (result != CS_OK || !dfsm || dfsm->cpg_callbacks != &cpg_callbacks) {
+               cfs_critical("cpg_context_get error: %d (%p)", result, (void *) dfsm);
+               return; /* we have no valid dfsm pointer, so we can just ignore this */
+       }
+       dfsm_mode_t mode = dfsm_get_mode(dfsm);
+
+       cfs_dom_debug(dfsm->log_domain, "dfsm mode is %d", mode);
+
+       if (mode >= DFSM_ERROR_MODE_START) {
+               cfs_dom_debug(dfsm->log_domain, "error mode - ignoring message");
+               goto leave;
+       }
+
+       if (!dfsm->sync_info) {
+               cfs_dom_critical(dfsm->log_domain, "no dfsm_sync_info - internal error");
+               goto leave;
+       }
+
+       if (msg_len < sizeof(dfsm_message_header_t)) {
+               cfs_dom_critical(dfsm->log_domain, "received short message (%zd bytes)", msg_len);
+               goto leave;
+       }
+
+       dfsm_message_header_t *base_header = (dfsm_message_header_t *)msg;
+       
+       if (base_header->protocol_version > dfsm->protocol_version) {
+               cfs_dom_critical(dfsm->log_domain, "received message with protocol version %d", 
+                                base_header->protocol_version);
+               dfsm_set_mode(dfsm, DFSM_MODE_VERSION_ERROR);
+               return;
+       } else if (base_header->protocol_version < dfsm->protocol_version) {
+               cfs_dom_message(dfsm->log_domain, "ignore message with wrong protocol version %d", 
+                               base_header->protocol_version);
+               return;
+       }
+
+       if (base_header->type == DFSM_MESSAGE_NORMAL) {
+
+               dfsm_message_normal_header_t *header = (dfsm_message_normal_header_t *)msg;
+
+               if (msg_len < sizeof(dfsm_message_normal_header_t)) {
+                       cfs_dom_critical(dfsm->log_domain, "received short message (type = %d, subtype = %d, %zd bytes)",
+                                        base_header->type, base_header->subtype, msg_len);
+                       goto leave;
+               }
+
+               if (mode != DFSM_MODE_SYNCED) {
+                       cfs_dom_debug(dfsm->log_domain, "queue message %" PRIu64 " (subtype = %d, length = %zd)",
+                                     header->count, base_header->subtype, msg_len); 
+
+                       if (!dfsm_queue_add_message(dfsm, nodeid, pid, header->count, msg, msg_len))
+                               goto leave;
+               } else {
+
+                       int msg_res = -1;
+                       int res = dfsm->dfsm_callbacks->dfsm_deliver_fn(
+                               dfsm, dfsm->data, &msg_res, nodeid, pid, base_header->subtype, 
+                               base_header->time, (uint8_t *)msg + sizeof(dfsm_message_normal_header_t),
+                               msg_len - sizeof(dfsm_message_normal_header_t));
+
+                       if (nodeid == dfsm->nodeid && pid == dfsm->pid)
+                               dfsm_record_local_result(dfsm, header->count, msg_res, res);
+
+                       if (res < 0)
+                               goto leave;
+               }
+
+               return;
+       } 
+
+       /* state related messages
+        * we needs right epoch - else we simply discard the message 
+        */
+
+       dfsm_message_state_header_t *header = (dfsm_message_state_header_t *)msg;
+
+       if (msg_len < sizeof(dfsm_message_state_header_t)) {
+               cfs_dom_critical(dfsm->log_domain, "received short state message (type = %d, subtype = %d, %zd bytes)",
+                                base_header->type, base_header->subtype, msg_len);
+               goto leave;
+       }
+
+       if (base_header->type != DFSM_MESSAGE_SYNC_START && 
+           (memcmp(&header->epoch, &dfsm->sync_epoch, sizeof(dfsm_sync_epoch_t)) != 0)) {
+               cfs_dom_debug(dfsm->log_domain, "ignore message (msg_type == %d) with "
+                             "wrong epoch (epoch %d/%d/%08X)", base_header->type, 
+                             header->epoch.nodeid, header->epoch.pid, header->epoch.epoch);
+               return;
+       }
+
+       msg = (uint8_t *) msg + sizeof(dfsm_message_state_header_t);
+       msg_len -= sizeof(dfsm_message_state_header_t);
+
+       if (mode == DFSM_MODE_SYNCED) {
+               if (base_header->type == DFSM_MESSAGE_UPDATE_COMPLETE) {
+
+                       for (int i = 0; i < dfsm->sync_info->node_count; i++)
+                               dfsm->sync_info->nodes[i].synced = 1;
+
+                       if (!dfsm_deliver_queue(dfsm))
+                               goto leave;
+
+                       return;
+
+               } else if (base_header->type == DFSM_MESSAGE_VERIFY_REQUEST) {
+
+                       if (msg_len != sizeof(dfsm->csum_counter)) {
+                               cfs_dom_critical(dfsm->log_domain, "cpg received verify request with wrong length (%zd bytes) form node %d/%d", msg_len, nodeid, pid);
+                               goto leave;
+                       }
+
+                       uint64_t csum_id = *((uint64_t *)msg);
+                       msg = (uint8_t *) msg + 8; msg_len -= 8;
+
+                       cfs_dom_debug(dfsm->log_domain, "got verify request from node %d %016" PRIX64, nodeid, csum_id);
+
+                       if (dfsm->dfsm_callbacks->dfsm_checksum_fn) {
+                               if (!dfsm->dfsm_callbacks->dfsm_checksum_fn(
+                                           dfsm, dfsm->data, dfsm->csum, sizeof(dfsm->csum))) {
+                                       cfs_dom_critical(dfsm->log_domain, "unable to compute data checksum");
+                                       goto leave;
+                               }
+
+                               dfsm->csum_epoch = header->epoch;
+                               dfsm->csum_id = csum_id;
+
+                               if (nodeid == dfsm->nodeid && pid == dfsm->pid) {
+                                       if (!dfsm_send_checksum(dfsm)) 
+                                               goto leave;
+                               }
+                       }
+
+                       return;
+
+               } else if (base_header->type == DFSM_MESSAGE_VERIFY) {
+               
+                       cfs_dom_debug(dfsm->log_domain, "received verify message");
+                       
+                       if (dfsm->dfsm_callbacks->dfsm_checksum_fn) {
+
+                               if (msg_len != (sizeof(dfsm->csum_id) + sizeof(dfsm->csum))) {
+                                       cfs_dom_critical(dfsm->log_domain, "cpg received verify message with wrong length (%zd bytes)", msg_len);
+                                       goto leave;
+                               }
+
+                               uint64_t csum_id = *((uint64_t *)msg);
+                               msg = (uint8_t *) msg + 8; msg_len -= 8;
+                               
+                               if (dfsm->csum_id == csum_id &&
+                                   (memcmp(&dfsm->csum_epoch, &header->epoch, sizeof(dfsm_sync_epoch_t)) == 0)) {
+                                       if (memcmp(msg, dfsm->csum, sizeof(dfsm->csum)) != 0) {
+                                               cfs_dom_critical(dfsm->log_domain, "wrong checksum %016" PRIX64 " != %016" PRIX64 " - restarting",
+                                                                *(uint64_t *)msg, *(uint64_t *)dfsm->csum);
+                                               goto leave;
+                                       } else {
+                                               cfs_dom_message(dfsm->log_domain, "data verification successful");
+                                       }
+                               } else {
+                                       cfs_dom_message(dfsm->log_domain, "skip verification - no checksum saved");
+                               }
+                       }
+
+                       return;
+
+               } else {
+                       /* ignore (we already got all required updates, or we are leader) */
+                       cfs_dom_debug(dfsm->log_domain, "ignore state sync message %d", 
+                                     base_header->type);
+                       return;
+               }
+               
+       } else if (mode == DFSM_MODE_START_SYNC) {
+
+               if (base_header->type == DFSM_MESSAGE_SYNC_START) {
+
+                       if (nodeid != dfsm->lowest_nodeid) {
+                               cfs_dom_critical(dfsm->log_domain, "ignore sync request from wrong member %d/%d",
+                                                nodeid, pid);
+                       }
+
+                       cfs_dom_message(dfsm->log_domain, "received sync request (epoch %d/%d/%08X)",
+                                       header->epoch.nodeid, header->epoch.pid, header->epoch.epoch);
+
+                       dfsm->sync_epoch = header->epoch;
+
+                       dfsm_release_sync_resources(dfsm, NULL, 0);
+
+                       unsigned int state_len = 0;
+                       gpointer state = NULL;
+                       
+                       state = dfsm->dfsm_callbacks->dfsm_get_state_fn(dfsm, dfsm->data, &state_len);
+
+                       if (!(state && state_len)) {
+                               cfs_dom_critical(dfsm->log_domain, "dfsm_get_state_fn failed");
+                               goto leave;
+                       }
+
+                       struct iovec iov[1];
+                       iov[0].iov_base = state;
+                       iov[0].iov_len = state_len;
+
+                       result = dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_STATE, iov, 1);
+
+                       if (state)
+                               g_free(state);
+
+                       if (result != CS_OK)
+                               goto leave;
+
+                       return;
+
+               } else if (base_header->type == DFSM_MESSAGE_STATE) {
+
+                       dfsm_node_info_t *ni;
+                       
+                       if (!(ni = dfsm_node_info_lookup(dfsm, nodeid, pid))) {
+                               cfs_dom_critical(dfsm->log_domain, "received state for non-member %d/%d", nodeid, pid);
+                               goto leave;
+                       }
+
+                       if (ni->state) {
+                               cfs_dom_critical(dfsm->log_domain, "received duplicate state for member %d/%d", nodeid, pid);
+                               goto leave;
+                       }
+
+                       ni->state = g_memdup2(msg, msg_len);
+                       ni->state_len = msg_len;
+
+                       int received_all = 1;
+                       for (int i = 0; i < dfsm->sync_info->node_count; i++) {
+                               if (!dfsm->sync_info->nodes[i].state) {
+                                       received_all = 0;
+                                       break;
+                               }
+                       }
+
+                       if (received_all) {
+                               cfs_dom_message(dfsm->log_domain, "received all states");
+
+                               int res = dfsm->dfsm_callbacks->dfsm_process_state_update_fn(dfsm, dfsm->data, dfsm->sync_info);
+                               if (res < 0)
+                                       goto leave;
+
+                               if (dfsm->sync_info->local->synced)  {
+                                       dfsm_set_mode(dfsm, DFSM_MODE_SYNCED);
+                                       dfsm_release_sync_resources(dfsm, NULL, 0);
+
+                                       if (!dfsm_deliver_queue(dfsm))
+                                               goto leave;
+                                       
+                               } else {
+                                       dfsm_set_mode(dfsm, DFSM_MODE_UPDATE);
+
+                                       if (!dfsm_deliver_queue(dfsm))
+                                               goto leave;
+                               }
+
+                       }
+
+                       return;
+               }
+
+       } else if (mode == DFSM_MODE_UPDATE) {
+
+               if (base_header->type == DFSM_MESSAGE_UPDATE) {
+                               
+                       int res = dfsm->dfsm_callbacks->dfsm_process_update_fn(
+                               dfsm, dfsm->data, dfsm->sync_info, nodeid, pid, msg, msg_len);
+
+                       if (res < 0)
+                               goto leave;
+
+                       return;
+
+               } else if (base_header->type == DFSM_MESSAGE_UPDATE_COMPLETE) {
+
+
+                       int res = dfsm->dfsm_callbacks->dfsm_commit_fn(dfsm, dfsm->data, dfsm->sync_info);
+
+                       if (res < 0)
+                               goto leave;
+
+                       for (int i = 0; i < dfsm->sync_info->node_count; i++)
+                               dfsm->sync_info->nodes[i].synced = 1;
+
+                       dfsm_set_mode(dfsm, DFSM_MODE_SYNCED);
+
+                       if (!dfsm_deliver_sync_queue(dfsm))
+                               goto leave;
+
+                       if (!dfsm_deliver_queue(dfsm))
+                               goto leave;
+
+                       dfsm_release_sync_resources(dfsm, NULL, 0);
+
+                       return;
+               }
+
+       } else {
+               cfs_dom_critical(dfsm->log_domain, "internal error - unknown mode %d", mode);
+               goto leave;
+       }
+
+       if (base_header->type == DFSM_MESSAGE_VERIFY_REQUEST ||
+           base_header->type == DFSM_MESSAGE_VERIFY) {
+
+               cfs_dom_debug(dfsm->log_domain, "ignore verify message %d while not synced", base_header->type);
+    
+       } else {
+               cfs_dom_critical(dfsm->log_domain, "received unknown state message type (type = %d, %zd bytes)",
+                                base_header->type, msg_len);
+               goto leave;
+       }
+
+leave:
+       dfsm_set_mode(dfsm, DFSM_MODE_LEAVE);
+       dfsm_release_sync_resources(dfsm, NULL, 0);
+       return;
+}
+
+static gboolean 
+dfsm_resend_queue(dfsm_t *dfsm)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+       g_return_val_if_fail(dfsm->msg_queue != NULL, FALSE);
+
+       GSequenceIter *iter = g_sequence_get_begin_iter(dfsm->msg_queue);
+       GSequenceIter *end = g_sequence_get_end_iter(dfsm->msg_queue);
+       gboolean res = TRUE;
+
+       while (iter != end) {
+               GSequenceIter *cur = iter; 
+               iter = g_sequence_iter_next(iter);
+
+               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)
+                       g_sequence_get(cur);
+
+               if (qm->nodeid == dfsm->nodeid && qm->pid == dfsm->pid) {
+                       cs_error_t result;
+                       struct iovec iov[1];
+                       iov[0].iov_base = qm->msg;
+                       iov[0].iov_len = qm->msg_len;
+
+                       if ((result = dfsm_send_message_full(dfsm, iov, 1, 1)) != CS_OK) {
+                               res = FALSE;                    
+                               break;
+                       }
+               }
+       }
+
+       dfsm_free_message_queue(dfsm);
+
+       return res;
+}
+
+static gboolean
+dfsm_deliver_sync_queue(dfsm_t *dfsm) 
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+
+       if (!dfsm->sync_queue)
+               return TRUE;
+
+       gboolean res = TRUE;
+
+       // fixme: cfs_debug
+       cfs_dom_message(dfsm->log_domain, "%s: queue length %d", __func__, 
+                       g_list_length(dfsm->sync_queue));
+
+       GList *iter = dfsm->sync_queue;
+       while (iter) {
+               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)iter->data;
+               iter = g_list_next(iter);
+
+               if (res && dfsm->mode == DFSM_MODE_SYNCED) {            
+                       dfsm_cpg_deliver_callback(dfsm->cpg_handle, &dfsm->cpg_group_name,
+                                                 qm->nodeid, qm->pid, qm->msg, qm->msg_len);
+               } else {
+                       res = FALSE;
+               }
+
+               dfsm_free_queue_entry(qm);
+       }
+       g_list_free(dfsm->sync_queue);
+       dfsm->sync_queue = NULL;
+
+       return res;
+}
+
+static gboolean 
+dfsm_deliver_queue(dfsm_t *dfsm)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+       g_return_val_if_fail(dfsm->msg_queue != NULL, FALSE);
+
+       int qlen = g_sequence_get_length(dfsm->msg_queue);
+       if (!qlen)
+               return TRUE;
+
+       GSequenceIter *iter = g_sequence_get_begin_iter(dfsm->msg_queue);
+       GSequenceIter *end = g_sequence_get_end_iter(dfsm->msg_queue);
+       gboolean res = TRUE;
+
+       // fixme: cfs_debug
+       cfs_dom_message(dfsm->log_domain, "%s: queue length %d", __func__, qlen);
+
+       while (iter != end) {
+               GSequenceIter *cur = iter; 
+               iter = g_sequence_iter_next(iter);
+
+               dfsm_queued_message_t *qm = (dfsm_queued_message_t *)
+                       g_sequence_get(cur);
+       
+               dfsm_node_info_t *ni = dfsm_node_info_lookup(dfsm, qm->nodeid, qm->pid);
+               if (!ni) {
+                       cfs_dom_message(dfsm->log_domain, "remove message from non-member %d/%d", 
+                                       qm->nodeid, qm->pid);
+                       dfsm_free_queue_entry(qm);
+                       g_sequence_remove(cur);
+                       continue;
+               }
+
+               if (dfsm->mode == DFSM_MODE_SYNCED) {
+                       if (ni->synced) {
+                               dfsm_cpg_deliver_callback(dfsm->cpg_handle, &dfsm->cpg_group_name,
+                                                         qm->nodeid, qm->pid, qm->msg, qm->msg_len);
+                               dfsm_free_queue_entry(qm);
+                               g_sequence_remove(cur);
+                       }
+               } else if (dfsm->mode == DFSM_MODE_UPDATE) {
+                       if (ni->synced) {
+                               dfsm->sync_queue = g_list_append(dfsm->sync_queue, qm);
+                               g_sequence_remove(cur);
+                       }
+               } else {
+                       res = FALSE;
+                       break;
+               }
+       }
+
+       return res;
+}
+
+static void 
+dfsm_cpg_confchg_callback(
+       cpg_handle_t handle,
+       const struct cpg_name *group_name,
+       const struct cpg_address *member_list, 
+       size_t member_list_entries,
+       const struct cpg_address *left_list, 
+       size_t left_list_entries,
+       const struct cpg_address *joined_list, 
+       size_t joined_list_entries)
+{
+       cs_error_t result;
+
+       dfsm_t *dfsm = NULL;
+       result = cpg_context_get(handle, (gpointer *)&dfsm);
+       if (result != CS_OK || !dfsm || dfsm->cpg_callbacks != &cpg_callbacks) {
+               cfs_critical("cpg_context_get error: %d (%p)", result, (void *) dfsm);
+               return; /* we have no valid dfsm pointer, so we can just ignore this */
+       }
+
+       dfsm->we_are_member = 0;
+
+       /* create new epoch */
+       dfsm->local_epoch_counter++;
+       dfsm->sync_epoch.epoch = dfsm->local_epoch_counter;
+       dfsm->sync_epoch.nodeid = dfsm->nodeid;
+       dfsm->sync_epoch.pid = dfsm->pid;
+       dfsm->sync_epoch.time = time(NULL);
+
+       /* invalidate saved checksum */
+       dfsm->csum_id = dfsm->csum_counter;
+       memset(&dfsm->csum_epoch, 0, sizeof(dfsm->csum_epoch));
+
+       dfsm_free_sync_queue(dfsm);
+
+       dfsm_mode_t mode = dfsm_get_mode(dfsm);
+
+       cfs_dom_debug(dfsm->log_domain, "dfsm mode is %d", mode);
+
+       if (mode >= DFSM_ERROR_MODE_START) {
+               cfs_dom_debug(dfsm->log_domain, "already left group - ignore message");
+               return;
+       }
+
+       int lowest_nodeid = 0;
+       GString *member_ids = g_string_new(NULL);
+       for (int i = 0; i < member_list_entries; i++) {
+
+               g_string_append_printf(member_ids, i ? ", %d/%d" : "%d/%d",
+                                      member_list[i].nodeid, member_list[i].pid);
+
+               if (lowest_nodeid == 0 || lowest_nodeid > member_list[i].nodeid)
+                       lowest_nodeid =  member_list[i].nodeid;
+
+               if (member_list[i].nodeid == dfsm->nodeid &&
+                   member_list[i].pid == dfsm->pid)
+                       dfsm->we_are_member = 1;
+       }
+
+
+       if ((dfsm->we_are_member || mode != DFSM_MODE_START))
+               cfs_dom_message(dfsm->log_domain, "members: %s",  member_ids->str);
+
+       g_string_free(member_ids, 1);
+
+       dfsm->lowest_nodeid = lowest_nodeid;
+
+       /* NOTE: one node can be in left and joined list at the same time,
+          so it is better to query member list. Also JOIN/LEAVE list are
+          different on different nodes!
+       */
+
+       dfsm_release_sync_resources(dfsm, member_list, member_list_entries);
+
+       if (!dfsm->we_are_member) {
+               if (mode == DFSM_MODE_START) {
+                       cfs_dom_debug(dfsm->log_domain, "ignore leave message");
+                       return;
+               }
+               cfs_dom_message(dfsm->log_domain, "we (%d/%d) left the process group", 
+                               dfsm->nodeid, dfsm->pid);
+               goto leave;
+       }
+
+       if (member_list_entries > 1) {
+
+               int qlen = g_sequence_get_length(dfsm->msg_queue);
+               if (joined_list_entries && qlen) {
+                       /* we need to make sure that all members have the same queue. */
+                       cfs_dom_message(dfsm->log_domain, "queue not emtpy - resening %d messages", qlen);
+                       if (!dfsm_resend_queue(dfsm)) {
+                               cfs_dom_critical(dfsm->log_domain, "dfsm_resend_queue failed");
+                               goto leave;
+                       }
+               }
+
+               dfsm_set_mode(dfsm, DFSM_MODE_START_SYNC);
+               if (lowest_nodeid == dfsm->nodeid) {
+                       if (dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_SYNC_START, NULL, 0) != CS_OK) {
+                               cfs_dom_critical(dfsm->log_domain, "failed to send SYNC_START message");
+                               goto leave;
+                       }
+               }
+       } else {
+               dfsm_set_mode(dfsm, DFSM_MODE_SYNCED);
+               dfsm->sync_info->local->synced = 1;
+               if (!dfsm_deliver_queue(dfsm))
+                       goto leave;
+       }
+
+       if (dfsm->dfsm_callbacks->dfsm_confchg_fn) 
+               dfsm->dfsm_callbacks->dfsm_confchg_fn(dfsm, dfsm->data, member_list, member_list_entries);
+
+       return;
+leave:
+       dfsm_set_mode(dfsm, DFSM_MODE_LEAVE);
+       return;
+}
+
+static cpg_callbacks_t cpg_callbacks = {
+       .cpg_deliver_fn = dfsm_cpg_deliver_callback,
+       .cpg_confchg_fn = dfsm_cpg_confchg_callback,
+};
+
+dfsm_t *
+dfsm_new(
+       gpointer data, 
+       const char *group_name, 
+       const char *log_domain,
+       guint32 protocol_version, 
+       dfsm_callbacks_t *callbacks)
+{
+       g_return_val_if_fail(sizeof(dfsm_message_header_t) == 16, NULL);
+       g_return_val_if_fail(sizeof(dfsm_message_state_header_t) == 32, NULL);
+       g_return_val_if_fail(sizeof(dfsm_message_normal_header_t) == 24, NULL); 
+
+       g_return_val_if_fail(callbacks != NULL, NULL);
+       g_return_val_if_fail(callbacks->dfsm_deliver_fn != NULL, NULL);
+
+       g_return_val_if_fail(callbacks->dfsm_get_state_fn != NULL, NULL);
+       g_return_val_if_fail(callbacks->dfsm_process_state_update_fn != NULL, NULL);
+       g_return_val_if_fail(callbacks->dfsm_process_update_fn != NULL, NULL);
+       g_return_val_if_fail(callbacks->dfsm_commit_fn != NULL, NULL);
+  
+       dfsm_t *dfsm;
+
+       if ((dfsm = g_new0(dfsm_t, 1)) == NULL)
+               return NULL;
+
+       g_mutex_init(&dfsm->sync_mutex);
+       
+       g_cond_init(&dfsm->sync_cond);
+
+       if (!(dfsm->results = g_hash_table_new(g_int64_hash, g_int64_equal)))
+               goto err;
+
+       if (!(dfsm->msg_queue = g_sequence_new(NULL))) 
+               goto err;
+
+       g_mutex_init(&dfsm->cpg_mutex);
+
+       dfsm->log_domain = log_domain;
+       dfsm->data = data;
+       dfsm->mode = DFSM_MODE_START;
+       dfsm->protocol_version = protocol_version;
+       strcpy (dfsm->cpg_group_name.value, group_name);
+       dfsm->cpg_group_name.length = strlen (group_name) + 1;
+
+       dfsm->cpg_callbacks = &cpg_callbacks;
+       dfsm->dfsm_callbacks = callbacks;
+
+       dfsm->members = g_hash_table_new(dfsm_sync_info_hash, dfsm_sync_info_equal);
+       if (!dfsm->members)
+               goto err;
+
+       g_mutex_init(&dfsm->mode_mutex);
+
+       return dfsm;
+
+err:
+       dfsm_destroy(dfsm);
+       return NULL;
+}
+
+gboolean
+dfsm_is_initialized(dfsm_t *dfsm)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+
+       return (dfsm->cpg_handle != 0) ? TRUE : FALSE;
+}
+
+gboolean 
+dfsm_lowest_nodeid(dfsm_t *dfsm)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+
+       if (dfsm->lowest_nodeid && (dfsm->lowest_nodeid == dfsm->nodeid))
+               return TRUE;
+
+       return FALSE;
+}
+
+cs_error_t 
+dfsm_verify_request(dfsm_t *dfsm)
+{
+       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
+
+       /* only do when we have lowest nodeid */
+       if (!dfsm->lowest_nodeid || (dfsm->lowest_nodeid != dfsm->nodeid))
+               return CS_OK;
+
+       dfsm_mode_t mode = dfsm_get_mode(dfsm);
+       if (mode != DFSM_MODE_SYNCED)
+               return CS_OK;           
+
+       int len = 1;
+       struct iovec iov[len];
+
+       if (dfsm->csum_counter != dfsm->csum_id) {
+               g_message("delay verify request %016" PRIX64, dfsm->csum_counter + 1);
+               return CS_OK;
+       };
+
+       dfsm->csum_counter++;
+       iov[0].iov_base = (char *)&dfsm->csum_counter;
+       iov[0].iov_len = sizeof(dfsm->csum_counter);
+       
+       cfs_debug("send verify request %016" PRIX64, dfsm->csum_counter);
+
+       cs_error_t result;
+       result = dfsm_send_state_message_full(dfsm, DFSM_MESSAGE_VERIFY_REQUEST, iov, len);
+
+       if (result != CS_OK)
+               cfs_dom_critical(dfsm->log_domain, "failed to send VERIFY_REQUEST message");
+
+       return result;
+}
+
+
+cs_error_t
+dfsm_dispatch(
+       dfsm_t *dfsm, 
+       cs_dispatch_flags_t dispatch_types) 
+{
+       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
+       g_return_val_if_fail(dfsm->cpg_handle != 0, CS_ERR_INVALID_PARAM);
+
+       cs_error_t result;
+
+       struct timespec tvreq = { .tv_sec = 0, .tv_nsec = 100000000 };
+       int retries = 0;
+loop:
+       result = cpg_dispatch(dfsm->cpg_handle, dispatch_types);
+       if (result == CS_ERR_TRY_AGAIN) {
+               nanosleep(&tvreq, NULL);
+               ++retries;
+               if ((retries % 10) == 0)
+                       cfs_dom_message(dfsm->log_domain, "cpg_dispatch retry %d", retries);
+               goto loop;
+       }
+
+       if (!(result == CS_OK || result == CS_ERR_TRY_AGAIN)) {
+               cfs_dom_critical(dfsm->log_domain, "cpg_dispatch failed: %d", result);
+       }
+
+       return result;
+}
+
+
+cs_error_t
+dfsm_initialize(dfsm_t *dfsm, int *fd) 
+{
+       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
+       g_return_val_if_fail(fd != NULL, CS_ERR_INVALID_PARAM);
+
+       /* remove old messages */
+       dfsm_free_message_queue(dfsm);
+       dfsm_send_sync_message_abort(dfsm);
+
+       dfsm->joined = FALSE;
+       dfsm->we_are_member = 0;
+
+       dfsm_set_mode(dfsm, DFSM_MODE_START);
+
+       cs_error_t result;
+
+       if (dfsm->cpg_handle == 0) {
+               if ((result = cpg_initialize(&dfsm->cpg_handle, dfsm->cpg_callbacks)) != CS_OK) {
+                       cfs_dom_critical(dfsm->log_domain, "cpg_initialize failed: %d", result);
+                       goto err_no_finalize;
+               }
+
+               if ((result = cpg_local_get(dfsm->cpg_handle, &dfsm->nodeid)) != CS_OK) {
+                       cfs_dom_critical(dfsm->log_domain, "cpg_local_get failed: %d", result);
+                       goto err_finalize;
+               }
+
+               dfsm->pid = getpid();
+
+               result = cpg_context_set(dfsm->cpg_handle, dfsm);
+               if (result != CS_OK) {
+                       cfs_dom_critical(dfsm->log_domain, "cpg_context_set failed: %d", result);
+                       goto err_finalize;
+               }
+       }
+
+       result = cpg_fd_get(dfsm->cpg_handle, fd);
+       if (result != CS_OK) {
+               cfs_dom_critical(dfsm->log_domain, "cpg_fd_get failed: %d", result);
+               goto err_finalize;
+       }
+       
+       return CS_OK;
+
+ err_finalize:
+       cpg_finalize(dfsm->cpg_handle);
+ err_no_finalize:
+       dfsm->cpg_handle = 0;
+       return result;
+}
+
+cs_error_t
+dfsm_join(dfsm_t *dfsm) 
+{
+       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
+       g_return_val_if_fail(dfsm->cpg_handle != 0, CS_ERR_LIBRARY);
+       g_return_val_if_fail(dfsm->joined == 0, CS_ERR_EXIST);
+
+       cs_error_t result;
+
+       struct timespec tvreq = { .tv_sec = 0, .tv_nsec = 100000000 };
+       int retries = 0;
+loop:
+       g_mutex_lock (&dfsm->cpg_mutex);
+       result = cpg_join(dfsm->cpg_handle, &dfsm->cpg_group_name); 
+       g_mutex_unlock (&dfsm->cpg_mutex);
+       if (result == CS_ERR_TRY_AGAIN) {
+               nanosleep(&tvreq, NULL);
+               ++retries;
+               if ((retries % 10) == 0)
+                       cfs_dom_message(dfsm->log_domain, "cpg_join retry %d", retries);
+               goto loop;
+       }
+
+       if (result != CS_OK) {
+               cfs_dom_critical(dfsm->log_domain, "cpg_join failed: %d", result);
+               return result;
+       }
+
+       dfsm->joined = TRUE;
+       return TRUE;
+}
+
+cs_error_t
+dfsm_leave (dfsm_t *dfsm)
+{
+       g_return_val_if_fail(dfsm != NULL, CS_ERR_INVALID_PARAM);
+       g_return_val_if_fail(dfsm->joined, CS_ERR_NOT_EXIST);
+
+       cs_error_t result;
+
+       struct timespec tvreq = { .tv_sec = 0, .tv_nsec = 100000000 };
+       int retries = 0;
+loop:
+       g_mutex_lock (&dfsm->cpg_mutex);
+       result = cpg_leave(dfsm->cpg_handle, &dfsm->cpg_group_name);
+       g_mutex_unlock (&dfsm->cpg_mutex);
+       if (result == CS_ERR_TRY_AGAIN) {
+               nanosleep(&tvreq, NULL);
+               ++retries;
+               if ((retries % 10) == 0)
+                       cfs_dom_message(dfsm->log_domain, "cpg_leave retry %d", retries);
+               goto loop;
+       }
+
+       if (result != CS_OK) {
+               cfs_dom_critical(dfsm->log_domain, "cpg_leave failed: %d", result);
+               return result;
+       }
+
+       dfsm->joined = FALSE;
+
+       return TRUE;            
+}
+
+gboolean 
+dfsm_finalize(dfsm_t *dfsm)
+{
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+
+       dfsm_send_sync_message_abort(dfsm);
+
+       if (dfsm->joined)
+               dfsm_leave(dfsm);
+
+       if (dfsm->cpg_handle) {
+               cpg_finalize(dfsm->cpg_handle);
+               dfsm->cpg_handle = 0;
+               dfsm->joined = FALSE;
+               dfsm->we_are_member = 0;
+       }
+
+       return TRUE;
+}
+
+void 
+dfsm_destroy(dfsm_t *dfsm)
+{
+       g_return_if_fail(dfsm != NULL);
+
+       dfsm_finalize(dfsm);
+
+       if (dfsm->sync_info && dfsm->sync_info->data && dfsm->dfsm_callbacks->dfsm_cleanup_fn)
+               dfsm->dfsm_callbacks->dfsm_cleanup_fn(dfsm, dfsm->data, dfsm->sync_info);
+
+       dfsm_free_sync_queue(dfsm);
+
+       g_mutex_clear (&dfsm->mode_mutex);
+
+       g_mutex_clear (&dfsm->sync_mutex);
+
+       g_cond_clear (&dfsm->sync_cond);
+
+       g_mutex_clear (&dfsm->cpg_mutex);
+       if (dfsm->results)
+               g_hash_table_destroy(dfsm->results);
+
+       if (dfsm->msg_queue) {
+               dfsm_free_message_queue(dfsm);
+               g_sequence_free(dfsm->msg_queue);
+       }
+
+       if (dfsm->sync_info)
+               g_free(dfsm->sync_info);
+
+       if (dfsm->cpg_handle)
+               cpg_finalize(dfsm->cpg_handle);
+
+       if (dfsm->members)
+               g_hash_table_destroy(dfsm->members);
+
+       g_free(dfsm);
+}
+
+typedef struct {
+       dfsm_t *dfsm;
+} service_dfsm_private_t;
+
+static gboolean 
+service_dfsm_finalize(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_val_if_fail(service != NULL, FALSE);
+       g_return_val_if_fail(context != NULL, FALSE);
+
+       service_dfsm_private_t *private = (service_dfsm_private_t *)context;
+       dfsm_t *dfsm = private->dfsm;
+
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+
+       return dfsm_finalize(dfsm);
+}
+
+static int 
+service_dfsm_initialize(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_val_if_fail(service != NULL, -1);
+       g_return_val_if_fail(context != NULL, -1);
+
+       service_dfsm_private_t *private = (service_dfsm_private_t *)context;
+       dfsm_t *dfsm = private->dfsm;
+
+       g_return_val_if_fail(dfsm != NULL, -1);
+
+       /* serious internal error - don't try to recover */
+       if (!dfsm_restartable(dfsm))
+               return -1;
+       
+       int fd = -1;
+
+       cs_error_t result;
+       if ((result = dfsm_initialize(dfsm, &fd)) != CS_OK)
+               return -1;
+       
+       result = dfsm_join(dfsm);
+       if (result != CS_OK) {
+               /* we can't dispatch if not joined, so we need to finalize */
+               dfsm_finalize(dfsm);
+               return -1;
+       }
+
+       return fd;
+}
+
+static gboolean 
+service_dfsm_dispatch(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_val_if_fail(service != NULL, FALSE);
+       g_return_val_if_fail(context != NULL, FALSE);
+
+       service_dfsm_private_t *private = (service_dfsm_private_t *)context;
+       dfsm_t *dfsm = private->dfsm;
+
+       g_return_val_if_fail(dfsm != NULL, FALSE);
+       g_return_val_if_fail(dfsm->cpg_handle != 0, FALSE);
+
+       cs_error_t result;
+
+       result = dfsm_dispatch(dfsm, CS_DISPATCH_ONE);
+       if (result == CS_ERR_LIBRARY || result == CS_ERR_BAD_HANDLE)
+               goto finalize;
+       if (result != CS_OK)
+               goto fail;
+
+       dfsm_mode_t mode = dfsm_get_mode(dfsm);
+       if (mode >= DFSM_ERROR_MODE_START) {
+               if (dfsm->joined) {
+                       result = dfsm_leave(dfsm);
+                       if (result == CS_ERR_LIBRARY || result == CS_ERR_BAD_HANDLE)
+                               goto finalize;
+                       if (result != CS_OK)
+                               goto finalize;
+               } else {
+                       if (!dfsm->we_are_member)
+                               return FALSE;
+               }
+       }
+
+       return TRUE;
+
+finalize:
+       dfsm_finalize(dfsm);
+fail:
+       cfs_service_set_restartable(service, dfsm_restartable(dfsm));
+       return FALSE;
+}
+
+static void 
+service_dfsm_timer(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_if_fail(service != NULL);
+       g_return_if_fail(context != NULL);
+
+       service_dfsm_private_t *private = (service_dfsm_private_t *)context;
+       dfsm_t *dfsm = private->dfsm;
+
+       g_return_if_fail(dfsm != NULL);
+
+       dfsm_verify_request(dfsm);
+}
+
+static cfs_service_callbacks_t cfs_dfsm_callbacks = {
+       .cfs_service_initialize_fn =  service_dfsm_initialize,
+       .cfs_service_finalize_fn = service_dfsm_finalize,
+       .cfs_service_dispatch_fn = service_dfsm_dispatch,
+       .cfs_service_timer_fn = service_dfsm_timer,
+};
+
+cfs_service_t *
+service_dfsm_new(dfsm_t *dfsm)
+{
+       cfs_service_t *service;
+
+       g_return_val_if_fail(dfsm != NULL, NULL);
+
+       service_dfsm_private_t *private = g_new0(service_dfsm_private_t, 1);
+       if (!private)
+               return NULL;
+
+       private->dfsm = dfsm;
+
+       service = cfs_service_new(&cfs_dfsm_callbacks, dfsm->log_domain, private); 
+
+       return service;
+}
+
+void 
+service_dfsm_destroy(cfs_service_t *service) 
+{
+       g_return_if_fail(service != NULL);
+
+       service_dfsm_private_t *private = 
+               (service_dfsm_private_t *)cfs_service_get_context(service);
+
+       g_free(private);
+       g_free(service);
+}
+
+
+
+
diff --git a/src/pmxcfs/dfsm.h b/src/pmxcfs/dfsm.h
new file mode 100644 (file)
index 0000000..858127f
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_DFSM_H_
+#define _PVE_DFSM_H_
+
+#include <corosync/corotypes.h>
+#include <corosync/cpg.h>
+
+#include "loop.h"
+
+typedef struct dfsm dfsm_t;
+
+typedef struct {
+       uint32_t nodeid;
+       uint32_t pid;
+       gpointer state; 
+       unsigned int state_len;
+       gboolean synced;
+} dfsm_node_info_t;
+
+typedef struct {
+       int node_count;
+       gpointer data; /* app can use that */
+       dfsm_node_info_t *local;
+       dfsm_node_info_t nodes[];
+} dfsm_sync_info_t;
+
+/* return values:
+ * res <= 0    ... serious error, leave group
+ * res > 0    ... OK, continue
+ */
+typedef int (*dfsm_deliver_fn_t)(
+       dfsm_t *dfsm,
+       gpointer data,
+       int *res_ptr,
+       uint32_t nodeid,
+       uint32_t pid,
+       uint16_t msgtype,
+       uint32_t msg_time,
+       const void *msg,
+       size_t msg_len);
+
+typedef void (*dfsm_confchg_fn_t)(
+       dfsm_t *dfsm,
+       gpointer data,
+       const struct cpg_address *member_list, 
+       size_t member_list_entries);
+
+/* return values:
+ * res == NULL ... serious error, leave group
+ * res != NULL ... OK, continue
+ */
+typedef gpointer (*dfsm_get_state_fn_t)(
+       dfsm_t *dfsm,
+       gpointer data,
+       unsigned int *res_len);
+
+typedef gboolean (*dfsm_checksum_fn_t)(
+       dfsm_t *dfsm,
+       gpointer data,
+       unsigned char *csum,
+       size_t csum_len);
+
+typedef int (*dfsm_process_state_update_fn_t)(
+       dfsm_t *dfsm, 
+       gpointer data,
+       dfsm_sync_info_t *syncinfo);
+
+typedef void (*dfsm_synced_fn_t)(dfsm_t *dfsm);
+
+/* return values:
+ * res < 0    ... serious error, leave group
+ * res >=  0  ... OK
+ */
+typedef int (*dfsm_process_update_fn_t)(
+       dfsm_t *dfsm, 
+       gpointer data,
+       dfsm_sync_info_t *syncinfo,
+       uint32_t nodeid,
+       uint32_t pid,
+       const void *msg,
+       size_t msg_len);
+
+typedef struct {
+       dfsm_deliver_fn_t dfsm_deliver_fn;
+       dfsm_confchg_fn_t dfsm_confchg_fn;
+       dfsm_get_state_fn_t dfsm_get_state_fn;
+       dfsm_process_state_update_fn_t dfsm_process_state_update_fn;
+       dfsm_process_state_update_fn_t dfsm_commit_fn;
+       dfsm_process_state_update_fn_t dfsm_cleanup_fn;
+       dfsm_process_update_fn_t dfsm_process_update_fn;
+       dfsm_checksum_fn_t dfsm_checksum_fn;
+       dfsm_synced_fn_t dfsm_synced_fn;
+} dfsm_callbacks_t;
+
+typedef struct {
+       uint64_t msgcount;
+       int result; /* we only have integer results for now */
+       int processed; 
+} dfsm_result_t;
+
+dfsm_t *
+dfsm_new(
+       gpointer data,
+       const char *group_name, 
+       const char *log_domain, 
+       guint32 protocol_version, 
+       dfsm_callbacks_t *callbacks);
+
+void 
+dfsm_destroy(dfsm_t *dfsm);
+
+cs_error_t
+dfsm_initialize(dfsm_t *dfsm, int *fd);
+
+gboolean 
+dfsm_finalize(dfsm_t *dfsm);
+
+cs_error_t
+dfsm_join(dfsm_t *dfsm);
+
+cs_error_t
+dfsm_leave(dfsm_t *dfsm);
+
+cs_error_t
+dfsm_dispatch(
+       dfsm_t *dfsm,
+       cs_dispatch_flags_t dispatch_types);
+
+gboolean 
+dfsm_restartable(dfsm_t *dfsm);
+
+gboolean
+dfsm_is_initialized(dfsm_t *dfsm);
+
+void
+dfsm_set_errormode(dfsm_t *dfsm);
+
+cs_error_t 
+dfsm_send_message(
+       dfsm_t *dfsm,
+       uint16_t msgtype,
+       struct iovec *iov, 
+       int len);
+
+/* only call this from another thread - else you get blocked forever */
+cs_error_t 
+dfsm_send_message_sync(
+       dfsm_t *dfsm,
+       uint16_t msgtype,
+       struct iovec *iov, 
+       int len,
+       dfsm_result_t *rp);
+
+cs_error_t 
+dfsm_send_update(
+       dfsm_t *dfsm,
+       struct iovec *iov, 
+       unsigned int len);
+
+cs_error_t 
+dfsm_send_update_complete(dfsm_t *dfsm);
+
+gboolean 
+dfsm_lowest_nodeid(dfsm_t *dfsm);
+
+gboolean 
+dfsm_nodeid_is_local(
+       dfsm_t *dfsm, 
+       uint32_t nodeid,
+       uint32_t pid);
+
+cs_error_t 
+dfsm_verify_request(dfsm_t *dfsm);
+
+cfs_service_t *
+service_dfsm_new(dfsm_t *dfsm);
+
+void 
+service_dfsm_destroy(cfs_service_t *service); 
+
+
+#endif /* _PVE_DFSM_H_ */
diff --git a/src/pmxcfs/logger.c b/src/pmxcfs/logger.c
new file mode 100644 (file)
index 0000000..3792650
--- /dev/null
@@ -0,0 +1,663 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#define _XOPEN_SOURCE /* glibc2 needs this */
+#include <time.h> /* for strptime */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <glib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+
+#define SYSLOG_MAX_LINE_LENGTH 8192
+
+#include "cfs-utils.h"
+#include "logger.h"
+
+/*
+ * 64 bit FNV-1a non-zero initial basis
+ */
+#define FNV1A_64_INIT ((uint64_t) 0xcbf29ce484222325ULL)
+/*
+ * 64 bit Fowler/Noll/Vo FNV-1a hash code
+ * (copied from sheepdog sources)
+ */
+static inline uint64_t fnv_64a_buf(const void *buf, size_t len, uint64_t hval)
+{
+       unsigned char *bp = (unsigned char *) buf;
+       unsigned char *be = bp + len;
+       while (bp < be) {
+               hval ^= (uint64_t) *bp++;
+               hval += (hval << 1) + (hval << 4) + (hval << 5) +
+                       (hval << 7) + (hval << 8) + (hval << 40);
+       }
+       return hval;
+}
+
+static uint32_t uid_counter = 0;
+
+struct clog_base {
+       uint32_t size;
+       uint32_t cpos;
+       char data[];
+};
+
+typedef struct {
+       uint64_t node_digest;
+       uint32_t uid;
+       uint32_t time;
+} dedup_entry_t;
+
+static clog_entry_t *
+clog_alloc_entry(
+       clog_base_t *clog,
+       uint32_t size)
+{
+       g_return_val_if_fail(clog != NULL, NULL);
+       g_return_val_if_fail(size > sizeof(clog_entry_t), NULL);
+       g_return_val_if_fail(size <= CLOG_MAX_ENTRY_SIZE, NULL);
+
+       uint32_t realsize = ((size + 7) & 0xfffffff8);
+
+       uint32_t newpos;
+
+       if (!clog->cpos) {
+               newpos = sizeof(clog_base_t);
+       } else {
+               clog_entry_t *cur = (clog_entry_t *)((char *)clog + clog->cpos);
+               newpos = cur->next;
+               if ((newpos + realsize) >= clog->size) {
+                       newpos = sizeof(clog_base_t);
+               }
+       }
+
+       clog_entry_t *entry = (clog_entry_t *)((char *)clog + newpos);
+
+       entry->prev = clog->cpos;
+       clog->cpos = newpos;
+       entry->next = newpos + realsize; 
+
+       return entry;
+}
+
+static void
+clog_dump_entry(clog_entry_t *cur, uint32_t cpos)
+{
+       g_return_if_fail(cur != NULL);
+
+       char *node = cur->data;
+       char *ident = node + cur->node_len;
+       char *tag = ident + cur->ident_len;
+       char *msg = tag + cur->tag_len;
+
+       time_t lt = cur->time;
+       char tbuf[256];
+       strftime(tbuf, sizeof(tbuf), "%F %T", localtime(&lt));
+       printf("cpos %05d %08x %s", cpos, cur->uid, tbuf);
+       printf(" %s{%016" PRIX64 "} %s[%s{%016" PRIX64 "}]: %s\n", node, cur->node_digest, tag, ident, cur->ident_digest, msg);
+       
+}
+
+void
+clog_dump(clog_base_t *clog)
+{
+       g_return_if_fail(clog != NULL);
+       
+       uint32_t cpos = clog->cpos;
+
+       while (cpos && (cpos <= clog->cpos || cpos > (clog->cpos + CLOG_MAX_ENTRY_SIZE))) {
+               clog_entry_t *cur = (clog_entry_t *)((char *)clog + cpos);
+               clog_dump_entry(cur, cpos);
+
+               // wrap-around has to land after initial position
+               if (cpos < cur->prev && cur->prev <= clog->cpos) {
+                       break;
+               }
+               cpos = cur->prev;
+       }
+}
+
+void
+clog_dump_json(
+       clog_base_t *clog, 
+       GString *str, 
+       const char *ident, 
+       guint max_entries)
+{
+       g_return_if_fail(clog != NULL);
+       g_return_if_fail(str != NULL);
+       
+       guint64 ident_digest = 0;
+
+       if (ident && ident[0]) {
+               ident_digest = fnv_64a_buf(ident, strlen(ident) + 1, FNV1A_64_INIT);
+       }
+
+       uint32_t cpos = clog->cpos;
+
+       g_string_append_printf(str, "{\n");
+
+       g_string_append_printf(str, "\"data\": [\n");
+
+       guint count = 0;
+       while (cpos && (cpos <= clog->cpos || cpos > (clog->cpos + CLOG_MAX_ENTRY_SIZE))) {
+               clog_entry_t *cur = (clog_entry_t *)((char *)clog + cpos);
+
+               // wrap-around has to land after initial position
+               if (cpos < cur->prev && cur->prev <= clog->cpos) {
+                       break;
+               }
+               cpos = cur->prev;
+
+               if (count >= max_entries)
+                       break;
+
+               if (ident_digest && ident_digest != cur->ident_digest)
+                       continue;
+
+               char *node = cur->data;
+               char *ident = node + cur->node_len;
+               char *tag = ident + cur->ident_len;
+               char *msg = tag + cur->tag_len;
+
+               if (count)
+                       g_string_append_printf(str, ",\n");
+
+               g_string_append_printf(str, "{\"uid\": %u, \"time\": %u, \"pri\": %d, \"tag\": \"%s\", "
+                                      "\"pid\": %u, \"node\": \"%s\", \"user\": \"%s\", "
+                                      "\"msg\": \"%s\"}", cur->uid, cur->time, cur->priority, tag, 
+                                      cur->pid, node, ident, msg);
+               
+               count++;
+
+       }
+
+       if (count)
+               g_string_append_printf(str, "\n");
+
+       g_string_append_printf(str, "]\n");
+       g_string_append_printf(str, "}\n");
+
+}
+
+uint32_t
+clog_entry_size(const clog_entry_t *entry)
+{
+       g_return_val_if_fail(entry != NULL, 0);
+
+       return sizeof(clog_entry_t) + entry->node_len + 
+               entry->ident_len + entry->tag_len + entry->msg_len;
+}
+
+void
+clog_copy(
+       clog_base_t *clog,
+       const clog_entry_t *entry)
+{
+       g_return_if_fail(clog != NULL);
+       g_return_if_fail(entry != NULL);
+
+       uint32_t size = clog_entry_size(entry);
+       
+       clog_entry_t *new;
+       if ((new = clog_alloc_entry(clog, size)))
+               memcpy((char *)new + 8, (char *)entry + 8, size - 8);
+}
+
+uint32_t
+clog_pack(
+       clog_entry_t *entry,
+       const char *node, 
+       const char *ident, 
+       const char *tag,
+       uint32_t pid,
+       time_t logtime,
+       uint8_t priority,
+       const char *msg)
+{
+       g_return_val_if_fail(entry != NULL, 0);
+       g_return_val_if_fail(ident != NULL, 0);
+       g_return_val_if_fail(tag != NULL, 0);
+       g_return_val_if_fail(msg != NULL, 0);
+       g_return_val_if_fail(priority >= 0, 0);
+       g_return_val_if_fail(priority < 8, 0);
+
+       uint8_t node_len = CFS_MIN(strlen(node) + 1, 255);
+       uint8_t ident_len = CFS_MIN(strlen(ident) + 1, 255);
+       uint8_t tag_len = CFS_MIN(strlen(tag) + 1, 255);
+
+       char *msg_start = entry->data + node_len + ident_len + tag_len;
+       *msg_start = 0;
+
+       int buf_len = CLOG_MAX_ENTRY_SIZE - (msg_start - (char *)entry);
+       utf8_to_ascii(msg_start, buf_len, msg, TRUE);
+
+       uint32_t msg_len = strlen(msg_start) + 1;
+
+       uint32_t size = sizeof(clog_entry_t) + node_len + ident_len + 
+               tag_len + msg_len;
+
+       if (size > CLOG_MAX_ENTRY_SIZE) {
+               int diff = size - CLOG_MAX_ENTRY_SIZE;
+               msg_len -= diff;
+               size = CLOG_MAX_ENTRY_SIZE;
+       }
+
+       entry->prev = 0;
+       entry->next = 0;
+       entry->uid = ++uid_counter;
+       entry->time = logtime;
+       entry->node_digest = fnv_64a_buf(node, node_len, FNV1A_64_INIT);
+       entry->ident_digest = fnv_64a_buf(ident, ident_len, FNV1A_64_INIT);
+       entry->pid = pid;
+       entry->priority = priority;
+       entry->node_len = node_len;
+       entry->ident_len = ident_len;
+       entry->tag_len = tag_len;
+       entry->msg_len = msg_len;
+
+       char *p = entry->data;
+       g_strlcpy(p, node, node_len);
+       p = p + node_len;
+       g_strlcpy(p, ident, ident_len);
+       p = p + ident_len;
+       g_strlcpy(p, tag, tag_len);
+
+       return size;
+}
+
+clog_base_t *
+clog_new(uint32_t size)
+{
+       g_return_val_if_fail(sizeof(clog_base_t) == 8, NULL);
+
+       if (!size)
+               size = CLOG_DEFAULT_SIZE;
+
+       g_return_val_if_fail(size >= (CLOG_MAX_ENTRY_SIZE*10), NULL);
+
+
+       clog_base_t *clog = (clog_base_t *)g_malloc0(size);
+       if (clog) {
+               clog->size = size;
+       }
+
+       return clog;
+}
+
+static gint
+clog_entry_sort_fn(
+       gconstpointer v1, 
+       gconstpointer v2,
+       gpointer user_data)
+{
+       clog_entry_t *entry1 = (clog_entry_t *)v1;
+       clog_entry_t *entry2 = (clog_entry_t *)v2;
+
+       if (entry1->time != entry2->time)
+               return entry1->time - entry2->time;
+
+       if (entry1->node_digest != entry2->node_digest)
+               return entry1->node_digest - entry2->node_digest;
+
+       return entry1->uid - entry2->uid;
+}
+
+static gboolean
+clog_tree_foreach_fn(
+       gpointer key,
+       gpointer value,
+       gpointer data)
+{
+       clog_entry_t *entry = (clog_entry_t *)value;
+       clog_base_t *clog = (clog_base_t *)data;
+
+       clog_copy(clog, entry);
+
+       return FALSE;
+}
+
+clog_base_t *
+clog_sort(clog_base_t *clog)
+{
+       g_return_val_if_fail(clog != NULL, NULL);
+       g_return_val_if_fail(clog->cpos != 0, NULL);
+       
+       clog_base_t *res = clog_new(clog->size);
+       if (!res)
+               return NULL;
+
+       GTree *tree = g_tree_new_with_data(clog_entry_sort_fn, NULL);
+       if (!tree) {
+               g_free(res);
+               return NULL;
+       }
+
+       uint32_t cpos = clog->cpos;
+
+       while (cpos && (cpos <= clog->cpos || cpos > (clog->cpos + CLOG_MAX_ENTRY_SIZE))) {
+               clog_entry_t *cur = (clog_entry_t *)((char *)clog + cpos);
+
+               g_tree_insert(tree, cur, cur);
+
+               // wrap-around has to land after initial position
+               if (cpos < cur->prev && cur->prev <= clog->cpos) {
+                       break;
+               }
+
+               cpos = cur->prev;
+       }
+
+       g_tree_foreach(tree, clog_tree_foreach_fn, res);
+       g_tree_destroy(tree);
+
+       return res;
+}
+
+uint32_t
+clog_size(clog_base_t *clog)
+{
+       g_return_val_if_fail(clog != NULL, 0);
+       return clog->size;
+}
+
+static gboolean
+dedup_lookup(
+       GHashTable *dedup,
+       const clog_entry_t *entry)
+{
+       g_return_val_if_fail(dedup != NULL, FALSE);
+       g_return_val_if_fail(entry != NULL, FALSE);
+
+       dedup_entry_t *dd = g_hash_table_lookup(dedup, &entry->node_digest);
+       if (!dd) {
+               if (!(dd = g_new0(dedup_entry_t, 1)))
+                       return FALSE;
+
+               dd->node_digest = entry->node_digest;
+               dd->time = entry->time;
+               dd->uid = entry->uid;
+
+               g_hash_table_insert(dedup, dd, dd); 
+
+               return TRUE;
+       }
+
+       if (entry->time > dd->time ||
+           (entry->time == dd->time && entry->uid > dd->uid)) {
+               dd->time = entry->time; 
+               dd->uid = entry->uid; 
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+struct clusterlog {
+       GHashTable *dedup;
+       GMutex mutex;
+       clog_base_t *base;
+};
+
+void 
+clusterlog_dump(
+       clusterlog_t *cl,
+       GString *str, 
+       const char *user, 
+       guint max_entries)
+{
+       g_return_if_fail(cl != NULL);
+       g_return_if_fail(str != NULL);
+
+       g_mutex_lock(&cl->mutex);
+       clog_dump_json(cl->base, str, user, max_entries);
+       g_mutex_unlock(&cl->mutex);
+}
+
+clog_base_t *
+clusterlog_merge(
+       clusterlog_t *cl,
+       clog_base_t **clog, 
+       int count,
+       int local_index)
+{
+       g_return_val_if_fail(cl != NULL, NULL);
+       g_return_val_if_fail(clog != NULL, NULL);
+       g_return_val_if_fail(count >= 2, NULL);
+       g_return_val_if_fail(local_index >= 0, NULL);
+       g_return_val_if_fail(local_index < count, NULL);
+
+       uint32_t cpos[count];
+       uint32_t maxsize = 0;
+
+       GHashTable *dedup;
+       if (!(dedup = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, g_free)))
+               return NULL;
+
+       GTree *tree = g_tree_new_with_data(clog_entry_sort_fn, NULL);
+       if (!tree) {
+               g_hash_table_destroy(dedup);
+               return NULL;
+       }
+
+       clog_base_t *res = clog_new(maxsize);
+       if (!res) {
+               g_hash_table_destroy(dedup);
+               g_tree_destroy(tree);
+               return NULL;
+       }
+
+       g_mutex_lock(&cl->mutex);
+
+       for (int i = 0; i < count; i++) {
+               if (i == local_index)
+                       clog[i] = cl->base;
+
+               if (!clog[i]) {
+                       cfs_critical("log pointer is NULL!");
+                       cpos[i] = 0;
+                       continue;
+               }
+               cpos[i] = clog[i]->cpos;
+               if (clog[i]->size > maxsize)
+                       maxsize = clog[i]->size;
+       }
+
+       size_t logsize = 0;
+       maxsize = res->size - sizeof(clog_base_t) - CLOG_MAX_ENTRY_SIZE;
+
+       int found = 0;
+       while (found >= 0) {
+
+               found = -1;
+               uint32_t last = 0;
+
+               /* select entry wit latest time */
+               for (int i = 0; i < count; i++) {
+                       if (!cpos[i])
+                               continue;
+                       clog_entry_t *cur = (clog_entry_t *)((char *)clog[i] + cpos[i]);
+                       if (cur->time > last) {
+                               last = cur->time;
+                               found = i;
+                       }
+               }
+
+               if (found < 0)
+                       break;
+
+               clog_entry_t *cur = (clog_entry_t *)((char *)clog[found] + cpos[found]);
+
+               if (!g_tree_lookup(tree, cur)) {
+                       g_tree_insert(tree, cur, cur);
+                       dedup_lookup(dedup, cur); /* just to record versions */
+                       logsize += cur->next - cpos[found];
+                       if (logsize >= maxsize)
+                               break;
+               }
+
+               // no previous entry or wrap-around into already overwritten entry
+               if (!cur->prev || (cpos[found] < cur->prev && cur->prev <= clog[found]->cpos)) {
+                       cpos[found] = 0;
+               } else {
+                       cpos[found] = cur->prev;
+                       // wrap-around into current entry
+                       if (!(cpos[found] <= clog[found]->cpos || 
+                             cpos[found] > (clog[found]->cpos + CLOG_MAX_ENTRY_SIZE))) {
+                               cpos[found] = 0;
+                       }
+               }
+       }
+
+       g_tree_foreach(tree, clog_tree_foreach_fn, res);
+       g_tree_destroy(tree);
+
+       g_hash_table_destroy(cl->dedup);
+       cl->dedup = dedup;
+
+       g_free(cl->base);
+       cl->base = res;
+
+       g_mutex_unlock(&cl->mutex);
+
+       return res;
+}
+
+void
+clusterlog_destroy(clusterlog_t *cl)
+{
+       g_return_if_fail(cl != NULL);
+
+       g_mutex_clear(&cl->mutex);
+
+       if (cl->base)
+               g_free(cl->base);
+
+       if (cl->dedup)
+               g_hash_table_destroy(cl->dedup);
+
+       g_free(cl);
+}
+
+clusterlog_t *
+clusterlog_new(void)
+{
+       clusterlog_t *cl = g_new0(clusterlog_t, 1);
+       if (!cl)
+               return NULL;
+
+       g_mutex_init(&cl->mutex);
+
+       if (!(cl->base = clog_new(0)))
+               goto fail;
+
+       if (!(cl->dedup = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, g_free)))
+               goto fail;
+
+       return cl;
+
+fail:
+       clusterlog_destroy(cl);
+       return NULL;
+}
+
+gpointer
+clusterlog_get_state(  
+       clusterlog_t *cl,
+       unsigned int *res_len)
+{
+       g_return_val_if_fail(cl != NULL, NULL);
+       g_return_val_if_fail(res_len != NULL, NULL);
+       
+       g_mutex_lock(&cl->mutex);
+
+       clog_base_t *new;
+       if ((new = clog_sort(cl->base))) {
+               g_free(cl->base);
+               cl->base = new;
+       }
+
+       *res_len = clog_size(cl->base);
+       gpointer msg = g_memdup2(cl->base, *res_len);
+
+       g_mutex_unlock(&cl->mutex);
+
+       return msg;
+}
+
+void
+clusterlog_insert(
+       clusterlog_t *cl,
+       const clog_entry_t *entry)
+{
+       g_return_if_fail(cl != NULL);
+       g_return_if_fail(entry != NULL);
+
+       g_mutex_lock(&cl->mutex);
+
+       if (dedup_lookup(cl->dedup, entry)) {
+               clog_copy(cl->base, entry);
+       } else {
+               cfs_message("ignore insert of duplicate cluster log");
+       }
+
+       g_mutex_unlock(&cl->mutex);
+}
+
+void
+clusterlog_add(
+       clusterlog_t *cl,
+       const char *ident, 
+       const char *tag, 
+       uint32_t pid,
+       uint8_t priority, 
+       const gchar *format,
+       ...)
+{
+       g_return_if_fail(cl != NULL);
+       g_return_if_fail(format != NULL);
+
+       va_list args;
+       va_start (args, format);
+       char *msg = g_strdup_vprintf (format, args);
+       va_end (args);
+
+       time_t ctime = time(NULL);
+       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
+       uint32_t size = clog_pack(entry, cfs.nodename, ident, tag, pid, ctime, priority, msg);
+       g_free(msg);
+
+       if (!size)
+               return;
+
+       clusterlog_insert(cl, entry);
+}
+
diff --git a/src/pmxcfs/logger.h b/src/pmxcfs/logger.h
new file mode 100644 (file)
index 0000000..7c029ed
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_LOGGER_H_
+#define _PVE_LOGGER_H_
+
+#define CLOG_MAX_ENTRY_SIZE 4096
+#define CLOG_DEFAULT_SIZE (8192*16)
+
+typedef struct clog_base clog_base_t;
+
+typedef struct clusterlog clusterlog_t;
+
+typedef struct {
+       uint32_t prev;
+       uint32_t next;
+       uint32_t uid; /* unique id */
+       uint32_t time;
+       uint64_t node_digest;
+       uint64_t ident_digest;
+       uint32_t pid;
+       uint8_t priority; 
+       uint8_t node_len;
+       uint8_t ident_len;
+       uint8_t tag_len;
+       uint32_t msg_len;
+       char data[];
+} clog_entry_t;
+
+clusterlog_t *
+clusterlog_new(void);
+
+void
+clusterlog_destroy(clusterlog_t *cl);
+
+gpointer
+clusterlog_get_state(  
+       clusterlog_t *cl,
+       unsigned int *res_len);
+
+void
+clusterlog_add(
+       clusterlog_t *cl,
+       const char *ident, 
+       const char *tag, 
+       uint32_t pid,
+       uint8_t priority, 
+       const gchar *format,
+       ...) G_GNUC_PRINTF (6, 7);
+
+void
+clusterlog_insert(
+       clusterlog_t *cl,
+       const clog_entry_t *entry);
+
+void 
+clusterlog_dump(
+       clusterlog_t *cl,
+       GString *str, 
+       const char *user, 
+       guint max_entries);
+
+clog_base_t *
+clusterlog_merge(
+       clusterlog_t *cl,
+       clog_base_t **clog, 
+       int count,
+       int local_index);
+
+clog_base_t *
+clog_new(uint32_t size);
+
+uint32_t
+clog_size(clog_base_t *clog);
+
+void
+clog_dump(clog_base_t *clog);
+
+void
+clog_dump_json(
+       clog_base_t *clog, 
+       GString *str, 
+       const char *ident, 
+       guint max_entries);
+
+clog_base_t *
+clog_sort(clog_base_t *clog);
+
+uint32_t
+clog_pack(
+       clog_entry_t *buffer,
+       const char *node, 
+       const char *ident, 
+       const char *tag, 
+       uint32_t pid,
+       time_t logtime,
+       uint8_t priority, 
+       const char *msg);
+
+uint32_t
+clog_entry_size(const clog_entry_t *entry);
+
+void
+clog_copy(
+       clog_base_t *clog,
+       const clog_entry_t *entry);
+
+#endif /* _PVE_LOGGER_H_ */
diff --git a/src/pmxcfs/logtest.c b/src/pmxcfs/logtest.c
new file mode 100644 (file)
index 0000000..15c49ef
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define _XOPEN_SOURCE /* glibc2 needs this */
+#include <time.h> /* for strptime */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/syslog.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "cfs-utils.h"
+#include "logger.h"
+
+cfs_t cfs = {
+        .debug = 0,
+       .nodename = "testnode",
+};
+
+
+int
+main(void)
+{
+
+       uint32_t pid = getpid();
+
+#if 1
+       clusterlog_t *cl3 = clusterlog_new();
+
+       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
+       clog_pack(entry, cfs.nodename, "root", "cluster", pid, time(NULL), LOG_INFO, "starting cluster log");
+       clusterlog_insert(cl3, entry);
+
+       for (int i = 0; i < 5000; i++) {
+               clusterlog_add(cl3, "user1", "TESTDOMAIN1", pid, LOG_INFO, 
+                              "test user1 Ã¤ message asdasd d dsgfdfgdgdg dgg dgdg %d", i);
+       }
+
+#if 0
+       for (int i = 0; i < 5000; i++) {
+               clog_base_t *clog[2] = { cl3->base, cl3->base };
+
+               clusterlog_merge(cl3, clog, 2);
+               //clusterlog_sort(cl3);
+       }
+#endif
+
+       //clog_dump(cl3->base);
+
+       clusterlog_destroy(cl3);
+
+       exit(0);
+
+#endif
+
+
+       clusterlog_t *cl1 = clusterlog_new();
+
+       for (int i = 0; i < 5; i++) {
+               clusterlog_add(cl1, "user1", "TESTDOMAIN1", pid, LOG_INFO, 
+                              "test user1 message asdasd %d", i);              
+       }
+       
+
+#if 0
+       for (int i = 0; i < 5; i++) {
+               clusterlog_add(cl1, "user2", "TESTDOMAIN1", pid, LOG_INFO, 
+                              "test user2 message asdasd %d", i);              
+       }
+#endif
+
+       clusterlog_t *cl2 = clusterlog_new();
+
+#if 1
+       for (int i = 0; i < 5; i++) {
+               clusterlog_add(cl2, "user3", "TESTDOMAIN2", pid, LOG_INFO, 
+                              "test user3 message asdasd %d", i);      
+       }
+#endif
+
+#if 0
+       clog_base_t *clog[2] = { cl1->base, cl2->base };
+
+       clusterlog_merge(cl1, clog, 2);
+       clog_dump(cl1->base);
+
+       clusterlog_destroy(cl1);
+       clusterlog_destroy(cl2);
+#endif
+}
diff --git a/src/pmxcfs/logtest2.c b/src/pmxcfs/logtest2.c
new file mode 100644 (file)
index 0000000..5f8f8f8
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define _XOPEN_SOURCE /* glibc2 needs this */
+#include <time.h> /* for strptime */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/syslog.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "cfs-utils.h"
+#include "logger.h"
+
+struct clog_base {
+       uint32_t size;
+       uint32_t cpos;
+       char data[];
+};
+
+struct clusterlog {
+       GHashTable *dedup;
+       GMutex mutex;
+       clog_base_t *base;
+};
+
+cfs_t cfs = {
+        .debug = 0,
+       .nodename = "testnode",
+};
+
+void get_state(clusterlog_t *cl) {
+    unsigned int res_len;
+    clusterlog_get_state(cl, &res_len);
+}
+
+
+void insert(clusterlog_t *cl) {
+       uint32_t pid = getpid();
+       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
+       clog_pack(entry, cfs.nodename, "root", "cluster", pid, time(NULL), LOG_INFO, "short");
+       clusterlog_insert(cl, entry);
+}
+
+void insert2(clusterlog_t *cl) {
+       uint32_t pid = getpid();
+       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
+       clog_pack(entry, cfs.nodename, "root", "cluster", pid, time(NULL), LOG_INFO, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+       clusterlog_insert(cl, entry);
+}
+
+int
+main(void)
+{
+       uint32_t pid = getpid();
+
+       clusterlog_t *cl3 = clusterlog_new();
+
+       clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
+       clog_pack(entry, cfs.nodename, "root", "cluster", pid, time(NULL), LOG_INFO, "starting cluster log");
+       clusterlog_insert(cl3, entry);
+
+       for (int i = 0; i < 184; i++) {
+              insert2(cl3);
+       }
+
+       for (int i = 0; i < 1629; i++) {
+              insert(cl3);
+       }
+
+       GString *outbuf = g_string_new(NULL);
+
+       // all of these segfault if they don't handle wrap-arounds pointing to already overwritten entries
+       clusterlog_dump(cl3, outbuf, NULL, 8192);
+       clog_dump(cl3->base);
+       get_state(cl3);
+
+       clusterlog_destroy(cl3);
+}
diff --git a/src/pmxcfs/loop.c b/src/pmxcfs/loop.c
new file mode 100644 (file)
index 0000000..a80fc66
--- /dev/null
@@ -0,0 +1,378 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define G_LOG_DOMAIN "loop"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <utime.h>
+#include <sys/stat.h>
+#include <glib.h>
+#include <syslog.h>
+#include <poll.h>
+
+#include "cfs-utils.h"
+#include "loop.h"
+
+struct cfs_service {
+       qb_loop_t *qbloop;      
+       const char *log_domain;
+       cfs_service_callbacks_t *callbacks;
+       gboolean restartable;
+       gpointer context;
+       gboolean initialized;
+       int errcount;
+       time_t last_init;
+       enum qb_loop_priority priority;
+       time_t period;
+       time_t last_timeout;
+};
+
+struct cfs_loop {
+       GThread *worker;
+       gboolean server_started;
+       gboolean stop_worker_flag;
+       GCond server_started_cond;
+       GCond server_stopped_cond;
+       GMutex server_started_mutex;
+       qb_loop_t *qbloop;      
+       struct fuse *fuse;
+       GList *services;
+};
+
+gboolean 
+cfs_service_set_timer(
+       cfs_service_t *service,
+       time_t period)
+{
+       g_return_val_if_fail(service != NULL, FALSE);
+
+       service->period = period;
+
+       return TRUE;
+}
+
+gpointer 
+cfs_service_get_context(cfs_service_t *service)
+{
+       g_return_val_if_fail(service != NULL, NULL);
+
+       return service->context;
+}
+
+void 
+cfs_service_set_restartable(
+       cfs_service_t *service,
+       gboolean restartable)
+{
+       g_return_if_fail(service != NULL);
+
+       service->restartable = restartable;
+}
+
+cfs_service_t *
+cfs_service_new(
+       cfs_service_callbacks_t *callbacks,
+       const char *log_domain,
+       gpointer context)
+{
+       g_return_val_if_fail(callbacks != NULL, NULL);
+       g_return_val_if_fail(callbacks->cfs_service_initialize_fn != NULL, NULL);
+       g_return_val_if_fail(callbacks->cfs_service_finalize_fn != NULL, NULL);
+       g_return_val_if_fail(callbacks->cfs_service_dispatch_fn != NULL, NULL);
+
+       cfs_service_t *service = g_new0(cfs_service_t, 1);
+       if(!service)
+               return NULL;
+       
+       if (log_domain)
+               service->log_domain = log_domain;
+       else
+               service->log_domain = G_LOG_DOMAIN;
+
+       service->callbacks = callbacks;
+
+       service->restartable = TRUE;
+
+       service->context = context;
+
+       return service;
+}
+
+cfs_loop_t *
+cfs_loop_new(struct fuse *fuse)
+{
+       cfs_loop_t *loop = g_new0(cfs_loop_t, 1);
+
+       g_mutex_init(&loop->server_started_mutex);
+       g_cond_init(&loop->server_started_cond);
+       g_cond_init(&loop->server_stopped_cond);
+       
+       if (!(loop->qbloop = qb_loop_create())) {
+               cfs_critical("cant create event loop");
+               g_free(loop);
+               return NULL;
+       }
+
+       loop->fuse = fuse;
+
+       return loop;
+}
+
+void 
+cfs_loop_destroy(cfs_loop_t *loop)
+{
+       g_return_if_fail(loop != NULL);
+
+       if (loop->qbloop)
+               qb_loop_destroy(loop->qbloop);
+
+       if(loop->services)
+               g_list_free(loop->services);
+
+       g_mutex_clear(&loop->server_started_mutex);
+       g_cond_clear(&loop->server_started_cond);
+       g_cond_clear(&loop->server_stopped_cond);
+
+       g_free(loop);
+}
+
+gboolean 
+cfs_loop_add_service(
+       cfs_loop_t *loop, 
+       cfs_service_t *service, 
+       enum qb_loop_priority priority)
+{
+       g_return_val_if_fail(loop != NULL, FALSE);
+       g_return_val_if_fail(service != NULL, FALSE);
+       g_return_val_if_fail(service->log_domain != NULL, FALSE);
+
+       service->priority = priority;
+       service->qbloop = loop->qbloop;
+
+       loop->services = g_list_append(loop->services, service);
+
+       return TRUE;
+}
+
+static int32_t 
+poll_dispatch_fn(
+       int32_t fd, 
+       int32_t revents, 
+       void *data)
+{
+       cfs_service_t *service = (cfs_service_t *)data;
+
+       if (!service->callbacks->cfs_service_dispatch_fn(service, service->context)) {
+               qb_loop_poll_del(service->qbloop, fd);
+               service->initialized = FALSE;
+               service->errcount = 0;
+
+               if (!service->restartable)
+                       service->callbacks->cfs_service_finalize_fn(service, service->context);
+                       
+               return -1;
+       }
+
+       return 0;
+}
+
+static void 
+service_timer_job(void *data)
+{
+       g_return_if_fail(data != NULL);
+
+       cfs_loop_t *loop = (cfs_loop_t *)data;
+       qb_loop_t *qbloop = loop->qbloop;
+
+       gboolean terminate = FALSE;
+       
+       g_mutex_lock (&loop->server_started_mutex);
+
+       if (loop->stop_worker_flag) {
+               cfs_debug ("got terminate request");
+               qb_loop_stop(qbloop);
+               loop->server_started = 0;
+               g_cond_signal (&loop->server_stopped_cond);
+               terminate = TRUE;
+       } else if (!loop->server_started) {
+               loop->server_started = 1;
+               g_cond_signal (&loop->server_started_cond);
+       }
+       
+       g_mutex_unlock (&loop->server_started_mutex);
+
+       if (terminate)
+               return;
+       
+       GList *l = loop->services;
+       while (l) {
+               cfs_service_t *service = (cfs_service_t *)l->data;
+               l = g_list_next(l);
+
+               if (!service->initialized)
+                       continue;
+
+               time_t ctime = time(NULL);
+               if (service->period && service->callbacks->cfs_service_timer_fn &&
+                   ((ctime - service->last_timeout) >= service->period)) {
+                       service->last_timeout = ctime;
+                       service->callbacks->cfs_service_timer_fn(service, service->context);    
+               }
+       }
+
+       qb_loop_timer_handle th;
+       qb_loop_timer_add(qbloop, QB_LOOP_LOW, 1000000000, data, service_timer_job, &th);
+}
+
+static void 
+service_start_job(void *data)
+{
+       g_return_if_fail(data != NULL);
+
+       cfs_loop_t *loop = (cfs_loop_t *)data;
+       qb_loop_t *qbloop = loop->qbloop;
+
+       gboolean terminate = FALSE;
+       g_mutex_lock (&loop->server_started_mutex);
+       terminate = loop->stop_worker_flag;
+       g_mutex_unlock (&loop->server_started_mutex);
+
+       if (terminate)
+               return;
+
+       GList *l = loop->services;
+       time_t ctime = time(NULL);
+
+       while (l) {
+               cfs_service_t *service = (cfs_service_t *)l->data;
+               l = g_list_next(l);
+
+               if (service->restartable && !service->initialized && 
+                   ((ctime - service->last_init) > 5)) {
+                       int fd = service->callbacks->cfs_service_initialize_fn(service, service->context);
+                       service->last_init = ctime;
+
+                       if (fd >= 0) {
+                               service->initialized = TRUE;
+                               service->errcount = 0;
+
+                               int res;
+                               if ((res = qb_loop_poll_add(qbloop, service->priority, fd, POLLIN, 
+                                                           service, poll_dispatch_fn)) != 0) {
+                                       cfs_critical("qb_loop_poll_add failed: %s - disabling service", 
+                                                    g_strerror(-res));
+                                       service->initialized = FALSE;
+                                       service->restartable = FALSE;
+                                       service->callbacks->cfs_service_finalize_fn(service, service->context);
+                               }
+                       } else {
+                               if (!service->errcount) 
+                                       cfs_dom_critical(service->log_domain, "can't initialize service");
+                               service->errcount++;
+                       }
+               }
+       }
+
+       qb_loop_timer_handle th;
+       qb_loop_timer_add(qbloop, QB_LOOP_LOW, 1000000000, data, service_start_job, &th);
+}
+
+static gpointer 
+cfs_loop_worker_thread(gpointer data)
+{
+       g_return_val_if_fail(data != NULL, NULL);
+
+       cfs_loop_t *loop = (cfs_loop_t *)data;
+       qb_loop_t *qbloop = loop->qbloop;
+
+       GList *l;
+       time_t ctime = time(NULL);
+       l = loop->services;
+       while (l) {
+               cfs_service_t *service = (cfs_service_t *)l->data;
+               l = g_list_next(l);
+               service->last_timeout = ctime;
+       }
+
+       qb_loop_timer_handle th;
+       qb_loop_timer_add(qbloop, QB_LOOP_LOW, 10000000, loop, service_start_job, &th);
+
+       qb_loop_timer_add(qbloop, QB_LOOP_LOW, 1000000000, loop, service_timer_job, &th);
+
+       cfs_debug("start loop");
+       
+       qb_loop_run(qbloop);
+
+       cfs_debug("end loop");
+
+       l = loop->services;
+       while (l) {
+               cfs_service_t *service = (cfs_service_t *)l->data;
+               l = g_list_next(l);
+               service->callbacks->cfs_service_finalize_fn(service, service->context);
+       }
+
+       return NULL;
+}
+
+gboolean 
+cfs_loop_start_worker(cfs_loop_t *loop)
+{
+       g_return_val_if_fail(loop != NULL, FALSE);
+
+       loop->worker = g_thread_new("cfs_loop", cfs_loop_worker_thread, loop);
+       
+       g_mutex_lock (&loop->server_started_mutex);
+       while (!loop->server_started)
+               g_cond_wait (&loop->server_started_cond, &loop->server_started_mutex);
+       g_mutex_unlock (&loop->server_started_mutex);
+       
+       cfs_debug("worker started");
+       
+       return TRUE;
+}
+
+void
+cfs_loop_stop_worker(cfs_loop_t *loop)
+{
+       g_return_if_fail(loop != NULL);
+
+       cfs_debug("cfs_loop_stop_worker");
+
+       g_mutex_lock (&loop->server_started_mutex);
+       loop->stop_worker_flag = TRUE;
+       while (loop->server_started)
+               g_cond_wait (&loop->server_stopped_cond, &loop->server_started_mutex);
+       g_mutex_unlock (&loop->server_started_mutex);
+
+       if (loop->worker) {
+               g_thread_join(loop->worker);
+               loop->worker = NULL;
+       }
+}
diff --git a/src/pmxcfs/loop.h b/src/pmxcfs/loop.h
new file mode 100644 (file)
index 0000000..68b783c
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_LOOP_H_
+#define _PVE_LOOP_H_
+
+#define FUSE_USE_VERSION 26
+
+#include <glib.h>
+#include <fuse.h>
+#include <qb/qbdefs.h>
+#include <qb/qbutil.h>
+#include <qb/qbloop.h>
+
+typedef struct cfs_loop cfs_loop_t;
+
+typedef struct cfs_service cfs_service_t;
+
+typedef int (*cfs_service_initialize_fn_t)(
+       cfs_service_t *service, 
+       gpointer context);
+
+typedef        gboolean (*cfs_service_finalize_fn_t)(
+       cfs_service_t *service, 
+       gpointer context);
+
+typedef gboolean (*cfs_service_dispatch_fn_t)(
+       cfs_service_t *service, 
+       gpointer context);
+
+typedef void (*cfs_service_timer_fn_t)(
+       cfs_service_t *service, 
+       gpointer context);
+
+typedef struct {
+       cfs_service_initialize_fn_t cfs_service_initialize_fn;
+       cfs_service_finalize_fn_t cfs_service_finalize_fn;
+       cfs_service_dispatch_fn_t cfs_service_dispatch_fn;
+       cfs_service_timer_fn_t cfs_service_timer_fn;
+} cfs_service_callbacks_t;
+
+cfs_service_t *cfs_service_new(
+       cfs_service_callbacks_t *callbacks,
+       const char *log_domain,
+       gpointer context);
+
+gpointer cfs_service_get_context(
+       cfs_service_t *service);
+
+gboolean cfs_service_set_timer(
+       cfs_service_t *service,
+       time_t period);
+
+void cfs_service_set_restartable(
+       cfs_service_t *service,
+       gboolean restartable);
+
+cfs_loop_t *cfs_loop_new(struct fuse *fuse);
+
+void cfs_loop_destroy(
+       cfs_loop_t *loop);
+
+gboolean cfs_loop_add_service(
+       cfs_loop_t *loop,
+       cfs_service_t *service,
+       enum qb_loop_priority priority);
+
+gboolean cfs_loop_start_worker(
+       cfs_loop_t *loop);
+
+void cfs_loop_stop_worker(
+       cfs_loop_t *loop);
+
+
+#endif /* _PVE_LOOP_H_ */
diff --git a/src/pmxcfs/memdb.c b/src/pmxcfs/memdb.c
new file mode 100644 (file)
index 0000000..6953227
--- /dev/null
@@ -0,0 +1,1601 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+#include <glib.h>
+
+#include "cfs-utils.h"
+#include "memdb.h"
+#include "status.h"
+
+#define CFS_LOCK_TIMEOUT (60*2)
+
+memdb_tree_entry_t *
+memdb_tree_entry_new(
+       const char *name)
+{
+       g_return_val_if_fail(name != NULL, NULL);
+
+       memdb_tree_entry_t *te = g_malloc0(sizeof(memdb_tree_entry_t) + strlen(name) + 1);
+       g_return_val_if_fail(te != NULL, NULL);
+
+       strcpy(te->name, name);
+
+       return te;
+}
+
+memdb_tree_entry_t *
+memdb_tree_entry_copy(
+       memdb_tree_entry_t *te, 
+       gboolean with_data)
+{
+       g_return_val_if_fail(te != NULL, NULL);
+
+       memdb_tree_entry_t *cpy = memdb_tree_entry_new(te->name);
+
+       cpy->parent = te->parent;
+       cpy->inode = te->inode;
+       cpy->version = te->version;
+       cpy->writer = te->writer;
+       cpy->mtime = te->mtime;
+       cpy->type = te->type;
+       cpy->size = te->size;
+
+       if (with_data && te->size && te->type == DT_REG) {
+               cpy->data.value = g_memdup2(te->data.value, te->size);
+       } else { 
+               cpy->data.value = NULL;
+       }
+
+       return cpy;
+}
+
+void 
+memdb_tree_entry_free(
+       memdb_tree_entry_t *te)
+{
+       if (!te)
+               return;
+
+       if (te->type == DT_REG) {
+               if (te->data.value)
+                       g_free(te->data.value);
+       }
+
+       if (te->type == DT_DIR) {
+               if (te->data.entries)
+                       g_hash_table_destroy(te->data.entries);
+       }
+
+       g_free(te);
+}
+
+void 
+memdb_lock_info_free(memdb_lock_info_t *li)
+{
+       g_return_if_fail(li != NULL);
+
+       if (li->path)
+               g_free(li->path);
+
+       g_free(li);
+}
+
+static gint
+memdb_tree_compare(
+       gconstpointer v1,
+       gconstpointer v2)
+{
+       guint64 a = ((const memdb_tree_entry_t *)v1)->inode;
+       guint64 b = ((const memdb_tree_entry_t *)v2)->inode;
+
+       if (a == b)
+               return 0;
+
+       if (a > b)
+               return 1;
+       
+       return -1;
+}
+
+static void 
+split_path(
+       const char *path, 
+       char **dirname, 
+       char **basename)
+{
+       char *dup = g_strdup (path);
+       int len = strlen (dup) - 1;
+       while (len >= 0 && dup[len] == '/') dup[len--] = 0;
+
+       char *dn = g_path_get_dirname (dup);
+       char *bn = g_path_get_basename (dup);
+
+       g_free (dup);
+
+       *dirname = dn;
+       *basename = bn;
+}
+
+static memdb_tree_entry_t *
+memdb_lookup_dir_entry(
+       memdb_t *memdb, 
+       const char *name, 
+       memdb_tree_entry_t *parent)
+{
+
+       g_return_val_if_fail(memdb != NULL, NULL);
+       g_return_val_if_fail(name != NULL, NULL);
+       g_return_val_if_fail(parent != NULL, NULL);
+       g_return_val_if_fail(parent->type == DT_DIR, NULL);
+       
+       GHashTable *ht = parent->data.entries;
+
+       g_return_val_if_fail(ht != NULL, NULL);
+
+       return g_hash_table_lookup(ht, name);
+}
+
+static memdb_tree_entry_t *
+memdb_lookup_path(
+       memdb_t *memdb, 
+       const char *path,
+       memdb_tree_entry_t **parent)
+{
+       g_return_val_if_fail(memdb != NULL, NULL);
+       g_return_val_if_fail(path != NULL, NULL);
+       g_return_val_if_fail(parent != NULL, NULL);
+
+       memdb_tree_entry_t *cdir = memdb->root; 
+       *parent = NULL;
+
+       if (path[0] == 0 || ((path[0] == '.' || path[0] == '/')  && path[1] == 0))
+               return cdir;
+
+       gchar **set = g_strsplit_set(path, "/", 0);
+
+       int i = 0;
+       char *name;
+       
+       while ((name = set[i++])) {
+
+               if (name[0] == 0) continue;
+
+               *parent = cdir;
+               if ((cdir = memdb_lookup_dir_entry(memdb, name, cdir)) == NULL)
+                       break;
+       }
+
+       g_strfreev(set);
+
+       return cdir;
+}
+
+
+static gboolean 
+name_is_vm_config(
+       const char *name, 
+       guint32 *vmid_ret)
+{
+       if (!name || name[0] < '1' || name[0] > '9')
+               return FALSE;
+
+       char *end = NULL;
+
+       errno = 0; /* see man strtoul */
+
+       unsigned long int vmid = strtoul(name, &end, 10);
+
+       if (!end || end[0] != '.' || end[1] != 'c'|| end[2] != 'o' || end[3] != 'n' ||
+           end[4] != 'f' || end[5] != 0 || errno != 0 || vmid > G_MAXUINT32)
+               return FALSE;
+
+       if (vmid_ret)
+               *vmid_ret = (guint32)vmid;
+
+       return TRUE;
+}
+
+static gboolean
+valid_nodename(
+       const char *nodename) 
+{
+       g_return_val_if_fail(nodename != NULL, FALSE);
+
+       /* LDH rule (letters, digits, hyphen) */
+
+       int len = strlen(nodename);
+
+       if (len < 1) {
+               return FALSE;
+       }
+
+       for (int i = 0; i < len; i ++) {
+               char c = nodename[i];
+               if ((c >= 'A' && c <= 'Z') ||
+                   (c >= 'a' && c <= 'z') ||
+                   (c >= '0' && c <= '9') ||
+                   (i != 0 && i != (len-1) && c == '-'))
+                       continue;
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static char*
+dir_contain_vm_config(
+       const char *dirname, 
+       int *vmtype_ret)
+{
+       if (!dirname)
+               return NULL;
+
+       if (strncmp(dirname, "nodes/", 6) != 0)
+               return NULL;
+
+       dirname += 6;
+
+       char *nodename = NULL;
+
+       char **sa = g_strsplit(dirname, "/", 2);
+       if (sa[0] && sa[1] && valid_nodename(sa[0])) {
+               if (strcmp(sa[1], "qemu-server") == 0) {
+                       *vmtype_ret = VMTYPE_QEMU;
+                       nodename = g_strdup(sa[0]);
+               } else if (strcmp(sa[1], "openvz") == 0) {
+                       // FIXME: remove openvz stuff for 7.x
+                       *vmtype_ret = VMTYPE_OPENVZ;
+                       nodename = g_strdup(sa[0]);
+               } else if (strcmp(sa[1], "lxc") == 0) {
+                       *vmtype_ret = VMTYPE_LXC;
+                       nodename = g_strdup(sa[0]);
+               } 
+       }
+
+       g_strfreev(sa);
+
+       return nodename;
+}
+
+static char *
+path_contain_vm_config(
+       const char *path, 
+       int *vmtype_ret, 
+       guint32 *vmid_ret)
+{
+       if (!path)
+               return NULL;
+
+       char *dirname = NULL;
+       char *base = NULL;
+       char *nodename = NULL;
+
+       split_path(path, &dirname, &base);
+
+       if (name_is_vm_config(base, vmid_ret))
+               nodename = dir_contain_vm_config(dirname, vmtype_ret);
+       
+       g_free (dirname);
+       g_free (base);
+
+       return nodename;
+}
+
+static gboolean
+vmlist_add_dir(
+       memdb_t *memdb, 
+       GHashTable *vmlist,
+       const char *nodename, 
+       const int vmtype, 
+       memdb_tree_entry_t *subdir)
+{
+       g_return_val_if_fail(memdb != NULL, FALSE);
+       g_return_val_if_fail(vmlist != NULL, FALSE);
+       g_return_val_if_fail(subdir != NULL, FALSE);
+       g_return_val_if_fail(subdir->type == DT_DIR, FALSE);
+       g_return_val_if_fail(subdir->data.entries != NULL, FALSE);
+
+       gboolean ret = TRUE;
+
+       GHashTable *ht = subdir->data.entries;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_hash_table_iter_init (&iter, ht);
+
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+
+               memdb_tree_entry_t *node_te = (memdb_tree_entry_t *)value;
+
+               if (node_te->type != DT_REG)
+                       continue;
+
+               guint32 vmid = 0;
+               if (!name_is_vm_config(node_te->name, &vmid))
+                       continue;
+
+               if (!vmlist_hash_insert_vm(vmlist, vmtype, vmid, nodename, FALSE))
+                       ret = FALSE;
+       }
+
+       return ret;
+}
+
+
+gboolean
+memdb_lock_expired(
+       memdb_t *memdb,
+       const char *path,
+       const guchar csum[32])
+{
+       g_return_val_if_fail(memdb != NULL, FALSE);
+       g_return_val_if_fail(memdb->locks != NULL, FALSE);
+       g_return_val_if_fail(path != NULL, FALSE);
+       g_return_val_if_fail(csum != NULL, FALSE);
+
+       memdb_lock_info_t *li;
+       uint32_t ctime = time(NULL);
+
+       if ((li = g_hash_table_lookup(memdb->locks, path))) {
+               if (memcmp(csum, li->csum, 32) != 0) {
+                       li->ltime = ctime;
+                       memcpy(li->csum, csum, 32);
+                       g_critical("wrong lock csum - reset timeout");
+                       return FALSE;
+               }
+               if ((ctime > li->ltime) && ((ctime - li->ltime) > CFS_LOCK_TIMEOUT))
+                       return TRUE;
+       } else {
+               li = g_new0(memdb_lock_info_t, 1);
+               li->path = g_strdup(path);
+               li->ltime = ctime;
+               memcpy(li->csum, csum, 32);
+               g_hash_table_replace(memdb->locks, li->path, li);
+       }
+
+       return FALSE;
+}
+
+void
+memdb_update_locks(memdb_t *memdb)
+{
+       g_return_if_fail(memdb != NULL);
+       g_return_if_fail(memdb->locks != NULL);
+
+       memdb_tree_entry_t *te, *parent;
+
+       if (!(te = memdb_lookup_path(memdb, "priv/lock", &parent)))
+               return;
+               
+       if (te->type != DT_DIR)
+               return;
+
+
+       GHashTable *old =  memdb->locks;
+       memdb->locks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+                                            (GDestroyNotify)memdb_lock_info_free);
+       GHashTableIter iter;
+       GHashTable *ht = te->data.entries;
+
+       gpointer key, value;
+
+       g_hash_table_iter_init (&iter, ht);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+
+               memdb_tree_entry_t *lock_te = (memdb_tree_entry_t *)value;
+               if (lock_te->type != DT_DIR)
+                       continue;
+
+               memdb_lock_info_t *li;
+               li = g_new0(memdb_lock_info_t, 1);
+               li->path = g_strdup_printf("priv/lock/%s", lock_te->name);
+
+               guchar csum[32];
+               if (memdb_tree_entry_csum(lock_te, csum)) {
+                       memcpy(li->csum, csum, 32);
+                       memdb_lock_info_t *oldli;
+                       if ((oldli = g_hash_table_lookup(memdb->locks, lock_te->name)) &&
+                           (memcmp(csum, oldli->csum, 32) == 0)) {
+                               li->ltime = oldli->ltime;
+                       } else {
+                               li->ltime = time(NULL);
+                       }
+                       g_hash_table_insert(memdb->locks, li->path, li);
+               } else {
+                       memdb_lock_info_free(li);
+               }
+       }
+
+       if (old)
+               g_hash_table_destroy(old);
+       
+}
+
+gboolean 
+memdb_recreate_vmlist(
+       memdb_t *memdb)
+{
+       g_return_val_if_fail(memdb != NULL, FALSE);
+
+       memdb_tree_entry_t *te, *parent;
+
+       if (!(te = memdb_lookup_path(memdb, "nodes", &parent)))
+               return TRUE;
+               
+       if (te->type != DT_DIR)
+               return TRUE;
+
+       GHashTable *vmlist = vmlist_hash_new();
+
+       GHashTable *ht = te->data.entries;
+
+       gboolean ret = TRUE;
+
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_hash_table_iter_init (&iter, ht);
+
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+
+               memdb_tree_entry_t *node_te = (memdb_tree_entry_t *)value;
+               if (node_te->type != DT_DIR)
+                       continue;
+
+               if (!valid_nodename(node_te->name))
+                       continue;
+
+               if ((te = g_hash_table_lookup(node_te->data.entries, "qemu-server"))) {
+                       if (!vmlist_add_dir(memdb, vmlist, node_te->name, VMTYPE_QEMU, te))
+                               ret = FALSE;
+               }
+               // FIXME: remove openvz stuff for 7.x
+               if ((te = g_hash_table_lookup(node_te->data.entries, "openvz"))) {
+                       if (!vmlist_add_dir(memdb, vmlist, node_te->name, VMTYPE_OPENVZ, te))
+                               ret = FALSE;
+               }
+               if ((te = g_hash_table_lookup(node_te->data.entries, "lxc"))) {
+                       if (!vmlist_add_dir(memdb, vmlist, node_te->name, VMTYPE_LXC, te))
+                               ret = FALSE;
+               }
+       }
+
+       /* always update list - even if we detected duplicates */
+       cfs_status_set_vmlist(vmlist);
+
+       return ret;
+}
+
+memdb_t *
+memdb_open(const char *dbfilename)
+{
+       memdb_t *memdb = g_new0(memdb_t, 1);
+       
+       g_mutex_init(&memdb->mutex);
+
+       memdb->dbfilename = g_strdup(dbfilename);
+
+       memdb->root = memdb_tree_entry_new("");
+       memdb->root->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
+       memdb->root->type = DT_DIR;
+
+       memdb->index = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL,
+                                            (GDestroyNotify)memdb_tree_entry_free);
+
+       g_hash_table_replace(memdb->index, &memdb->root->inode, memdb->root);
+
+       memdb->locks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+                                            (GDestroyNotify)memdb_lock_info_free);
+
+       if (!(memdb->bdb = bdb_backend_open(dbfilename, memdb->root, memdb->index))) {
+               memdb_close(memdb);
+               return NULL;
+       }
+
+       record_memdb_reload();
+
+       if (!memdb_recreate_vmlist(memdb)) {
+               memdb_close(memdb);
+               return NULL;            
+       }
+
+       memdb_update_locks(memdb);
+
+       cfs_debug("memdb open '%s' successful (version = %016" PRIX64 ")",
+                 dbfilename, memdb->root->version);
+
+       return memdb;
+}
+
+void 
+memdb_close(memdb_t *memdb)
+{
+       g_return_if_fail(memdb != NULL);
+
+       g_mutex_lock (&memdb->mutex);
+
+       if (memdb->bdb)
+               bdb_backend_close(memdb->bdb);
+
+       if (memdb->index)
+               g_hash_table_destroy(memdb->index);
+
+       if (memdb->locks)
+               g_hash_table_destroy(memdb->locks);
+
+       if (memdb->dbfilename)
+               g_free(memdb->dbfilename);
+
+       memdb->index = NULL;
+       memdb->bdb = NULL;
+       memdb->dbfilename = NULL;
+
+       g_mutex_unlock (&memdb->mutex);
+
+       g_mutex_clear (&memdb->mutex);
+
+       g_free(memdb);
+}
+
+int memdb_mkdir(
+       memdb_t *memdb, 
+       const char *path,
+       guint32 writer,
+       guint32 mtime)
+{
+       g_return_val_if_fail(memdb != NULL, -EINVAL);
+       g_return_val_if_fail(path != NULL, -EINVAL);
+
+       int ret = -EACCES;
+
+       char *dirname = NULL;
+       char *base = NULL;
+
+       g_mutex_lock (&memdb->mutex);
+
+       if (memdb->errors) {
+               ret = -EIO;
+               goto ret;
+       }
+
+       split_path(path, &dirname, &base);
+
+       memdb_tree_entry_t *parent, *unused;
+
+       if (!(parent = memdb_lookup_path(memdb, dirname, &unused))) {
+               ret = -ENOENT;
+               goto ret;
+       }
+               
+       if (parent->type != DT_DIR) {
+               ret = -ENOTDIR;
+               goto ret;
+       }
+
+       /* do not allow '.' and '..' */
+       if ((base[0] == 0) ||
+           (base[0] == '.' && base[1] == 0) ||
+           (base[0] == '.' && base[1] == '.' && base[2] == 0)) {
+               ret = -EACCES;          
+               goto ret;
+       }
+
+       memdb_tree_entry_t *te;
+       if ((te = memdb_lookup_dir_entry(memdb, base, parent))) {
+               ret = -EEXIST;
+               goto ret;
+       }
+
+       memdb->root->version++;
+       memdb->root->mtime = mtime;
+       memdb->root->writer = writer;
+
+       te = memdb_tree_entry_new(base);
+       te->parent = parent->inode;
+       te->data.entries = g_hash_table_new(g_str_hash, g_str_equal);
+       te->inode = te->version = memdb->root->version;
+       te->writer = writer;
+       te->type = DT_DIR;
+       te->mtime = mtime;
+
+       g_hash_table_replace(parent->data.entries, te->name, te);
+       g_hash_table_replace(memdb->index, &te->inode, te);
+
+       cfs_debug("memdb_mkdir %s %s %016" PRIX64, dirname, base, memdb->root->version);
+
+       if (bdb_backend_write(memdb->bdb, te->inode, te->parent, te->version, 
+                             te->writer, te->mtime, 0, DT_DIR, te->name, NULL, 0)) {
+               memdb->errors = 1;
+               ret = -EIO;
+               goto ret;
+       }
+
+       if (strcmp(dirname, "priv/lock") == 0) {
+               g_hash_table_remove(memdb->locks, path);
+               guchar csum[32];
+               if (memdb_tree_entry_csum(te, csum)) {
+                       memdb_lock_expired(memdb, path, csum); // insert a new entry
+               }
+       }
+
+       ret = 0;
+
+ ret:
+       g_mutex_unlock (&memdb->mutex);
+
+       g_free (dirname);
+       g_free (base);
+
+       return ret;
+}
+
+// Original memdb_read without locking - Caller MUST handle the locking
+int
+memdb_read_nolock(
+       memdb_t *memdb,
+       const char *path,
+       gpointer *data_ret)
+{
+       g_return_val_if_fail(memdb != NULL, -EINVAL);
+       g_return_val_if_fail(path != NULL, -EINVAL);
+       g_return_val_if_fail(data_ret != NULL, -EINVAL);
+
+       memdb_tree_entry_t *te, *parent;
+
+       if ((te = memdb_lookup_path(memdb, path, &parent))) {
+               if (te->type == DT_REG) {
+                       *data_ret = g_memdup2(te->data.value, te->size);
+                       guint32 size = te->size;
+                       return size;
+               }
+       }
+
+       return -ENOENT;
+}
+
+int
+memdb_read(
+       memdb_t *memdb,
+       const char *path,
+       gpointer *data_ret)
+{
+       int res;
+       g_mutex_lock (&memdb->mutex);
+
+       res = memdb_read_nolock(memdb, path, data_ret);
+
+       g_mutex_unlock (&memdb->mutex);
+
+       return res;
+}
+
+static int 
+memdb_pwrite(
+       memdb_t *memdb, 
+       const char *path, 
+       guint32 writer, 
+       guint32 mtime, 
+       gconstpointer data, 
+       size_t count, 
+       off_t offset, 
+       gboolean truncate, 
+       gboolean create)
+{
+       g_return_val_if_fail(memdb != NULL, -EINVAL);
+       g_return_val_if_fail(path != NULL, -EINVAL);
+       g_return_val_if_fail(count == 0 || data != NULL, -EINVAL);
+
+       int ret = -EACCES;
+
+       char *dirname = NULL;
+       char *base = NULL;
+       char *nodename = NULL;
+
+       g_mutex_lock (&memdb->mutex);
+
+       if (memdb->errors) {
+               ret = -EIO;
+               goto ret;
+       }
+
+       if ((offset + count) > MEMDB_MAX_FILE_SIZE) {
+               ret = -EFBIG;
+               goto ret;
+       }
+
+       split_path(path, &dirname, &base);
+
+       memdb_tree_entry_t *parent, *unused;
+       if (!(parent = memdb_lookup_path(memdb, dirname, &unused))) {
+               ret = -ENOENT;
+               goto ret;
+       }
+       if (parent->type != DT_DIR) {
+               ret = -ENOTDIR;
+               goto ret;
+       }
+
+       /* do not allow '.' and '..' */
+       if ((base[0] == 0) ||
+           (base[0] == '.' && base[1] == 0) ||
+           (base[0] == '.' && base[1] == '.' && base[2] == 0)) {
+               ret = -EACCES;          
+               goto ret;
+       }
+
+       guint32 vmid = 0;
+       int vmtype = 0;
+
+       if ((nodename = path_contain_vm_config(path, &vmtype, &vmid))) {
+               if (vmlist_different_vm_exists(vmtype, vmid, nodename)) {
+                       ret = -EEXIST;
+                       goto ret;
+               }
+       }
+
+       gpointer olddata = NULL;
+
+       memdb_tree_entry_t *te, *old;
+       if ((old = te = memdb_lookup_dir_entry(memdb, base, parent))) {
+               if (te->type != DT_REG) { 
+                       ret = -ENOTDIR;
+                       goto ret;
+               }
+
+               if (create) {
+                       ret = -EEXIST;
+                       goto ret;
+               }
+
+               memdb->root->version++;
+               memdb->root->mtime = mtime;
+               memdb->root->writer = writer;
+
+               olddata = te->data.value;
+       } else {
+
+               if (!create) {
+                       ret = -ENOENT;
+                       goto ret;
+               }
+
+               memdb->root->version++;
+               memdb->root->mtime = mtime;
+               memdb->root->writer = writer;
+
+               te = memdb_tree_entry_new(base);
+               te->parent = parent->inode;
+               te->type = DT_REG;
+               te->inode = memdb->root->version;
+       }
+
+       te->version = memdb->root->version;
+       te->writer = writer;
+       te->mtime = mtime;
+
+       size_t newsize = offset + count;
+
+       gpointer newdata = NULL;
+
+       if (olddata) {
+
+               if (newsize > te->size) {
+                       newdata = g_malloc0(newsize);
+                       memcpy(newdata, olddata, te->size);
+
+               } else {
+
+                       if (!truncate) {
+                               newsize = te->size;
+                       }
+                       newdata = g_malloc0(newsize);
+                       memcpy(newdata, olddata, newsize);
+               }
+
+               if (count && data)
+                       memcpy((uint8_t *) newdata + offset, data, count);
+
+       } else {
+
+               if (count && data) {
+                       newdata = g_malloc0(newsize);
+                       memcpy((uint8_t *) newdata + offset, data, count);
+               }
+       }
+
+       te->size = newsize;
+       te->data.value = newdata;
+
+       g_free(olddata);
+
+       if (!old) {
+               g_hash_table_replace(parent->data.entries, te->name, te);
+               g_hash_table_replace(memdb->index, &te->inode, te);
+       }
+
+       record_memdb_change(path);
+
+       cfs_debug("memdb_pwrite %s %s %016" PRIX64 " %016" PRIX64, dirname, te->name, te->inode, te->version);
+
+       if (bdb_backend_write(memdb->bdb, te->inode, te->parent, te->version, 
+                             te->writer, te->mtime, te->size, te->type, te->name, 
+                             te->data.value, 0)) {
+               memdb->errors = 1;
+               ret = -EIO;
+               goto ret;
+       }
+
+       if (nodename)
+               vmlist_register_vm(vmtype, vmid, nodename);
+
+       ret = count;
+
+ ret:
+       g_mutex_unlock (&memdb->mutex);
+
+       g_free (nodename);
+       g_free (dirname);
+       g_free (base);
+
+       return ret;
+}
+
+int 
+memdb_mtime(
+       memdb_t *memdb, 
+       const char *path, 
+       guint32 writer, 
+       guint32 mtime)
+{
+       g_return_val_if_fail(memdb != NULL, -EINVAL);
+       g_return_val_if_fail(path != NULL, -EINVAL);
+
+       int ret = -EACCES;
+
+       char *dirname = NULL;
+       char *base = NULL;
+
+       g_mutex_lock (&memdb->mutex);
+
+       if (memdb->errors) {
+               ret = -EIO;
+               goto ret;
+       }
+
+       split_path(path, &dirname, &base);
+
+       memdb_tree_entry_t *parent, *unused;
+       if (!(parent = memdb_lookup_path(memdb, dirname, &unused))) {
+               ret = -ENOENT;
+               goto ret;
+       }
+       if (parent->type != DT_DIR) {
+               ret = -ENOTDIR;
+               goto ret;
+       }
+
+       /* do not allow '.' and '..' */
+       if ((base[0] == 0) ||
+           (base[0] == '.' && base[1] == 0) ||
+           (base[0] == '.' && base[1] == '.' && base[2] == 0)) {
+               ret = -EACCES;          
+               goto ret;
+       }
+
+       memdb_tree_entry_t *te;
+       if (!(te = memdb_lookup_dir_entry(memdb, base, parent))) {
+               ret = -ENOENT;
+               goto ret;
+       }
+
+        int is_lock = (strcmp(dirname, "priv/lock") == 0) && (te->type == DT_DIR);
+
+       /* NOTE: we use utime(0,0) to trigger 'unlock', so we do not
+        * allow to change mtime for locks (only if mtime is newer).
+        * See README for details about locks.
+        */
+        if (is_lock) {
+            if (mtime < te->mtime) {
+               cfs_debug("dir is locked");
+               ret = -EACCES;          
+               goto ret;
+            } else {
+                /* only allow lock updates if the writer is the same */
+                if (te->writer != writer) { 
+                    ret = -EACCES;             
+                    goto ret;
+                }
+            }
+       }
+       
+       memdb->root->version++;
+       memdb->root->mtime = mtime;
+       memdb->root->writer = writer;
+
+       te->version = memdb->root->version;
+       te->writer = writer;
+       te->mtime = mtime;
+
+       record_memdb_change(path);
+
+       cfs_debug("memdb_mtime %s %s %016" PRIX64 " %016" PRIX64, dirname, te->name, te->inode, te->version);
+
+       if (bdb_backend_write(memdb->bdb, te->inode, te->parent, te->version, 
+                             te->writer, te->mtime, te->size, te->type, te->name, 
+                             te->data.value, 0)) {
+               memdb->errors = 1;
+               ret = -EIO;
+               goto ret;
+       }
+
+        if (is_lock) {
+            cfs_debug("update cfs lock");
+            g_hash_table_remove(memdb->locks, path);
+            guchar csum[32];
+            if (memdb_tree_entry_csum(te, csum)) {
+                memdb_lock_expired(memdb, path, csum); // insert a new entry
+            }
+        }
+
+       ret = 0;
+
+ ret:
+       g_mutex_unlock (&memdb->mutex);
+
+       g_free (dirname);
+       g_free (base);
+
+       return ret;
+}
+
+int 
+memdb_create(
+       memdb_t *memdb, 
+       const char *path,
+       guint32 writer,
+       guint32 mtime)
+{
+       return memdb_pwrite(memdb, path, writer, mtime, NULL, 0, 0, FALSE, TRUE);
+}
+
+int 
+memdb_write(
+       memdb_t *memdb, 
+       const char *path, 
+       guint32 writer, 
+       guint32 mtime,
+       gconstpointer data, 
+       size_t count, 
+       off_t offset, 
+       gboolean truncate)
+{
+       return memdb_pwrite(memdb, path, writer, mtime, data, count, offset, truncate, FALSE);
+}
+
+memdb_tree_entry_t *
+memdb_getattr(
+       memdb_t *memdb, 
+       const char *path)
+{
+       memdb_tree_entry_t *te, *parent; 
+
+       g_mutex_lock (&memdb->mutex);
+
+       if ((te = memdb_lookup_path(memdb, path, &parent))) {
+
+               memdb_tree_entry_t *cpy = memdb_tree_entry_copy(te, 0);
+
+               g_mutex_unlock (&memdb->mutex);
+
+               return cpy;
+       }
+
+       g_mutex_unlock (&memdb->mutex);
+
+       return NULL;
+}
+
+GList *
+memdb_readdir(
+       memdb_t *memdb, 
+       const char *path)
+{
+       g_return_val_if_fail(memdb != NULL, NULL);
+       g_return_val_if_fail(path != NULL, NULL);
+
+       memdb_tree_entry_t *te, *parent;
+
+       GList *list = NULL;
+
+       g_mutex_lock (&memdb->mutex);
+
+       if (!(te = memdb_lookup_path(memdb, path, &parent)))
+               goto ret;
+               
+       if (te->type != DT_DIR)
+               goto ret;
+
+       GHashTable *ht = te->data.entries;
+
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_hash_table_iter_init (&iter, ht);
+
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+
+               te = (memdb_tree_entry_t *)value;
+
+               memdb_tree_entry_t *cpy = memdb_tree_entry_copy(te, 0);
+
+               list = g_list_append(list, cpy);
+       }
+
+ ret:
+       g_mutex_unlock (&memdb->mutex);
+       
+       return list;
+}
+
+void 
+memdb_dirlist_free(GList *dirlist)
+{
+       GList *l = dirlist;
+
+       while (l) {
+               if (l->data) 
+                       g_free (l->data);
+
+               l = g_list_next(l);
+       }
+
+       if (dirlist) 
+               g_list_free(dirlist);
+}
+
+static int 
+unlink_tree_entry(
+       memdb_t *memdb,
+       memdb_tree_entry_t *parent,
+       memdb_tree_entry_t *te)
+{
+       g_return_val_if_fail(parent != NULL, -EACCES);
+       g_return_val_if_fail(parent->inode == te->parent, -EACCES);
+
+       if (te->type == DT_DIR)
+               if (g_hash_table_size(te->data.entries))
+                       return -ENOTEMPTY;
+
+       if (!g_hash_table_steal(parent->data.entries, te->name)) {
+               cfs_critical("internal error - can't delete entry");
+               memdb->errors = 1;
+               return -EIO;
+       }
+
+       if (!g_hash_table_steal(memdb->index, &te->inode)) {
+               cfs_critical("internal error - can't delete entry");
+               memdb->errors = 1;
+               return -EIO;
+       }
+
+       return 0;
+}
+
+int 
+memdb_rename(
+       memdb_t *memdb, 
+       const char *from, 
+       const char *to, 
+       guint32 writer,
+       guint32 mtime)
+{
+       int ret = -EACCES;
+
+       char *nodename = NULL;
+       char *dirname = NULL;
+       char *base = NULL;
+
+       guint32 vmid = 0;
+       guint32 from_vmid = 0;
+       int vmtype = 0;
+       int from_vmtype = 0;
+       char *from_node = NULL;
+
+       g_mutex_lock (&memdb->mutex);
+
+       if (memdb->errors) {
+               ret = -EIO;
+               goto ret;
+       }
+
+       memdb_tree_entry_t *from_te, *from_parent;
+       memdb_tree_entry_t *to_te, *to_parent;
+       memdb_tree_entry_t *target_te, *target_parent;
+
+       guint64 delete_inode = 0;
+
+       if (!(from_te = memdb_lookup_path(memdb, from, &from_parent))) {
+               ret = -ENOENT;
+               goto ret;
+       }       
+
+       if (!from_parent) { /* can't rename root */
+               ret = -EACCES;
+               goto ret;
+       }
+
+       from_node = path_contain_vm_config(from, &from_vmtype, &from_vmid);
+
+       if (from_te->type == DT_REG && (nodename = path_contain_vm_config(to, &vmtype, &vmid))) {
+               if (vmlist_different_vm_exists(vmtype, vmid, nodename)) {
+                       if (!(from_node && vmid == from_vmid)) {
+                               ret = -EEXIST;
+                               goto ret;
+                       }
+               }
+       }
+
+       /* we do not allow rename for locks */
+       if (from_te->type == DT_DIR && path_is_lockdir(from)) {
+               ret = -EACCES;
+               goto ret;
+       }
+
+       if ((to_te = memdb_lookup_path(memdb, to, &to_parent))) {
+
+               if ((ret = unlink_tree_entry(memdb, to_parent, to_te)) != 0)
+                       goto ret;
+
+               base = strdup(to_te->name);
+
+               delete_inode = to_te->inode;
+
+               target_te = to_parent;
+
+               memdb_tree_entry_free(to_te);
+
+       } else {
+
+               split_path(to, &dirname, &base);
+
+               if (!(target_te = memdb_lookup_path(memdb, dirname, &target_parent))) {
+                       ret = -ENOENT;
+                       goto ret;
+               }
+               
+               if (target_te->type != DT_DIR) {
+                       ret = -ENOTDIR;
+                       goto ret;
+               }
+       }
+
+       record_memdb_change(from);
+       record_memdb_change(to);
+
+       /* NOTE: unlink_tree_entry() make sure that we can only 
+          rename emtpy directories */
+
+       if ((ret = unlink_tree_entry(memdb, from_parent, from_te)) != 0)
+               goto ret;
+
+       memdb->root->version++;
+       memdb->root->mtime = mtime;
+       memdb->root->writer = writer;
+
+       memdb_tree_entry_t *new = memdb_tree_entry_new(base);
+       new->parent = target_te->inode;
+       new->inode = from_te->inode;
+       new->version = memdb->root->version;
+       new->writer = writer;
+       new->mtime = mtime;
+       new->size = from_te->size;
+       new->type =  from_te->type;
+       new->data = from_te->data;
+
+       g_free(from_te);
+
+       g_hash_table_replace(target_te->data.entries, new->name, new);
+       g_hash_table_replace(memdb->index, &new->inode, new);
+
+       if (bdb_backend_write(memdb->bdb, new->inode, new->parent, 
+                             new->version, new->writer, new->mtime,
+                             new->size, new->type, new->name, 
+                             new->data.value, delete_inode)) {
+               memdb->errors = 1;
+               ret = -EIO;
+               goto ret;
+       }
+
+       if (new->type == DT_REG) {
+
+               if (from_node)
+                       vmlist_delete_vm(from_vmid);
+
+               if (nodename)
+                       vmlist_register_vm(vmtype, vmid, nodename);
+               
+       } else if (new->type == DT_DIR) {
+               /* directories are alwayse empty (see unlink_tree_entry) */
+       }
+
+       ret = 0;
+
+ ret:
+       g_mutex_unlock (&memdb->mutex);
+
+       g_free(from_node);
+       g_free (nodename);
+       g_free (dirname);
+       g_free (base);
+
+       return ret;
+}
+
+int 
+memdb_delete(
+       memdb_t *memdb, 
+       const char *path, 
+       guint32 writer, 
+       guint32 mtime)
+{
+       memdb_tree_entry_t *te, *parent;
+
+       g_mutex_lock (&memdb->mutex);
+
+       int ret = -EACCES;
+
+       if (memdb->errors) {
+               ret = -EIO;
+               goto ret;
+       }
+
+       if (!(te = memdb_lookup_path(memdb, path, &parent))) {
+               ret = -ENOENT;
+               goto ret;
+       }       
+               
+       if (!parent) { /* cant remove root */
+               ret = -EACCES;
+               goto ret;
+       }
+
+       if (te->type == DT_DIR) {
+               if (g_hash_table_size(te->data.entries)) {
+                       ret = -ENOTEMPTY;
+                       goto ret;
+               }
+
+               g_hash_table_remove(memdb->locks, path);
+       }
+
+       record_memdb_change(path);
+
+       if ((ret = unlink_tree_entry(memdb, parent, te)) != 0)
+               goto ret;
+
+       memdb->root->version++;
+       memdb->root->mtime = mtime;
+       memdb->root->writer = writer;
+
+       if (bdb_backend_write(memdb->bdb, 0, 0, memdb->root->version, writer, mtime, 0, 
+                             DT_REG, NULL, NULL, te->inode)) {
+               memdb->errors = 1;
+               memdb_tree_entry_free(te);
+               ret = -EIO;
+               goto ret;
+       }
+
+       memdb_tree_entry_free(te);
+
+       int vmtype = 0;
+       guint32 vmid = 0;
+       char *nodename;
+       if ((nodename = path_contain_vm_config(path, &vmtype, &vmid))) {
+               g_free(nodename); 
+               vmlist_delete_vm(vmid);
+       }
+
+       ret = 0;
+
+ ret:
+       g_mutex_unlock (&memdb->mutex);
+
+       return ret;
+}
+
+int 
+memdb_statfs(
+       memdb_t *memdb, 
+       struct statvfs *stbuf)
+{
+       g_return_val_if_fail(memdb != NULL, -EINVAL);
+       g_return_val_if_fail(stbuf != NULL, -EINVAL);
+
+       g_mutex_lock (&memdb->mutex);
+
+       GHashTableIter iter;
+       gpointer key, value;
+
+       size_t size = 0;
+       size_t files = 0;
+
+       g_hash_table_iter_init (&iter, memdb->index);
+
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               memdb_tree_entry_t *te = (memdb_tree_entry_t *)value;
+               files++;
+               size += te->size;
+       }
+
+       g_mutex_unlock (&memdb->mutex);
+
+       stbuf->f_bsize = MEMDB_BLOCKSIZE;
+       stbuf->f_blocks = MEMDB_BLOCKS;
+       stbuf->f_bfree = stbuf->f_bavail = stbuf->f_blocks - 
+               ((size + stbuf->f_bsize - 1)/stbuf->f_bsize);
+       stbuf->f_files = MEMDB_MAX_INODES;
+       stbuf->f_ffree = stbuf->f_files - files;
+
+       stbuf->f_namemax = 256;
+
+       return 0;
+}
+
+void 
+tree_entry_debug(memdb_tree_entry_t *te)
+{
+       g_return_if_fail(te != NULL);
+
+       // same as  tree_entry_print(), but use cfs_debug() instead of g_print()
+
+       cfs_debug("%016" PRIX64 " %c %016" PRIX64 " %016" PRIX64 " %08X %08X %08X %s\n",
+               te->inode, te->type == DT_DIR ? 'D' : 'R', te->parent, te->version,
+               te->writer, te->mtime, te->size, te->name); 
+}
+
+void 
+tree_entry_print(memdb_tree_entry_t *te)
+{
+       g_return_if_fail(te != NULL);
+
+       g_print("%016" PRIX64 " %c %016" PRIX64 " %016" PRIX64 " %08X %08X %08X %s\n",
+               te->inode, te->type == DT_DIR ? 'D' : 'R', te->parent, te->version,
+               te->writer, te->mtime, te->size, te->name); 
+}
+
+void 
+memdb_dump(memdb_t *memdb)
+{
+       g_return_if_fail(memdb != NULL);
+
+       g_mutex_lock (&memdb->mutex);
+       
+       GList *list = g_hash_table_get_values(memdb->index);
+      
+       list = g_list_sort(list, memdb_tree_compare);
+
+       g_print("%16s %c %16s %16s %8s %8s %8s %s\n",
+               "INODE", 'T', "PARENT", "VERSION", "WRITER", "MTIME", "SIZE", "NAME");
+
+       GList *l = list;
+       while (l) {
+               memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
+
+               tree_entry_print(te);
+
+               l = g_list_next(l);
+       }
+
+       g_list_free(list);
+
+       g_mutex_unlock (&memdb->mutex);
+}
+
+void 
+memdb_dump_index (memdb_index_t *idx)
+{
+       g_return_if_fail(idx != NULL);
+
+       g_print ("INDEX DUMP %016" PRIX64 "\n", idx->version);
+
+       int i;
+       for (i = 0; i < idx->size; i++) {
+               g_print ("%016" PRIX64 " %016" PRIX64 "%016" PRIX64 "%016" PRIX64 "%016" PRIX64 "\n", idx->entries[i].inode,
+                        *((guint64 *)idx->entries[i].digest),
+                        *((guint64 *)(idx->entries[i].digest + 8)),
+                        *((guint64 *)(idx->entries[i].digest + 16)),
+                        *((guint64 *)(idx->entries[i].digest + 24)));
+       }
+}
+
+memdb_index_t *
+memdb_index_copy(memdb_index_t *idx)
+{
+       g_return_val_if_fail(idx != NULL, NULL);
+
+       int bytes = sizeof(memdb_index_t) + idx->size*sizeof(memdb_index_extry_t);
+       if (idx->bytes != bytes) {
+               cfs_critical("memdb index contains wrong number of bytes");
+               return NULL;
+       }
+
+       memdb_index_t *copy = (memdb_index_t *)g_memdup2(idx, bytes);
+
+       return copy;
+}
+
+gboolean
+memdb_tree_entry_csum(
+       memdb_tree_entry_t *te,
+       guchar csum[32])
+{
+       g_return_val_if_fail(te != NULL, FALSE);
+       g_return_val_if_fail(csum != NULL, FALSE);
+
+       GChecksum *sha256 = g_checksum_new(G_CHECKSUM_SHA256); 
+
+       g_checksum_update(sha256, (unsigned char*)&te->inode, sizeof(te->inode));
+       g_checksum_update(sha256, (unsigned char*)&te->version, sizeof(te->version));
+       g_checksum_update(sha256, (unsigned char*)&te->writer, sizeof(te->writer));
+       g_checksum_update(sha256, (unsigned char*)&te->mtime, sizeof(te->mtime));
+       g_checksum_update(sha256, (unsigned char*)&te->size, sizeof(te->size));
+       g_checksum_update(sha256, (unsigned char*)&te->type, sizeof(te->type));
+       g_checksum_update(sha256, (unsigned char*)&te->parent, sizeof(te->parent));
+       g_checksum_update(sha256, (unsigned char*)te->name, strlen(te->name));
+
+       if (te->type == DT_REG && te->size) 
+               g_checksum_update(sha256, (unsigned char*)te->data.value, te->size);
+
+       size_t csum_len = 32;
+       g_checksum_get_digest(sha256, csum, &csum_len);
+       g_checksum_free(sha256);
+
+       return TRUE;
+} 
+
+gboolean 
+memdb_compute_checksum(
+       GHashTable *index, 
+       memdb_tree_entry_t *root, 
+       guchar *csum, 
+       size_t csum_len)
+{
+       g_return_val_if_fail(index != NULL, FALSE);
+       g_return_val_if_fail(root != NULL, FALSE);
+
+       GChecksum *sha256 = g_checksum_new(G_CHECKSUM_SHA256); 
+
+       GList *list = g_hash_table_get_values(index);
+      
+       list = g_list_sort(list, memdb_tree_compare);
+       
+       GList *l = list;
+       while (l) {
+               memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
+
+               g_checksum_update(sha256, (unsigned char*)&te->inode, sizeof(te->inode));
+               g_checksum_update(sha256, (unsigned char*)&te->version, sizeof(te->version));
+               g_checksum_update(sha256, (unsigned char*)&te->writer, sizeof(te->writer));
+               g_checksum_update(sha256, (unsigned char*)&te->mtime, sizeof(te->mtime));
+               g_checksum_update(sha256, (unsigned char*)&te->size, sizeof(te->size));
+               g_checksum_update(sha256, (unsigned char*)&te->type, sizeof(te->type));
+               g_checksum_update(sha256, (unsigned char*)&te->parent, sizeof(te->parent));
+               g_checksum_update(sha256, (unsigned char*)te->name, strlen(te->name));
+
+               if (te->type == DT_REG && te->size) 
+                       g_checksum_update(sha256, (unsigned char*)te->data.value, te->size);
+               
+               l = g_list_next(l);
+       }
+
+       g_list_free(list);
+
+       g_checksum_get_digest(sha256, csum, &csum_len);
+
+       cfs_debug("checksum: %s", g_checksum_get_string(sha256));
+
+       g_checksum_free(sha256);
+
+       return TRUE;
+}
+
+memdb_index_t *
+memdb_encode_index(
+       GHashTable *index, 
+       memdb_tree_entry_t *root)
+{
+       g_return_val_if_fail(index != NULL, NULL);
+       g_return_val_if_fail(root != NULL, NULL);
+
+       memdb_index_t *idx = NULL;
+
+       int count = g_hash_table_size(index);
+       if (!count) {
+               cfs_critical("memdb index has no entires");
+               return NULL;
+       }
+
+       int bytes = sizeof(memdb_index_t) + count*sizeof(memdb_index_extry_t);
+       idx = g_malloc0(bytes);
+
+       idx->size = count;
+       idx->bytes = bytes;
+       idx->version = root->version;
+       idx->mtime = root->mtime;
+       idx->writer = root->writer;
+
+       GChecksum *sha256 = g_checksum_new(G_CHECKSUM_SHA256); 
+
+       GList *list = g_hash_table_get_values(index);
+      
+       list = g_list_sort(list, memdb_tree_compare);
+       
+       int ind = 0;
+       GList *l = list;
+       while (l) {
+               memdb_tree_entry_t *te = (memdb_tree_entry_t *)l->data;
+
+               if (te->inode > idx->last_inode)
+                       idx->last_inode = te->inode;
+
+               idx->entries[ind].inode = te->inode;
+
+               g_checksum_reset (sha256);
+
+               g_checksum_update(sha256, (unsigned char*)&te->version, sizeof(te->version));
+               g_checksum_update(sha256, (unsigned char*)&te->writer, sizeof(te->writer));
+               g_checksum_update(sha256, (unsigned char*)&te->mtime, sizeof(te->mtime));
+               g_checksum_update(sha256, (unsigned char*)&te->size, sizeof(te->size));
+               g_checksum_update(sha256, (unsigned char*)&te->type, sizeof(te->type));
+               g_checksum_update(sha256, (unsigned char*)&te->parent, sizeof(te->parent));
+               g_checksum_update(sha256, (unsigned char*)te->name, strlen(te->name));
+
+               if (te->type == DT_REG && te->size) 
+                       g_checksum_update(sha256, (unsigned char*)te->data.value, te->size);
+
+               gsize len = 32;
+               g_checksum_get_digest(sha256, (guint8 *)idx->entries[ind].digest, &len);
+               
+               ind++;
+
+               l = g_list_next(l);
+       }
+
+       g_list_free(list);
+
+       g_checksum_free(sha256);
+
+       return idx;
+}
diff --git a/src/pmxcfs/memdb.h b/src/pmxcfs/memdb.h
new file mode 100644 (file)
index 0000000..2d7f54a
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+  Copyright (C) 2010 - 2021 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_MEMDB_H_
+#define _PVE_MEMDB_H_
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <sys/statvfs.h>
+
+#define MEMDB_MAX_FILE_SIZE (1024 * 1024) // 1 MiB
+#define MEMDB_MAX_FSSIZE (128 * 1024 * 1024) // 128 MiB
+#define MEMDB_MAX_INODES (256 * 1024) // 256k
+
+#define MEMDB_BLOCKSIZE 4096
+#define MEMDB_BLOCKS ((MEMDB_MAX_FSSIZE + MEMDB_BLOCKSIZE - 1)/MEMDB_BLOCKSIZE)
+
+typedef struct memdb_tree_entry memdb_tree_entry_t;
+struct memdb_tree_entry {
+       guint64 parent;
+       guint64 inode;
+       guint64 version;
+       guint32 writer;
+       guint32 mtime;
+       guint32 size;
+       char type;       /* DT_REG .. regular file, DT_DIR ... directory */
+       union {
+               GHashTable *entries;
+               gpointer value;
+       } data;
+       char name[];
+};
+
+typedef struct {
+       guint64 inode;
+       char digest[32]; /* SHA256 digest */
+} memdb_index_extry_t;
+
+typedef struct {
+       guint64 version;
+       guint64 last_inode;
+       guint32 writer;
+       guint32 mtime;
+       guint32 size;  /* number of entries */
+       guint32 bytes; /* total bytes allocated */
+       memdb_index_extry_t entries[];
+} memdb_index_t;
+
+typedef struct db_backend db_backend_t;
+
+typedef struct {
+       char *path;
+       guint32 ltime;
+       guchar csum[32];
+} memdb_lock_info_t;
+
+typedef struct {
+       char *dbfilename;
+       gboolean errors;
+       memdb_tree_entry_t *root;
+       GHashTable *index; /* map version ==> memdb_tree_entry */
+       GHashTable *locks; /* contains memdb_lock_info_t */
+       GMutex mutex;
+       db_backend_t *bdb;
+} memdb_t;
+
+memdb_t *
+memdb_open(const char *dbfilename);
+
+void
+memdb_close(memdb_t *memdb);
+
+gboolean
+memdb_checkpoint(memdb_t *memdb);
+
+gboolean
+memdb_recreate_vmlist(memdb_t *memdb);
+
+gboolean
+memdb_lock_expired(
+       memdb_t *memdb,
+       const char *path,
+       const guchar csum[32]);
+
+void
+memdb_update_locks(memdb_t *memdb);
+
+int
+memdb_statfs(
+       memdb_t *memdb,
+       struct statvfs *stbuf);
+
+int
+memdb_mkdir(
+       memdb_t *memdb,
+       const char *path,
+       guint32 writer,
+       guint32 mtime);
+
+int
+memdb_mtime(
+       memdb_t *memdb,
+       const char *path,
+       guint32 writer,
+       guint32 mtime);
+
+GList *
+memdb_readdir(
+       memdb_t *memdb,
+       const char *path);
+
+void
+memdb_dirlist_free(GList *dirlist);
+
+void
+tree_entry_debug(memdb_tree_entry_t *te);
+
+void
+tree_entry_print(memdb_tree_entry_t *te);
+
+memdb_tree_entry_t *
+memdb_tree_entry_new(const char *name);
+
+memdb_tree_entry_t *
+memdb_tree_entry_copy(
+       memdb_tree_entry_t *te,
+       gboolean with_data);
+
+void
+memdb_tree_entry_free(memdb_tree_entry_t *te);
+
+int
+memdb_delete(
+       memdb_t *memdb,
+       const char *path,
+       guint32 writer,
+       guint32 mtime);
+
+int
+memdb_read(
+       memdb_t *memdb,
+       const char *path,
+       gpointer *data_ret);
+
+int
+memdb_read_nolock(
+        memdb_t *memdb,
+        const char *path,
+        gpointer *data_ret);
+
+int
+memdb_create(
+       memdb_t *memdb,
+       const char *path,
+       guint32 writer,
+       guint32 mtime);
+
+int
+memdb_write(
+       memdb_t *memdb,
+       const char *path,
+       guint32 writer,
+       guint32 mtime,
+       gconstpointer data,
+       size_t count,
+       off_t offset,
+       gboolean truncate);
+
+memdb_tree_entry_t *
+memdb_getattr(
+       memdb_t *memdb,
+       const char *path);
+
+int
+memdb_rename(
+       memdb_t *memdb,
+       const char *from,
+       const char *to,
+       guint32 writer,
+       guint32 mtime);
+
+void
+memdb_dump (
+       memdb_t *memdb);
+
+gboolean
+memdb_compute_checksum(
+       GHashTable *index,
+       memdb_tree_entry_t *root,
+       guchar *csum,
+       size_t csum_len);
+
+memdb_index_t *
+memdb_encode_index(
+       GHashTable *index,
+       memdb_tree_entry_t *root);
+
+void
+memdb_dump_index (memdb_index_t *idx);
+
+memdb_index_t *
+memdb_index_copy(memdb_index_t *idx);
+
+gboolean
+memdb_tree_entry_csum(
+       memdb_tree_entry_t *te,
+       guchar csum[32]);
+
+db_backend_t *
+bdb_backend_open(
+       const char *filename,
+       memdb_tree_entry_t *root,
+       GHashTable *index);
+
+void
+bdb_backend_close(db_backend_t *bdb);
+
+int
+bdb_backend_write(
+       db_backend_t *bdb,
+       guint64 inode,
+       guint64 parent,
+       guint64 version,
+       guint32 writer,
+       guint32 mtime,
+       guint32 size,
+       char type,
+       char *name,
+       gpointer value,
+       guint64 delete_inode);
+
+gboolean
+bdb_backend_commit_update(
+       memdb_t *memdb,
+       memdb_index_t *master,
+       memdb_index_t *slave,
+       GList *inodes);
+
+
+#endif /* _PVE_MEMDB_H_ */
diff --git a/src/pmxcfs/pmxcfs.c b/src/pmxcfs/pmxcfs.c
new file mode 100644 (file)
index 0000000..d78a248
--- /dev/null
@@ -0,0 +1,1109 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/utsname.h>
+#include <grp.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <qb/qbdefs.h>
+#include <qb/qbutil.h>
+#include <qb/qblog.h>
+
+#include "cfs-utils.h"
+#include "cfs-plug.h"
+#include "cfs-plug-memdb.h"
+#include "status.h"
+#include "dcdb.h"
+#include "dfsm.h"
+#include "quorum.h"
+#include "confdb.h"
+#include "server.h"
+
+#define DBFILENAME VARLIBDIR "/config.db"
+#define LOCKFILE VARLIBDIR "/.pmxcfs.lockfile"
+#define RESTART_FLAG_FILE RUNDIR "/cfs-restart-flag"
+
+#define CFSDIR "/etc/pve"
+
+cfs_t cfs = {
+       .debug = 0,
+};
+
+static struct fuse *fuse = NULL;
+
+static cfs_plug_t *root_plug;
+
+static void glib_print_handler(const gchar *string)
+{
+       printf("%s", string);
+}
+
+static void glib_log_handler(const gchar *log_domain,
+                            GLogLevelFlags log_level,
+                            const gchar *message,
+                            gpointer user_data)
+{
+
+       cfs_log(log_domain, log_level, NULL, 0, NULL, "%s", message);
+}
+
+static gboolean write_pidfile(pid_t pid)
+{
+       char *strpid = g_strdup_printf("%d\n", pid);
+       gboolean res = atomic_write_file(CFS_PID_FN, strpid, strlen(strpid), 0644, getgid());
+       g_free(strpid);
+
+       return res;
+}
+
+static cfs_plug_t *find_plug(const char *path, char **sub)
+{
+       g_return_val_if_fail(root_plug != NULL, NULL);
+       g_return_val_if_fail(path != NULL, NULL);
+
+       while(*path == '/') path++;
+
+       cfs_debug("find_plug start %s", path);
+
+       char *tmppath = g_strdup(path);
+       char *subpath = tmppath;
+
+       cfs_plug_t *plug = root_plug->lookup_plug(root_plug, &subpath);
+
+       cfs_debug("find_plug end %s = %p (%s)", path, (void *) plug, subpath);
+
+       if (subpath && subpath[0])
+               *sub = g_strdup(subpath);
+
+       g_free(tmppath);
+
+       return plug;
+}
+
+void *cfs_fuse_init(struct fuse_conn_info *conn)
+{
+       return NULL;
+}
+
+static int cfs_fuse_getattr(const char *path, struct stat *stbuf)
+{
+       cfs_debug("enter cfs_fuse_getattr %s", path);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (plug && plug->ops && plug->ops->getattr) {
+               ret = plug->ops->getattr(plug, subpath ? subpath : "", stbuf);
+
+               stbuf->st_gid = cfs.gid;
+
+               if (path_is_private(path)) {
+                       stbuf->st_mode &= 0777700;
+               } else {
+                       if (S_ISDIR(stbuf->st_mode) || S_ISLNK(stbuf->st_mode)) {
+                               stbuf->st_mode &= 0777755; // access for other users
+                       } else {
+                               stbuf->st_mode &= 0777750; // no access for other users
+                       }
+               }
+       }
+
+       cfs_debug("leave cfs_fuse_getattr %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+
+}
+
+static int cfs_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+                           off_t offset, struct fuse_file_info *fi)
+{
+       (void) offset;
+       (void) fi;
+
+       cfs_debug("enter cfs_fuse_readdir %s", path);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (!plug)
+               goto ret;
+
+       if (plug->ops && plug->ops->readdir)
+               ret = plug->ops->readdir(plug, subpath ? subpath : "", buf, filler, 0, fi);
+ret:
+       cfs_debug("leave cfs_fuse_readdir %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_chmod(const char *path, mode_t mode)
+{
+       int ret = -EPERM;
+
+       cfs_debug("enter cfs_fuse_chmod %s", path);
+
+       mode_t allowed_mode = (S_IRUSR | S_IWUSR);
+       if (!path_is_private(path))
+               allowed_mode |= (S_IRGRP);
+
+       // allow only setting our supported modes (0600 for priv, 0640 for rest)
+       // mode has additional bits set, which we ignore; see stat(2)
+       if ((mode & ALLPERMS) == allowed_mode)
+               ret = 0;
+
+       cfs_debug("leave cfs_fuse_chmod %s (%d) mode: %o", path, ret, (int)mode);
+
+       return ret;
+}
+
+static int cfs_fuse_chown(const char *path, uid_t user, gid_t group)
+{
+       int ret = -EPERM;
+
+       cfs_debug("enter cfs_fuse_chown %s", path);
+
+       // we get -1 if no change should be made
+       if ((user == 0 || user == -1) && (group == cfs.gid || group == -1))
+               ret = 0;
+
+       cfs_debug("leave cfs_fuse_chown %s (%d) (uid: %d; gid: %d)", path, ret, user, group);
+
+       return ret;
+}
+
+static int cfs_fuse_mkdir(const char *path, mode_t mode)
+{
+       cfs_debug("enter cfs_fuse_mkdir %s", path);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (!plug)
+               goto ret;
+
+       if (subpath && plug->ops && plug->ops->mkdir)
+               ret = plug->ops->mkdir(plug, subpath, mode);
+
+ ret:
+       cfs_debug("leave cfs_fuse_mkdir %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_rmdir(const char *path)
+{
+       cfs_debug("enter cfs_fuse_rmdir %s", path);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (!plug)
+               goto ret;
+
+       if (subpath && plug->ops && plug->ops->rmdir)
+               ret = plug->ops->rmdir(plug, subpath);
+
+ ret:
+       cfs_debug("leave cfs_fuse_rmdir %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_rename(const char *from, const char *to)
+{
+       cfs_debug("enter cfs_fuse_rename from %s to %s", from, to);
+
+       int ret = -EACCES;
+
+       char *sub_from = NULL;
+       cfs_plug_t *plug_from = find_plug(from, &sub_from);
+
+       char *sub_to = NULL;
+       cfs_plug_t *plug_to = find_plug(to, &sub_to);
+
+       if (!plug_from || !plug_to || plug_from != plug_to)
+               goto ret;
+
+       if (plug_from->ops && plug_from->ops->rename && sub_from && sub_to)
+               ret = plug_from->ops->rename(plug_from, sub_from, sub_to);
+
+ ret:
+       cfs_debug("leave cfs_fuse_rename from %s to %s (%d)", from, to, ret);
+
+       if (sub_from)
+               g_free(sub_from);
+
+       if (sub_to)
+               g_free(sub_to);
+
+       return ret;
+}
+
+static int cfs_fuse_open(const char *path, struct fuse_file_info *fi)
+{
+       cfs_debug("enter cfs_fuse_open %s", path);
+
+       fi->direct_io = 1;
+       fi->keep_cache = 0;
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (plug && plug->ops) {
+               if ((subpath || !plug->ops->readdir) && plug->ops->open) {
+                       ret = plug->ops->open(plug, subpath ? subpath : "", fi);
+               }
+       }
+
+       cfs_debug("leave cfs_fuse_open %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_read(const char *path, char *buf, size_t size, off_t offset,
+                        struct fuse_file_info *fi)
+{
+       (void) fi;
+
+       cfs_debug("enter cfs_fuse_read %s %zu %jd", path, size, offset);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (plug && plug->ops) {
+               if ((subpath || !plug->ops->readdir) && plug->ops->read)
+                       ret = plug->ops->read(plug, subpath ? subpath : "", buf, size, offset, fi);
+       }
+
+       cfs_debug("leave cfs_fuse_read %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_write(const char *path, const char *buf, size_t size,
+                         off_t offset, struct fuse_file_info *fi)
+{
+       (void) fi;
+
+       cfs_debug("enter cfs_fuse_write %s %zu %jd", path, size, offset);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (plug && plug->ops) {
+               if ((subpath || !plug->ops->readdir) && plug->ops->write)
+               ret = plug->ops->write(plug, subpath ? subpath : "",
+                                      buf, size, offset, fi);
+       }
+
+       cfs_debug("leave cfs_fuse_write %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_truncate(const char *path, off_t size)
+{
+       cfs_debug("enter cfs_fuse_truncate %s %jd", path, size);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (plug && plug->ops) {
+               if ((subpath || !plug->ops->readdir) && plug->ops->truncate)
+                       ret = plug->ops->truncate(plug, subpath ? subpath : "", size);
+       }
+
+       cfs_debug("leave cfs_fuse_truncate %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+{
+       cfs_debug("enter cfs_fuse_create %s", path);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (!plug)
+               goto ret;
+
+       if (subpath && plug->ops && plug->ops->create)
+               ret = plug->ops->create(plug, subpath, mode, fi);
+
+ret:
+       cfs_debug("leave cfs_fuse_create %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_unlink(const char *path)
+{
+       cfs_debug("enter cfs_fuse_unlink %s", path);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (!plug)
+               goto ret;
+
+       if (subpath && plug->ops && plug->ops->unlink)
+               ret = plug->ops->unlink(plug, subpath);
+
+ret:
+       cfs_debug("leave cfs_fuse_unlink %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_readlink(const char *path, char *buf, size_t max)
+{
+       cfs_debug("enter cfs_fuse_readlink %s", path);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (!plug)
+               goto ret;
+
+       if (plug->ops && plug->ops->readlink)
+               ret = plug->ops->readlink(plug, subpath ? subpath : "", buf, max);
+
+ret:
+       cfs_debug("leave cfs_fuse_readlink %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_utimens(const char *path, const struct timespec tv[2])
+{
+       cfs_debug("enter cfs_fuse_utimens %s", path);
+
+       int ret = -EACCES;
+
+       char *subpath = NULL;
+       cfs_plug_t *plug = find_plug(path, &subpath);
+
+       if (!plug)
+               goto ret;
+
+       if (plug->ops && plug->ops->utimens)
+               ret = plug->ops->utimens(plug, subpath ? subpath : "", tv);
+
+ret:
+       cfs_debug("leave cfs_fuse_utimens %s (%d)", path, ret);
+
+       if (subpath)
+               g_free(subpath);
+
+       return ret;
+}
+
+static int cfs_fuse_statfs(const char *path, struct statvfs *stbuf)
+{
+       g_return_val_if_fail(root_plug != NULL, PARAM_CHECK_ERRNO);
+
+       cfs_debug("enter cfs_fuse_statfs %s", path);
+
+       int ret = -EACCES;
+
+       if (root_plug && root_plug->ops && root_plug->ops->statfs)
+               ret = root_plug->ops->statfs(root_plug, "", stbuf);
+
+       return ret;
+}
+
+static struct fuse_operations fuse_ops = {
+       .getattr = cfs_fuse_getattr,
+       .readdir = cfs_fuse_readdir,
+       .mkdir = cfs_fuse_mkdir,
+       .rmdir = cfs_fuse_rmdir,
+       .rename = cfs_fuse_rename,
+       .open = cfs_fuse_open,
+       .read = cfs_fuse_read,
+       .write = cfs_fuse_write,
+       .truncate = cfs_fuse_truncate,
+       .create = cfs_fuse_create,
+       .unlink = cfs_fuse_unlink,
+       .readlink = cfs_fuse_readlink,
+       .utimens = cfs_fuse_utimens,
+       .statfs = cfs_fuse_statfs,
+       .init = cfs_fuse_init,
+       .chown = cfs_fuse_chown,
+       .chmod = cfs_fuse_chmod
+};
+
+static char *
+create_dot_version_cb(cfs_plug_t *plug)
+{
+       GString *outbuf = g_string_new(NULL);
+       char *data = NULL;
+
+       if (cfs_create_version_msg(outbuf) == 0) {
+               data = outbuf->str;
+               g_string_free(outbuf, FALSE);
+       } else {
+               g_string_free(outbuf, TRUE);
+       }
+
+       return data;
+}
+
+static char *
+create_dot_members_cb(cfs_plug_t *plug)
+{
+       GString *outbuf = g_string_new(NULL);
+       char *data = NULL;
+
+       if (cfs_create_memberlist_msg(outbuf) == 0) {
+               data = outbuf->str;
+               g_string_free(outbuf, FALSE);
+       } else {
+               g_string_free(outbuf, TRUE);
+       }
+
+       return data;
+}
+
+static char *
+create_dot_vmlist_cb(cfs_plug_t *plug)
+{
+       GString *outbuf = g_string_new(NULL);
+       char *data = NULL;
+
+       if (cfs_create_vmlist_msg(outbuf) == 0) {
+               data = outbuf->str;
+               g_string_free(outbuf, FALSE);
+       } else {
+               g_string_free(outbuf, TRUE);
+       }
+
+       return data;
+}
+
+static char *
+create_dot_rrd_cb(cfs_plug_t *plug)
+{
+       GString *outbuf = g_string_new(NULL);
+
+       cfs_rrd_dump(outbuf);
+       char *data = outbuf->str;
+       g_string_free(outbuf, FALSE);
+
+       return data;
+}
+
+static char *
+create_dot_clusterlog_cb(cfs_plug_t *plug)
+{
+       GString *outbuf = g_string_new(NULL);
+
+       cfs_cluster_log_dump(outbuf, NULL, 50);
+       char *data = outbuf->str;
+       g_string_free(outbuf, FALSE);
+
+       return data;
+}
+
+static char *
+read_debug_setting_cb(cfs_plug_t *plug)
+{
+       return g_strdup_printf("%d\n", !!cfs.debug);
+}
+
+static void
+my_qb_log_filter(struct qb_log_callsite *cs)
+{
+       int32_t priority = cfs.debug ? LOG_DEBUG : LOG_INFO;
+
+       if (qb_bit_is_set(cs->tags, QB_LOG_TAG_LIBQB_MSG_BIT)) {
+               if (cs->priority <= (cfs.debug ? priority : LOG_WARNING)) {
+                       qb_bit_set(cs->targets, QB_LOG_SYSLOG);
+               } else {
+                       qb_bit_clear(cs->targets, QB_LOG_SYSLOG);
+               }
+               if (cs->priority <= priority) {
+                       qb_bit_set(cs->targets, QB_LOG_STDERR);
+               } else {
+                       qb_bit_clear(cs->targets, QB_LOG_STDERR);
+               }
+       } else {
+               if (cs->priority <= priority) {
+                       qb_bit_set(cs->targets, QB_LOG_SYSLOG);
+                       qb_bit_set(cs->targets, QB_LOG_STDERR);
+               } else {
+                       qb_bit_clear(cs->targets, QB_LOG_SYSLOG);
+                       qb_bit_clear(cs->targets, QB_LOG_STDERR);
+               }
+       }
+}
+
+static void
+update_qb_log_settings(void)
+{
+       qb_log_filter_fn_set(my_qb_log_filter);
+
+       if (cfs.debug) {
+               qb_log_format_set(QB_LOG_SYSLOG, "[%g] %p: %b (%f:%l:%n)");
+               qb_log_format_set(QB_LOG_STDERR, "[%g] %p: %b (%f:%l:%n)");
+       } else {
+               qb_log_format_set(QB_LOG_SYSLOG, "[%g] %p: %b");
+               qb_log_format_set(QB_LOG_STDERR, "[%g] %p: %b");
+       }
+}
+
+static int
+write_debug_setting_cb(
+       cfs_plug_t *plug,
+       const char *buf,
+       size_t size)
+{
+       int res = -EIO;
+
+       if (size < 2)
+               return res;
+
+       if (strncmp(buf, "0\n", 2) == 0) {
+               if (cfs.debug) {
+                       cfs_message("disable debug mode");
+                       cfs.debug = 0;
+                       update_qb_log_settings();
+               }
+               return 2;
+       } else if (strncmp(buf, "1\n", 2) == 0) {
+               if (!cfs.debug) {
+                       cfs.debug = 1;
+                       update_qb_log_settings();
+                       cfs_message("enable debug mode");
+               }
+               return 2;
+       }
+
+       return res;
+}
+
+static void
+create_symlinks(cfs_plug_base_t *bplug, const char *nodename)
+{
+       g_return_if_fail(bplug != NULL);
+       g_return_if_fail(nodename != NULL);
+
+       char *lnktarget = g_strdup_printf("nodes/%s", nodename);
+       cfs_plug_link_t *lnk = cfs_plug_link_new("local", lnktarget);
+       g_free(lnktarget);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)lnk);
+
+       lnktarget = g_strdup_printf("nodes/%s/qemu-server", nodename);
+       lnk = cfs_plug_link_new("qemu-server", lnktarget);
+       g_free(lnktarget);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)lnk);
+
+       // FIXME: remove openvz stuff for 7.x
+       lnktarget = g_strdup_printf("nodes/%s/openvz", nodename);
+       lnk = cfs_plug_link_new("openvz", lnktarget);
+       g_free(lnktarget);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)lnk);
+
+       lnktarget = g_strdup_printf("nodes/%s/lxc", nodename);
+       lnk = cfs_plug_link_new("lxc", lnktarget);
+       g_free(lnktarget);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)lnk);
+
+       cfs_plug_func_t *fplug = cfs_plug_func_new(".version", 0440, create_dot_version_cb, NULL);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
+
+       fplug = cfs_plug_func_new(".members", 0440, create_dot_members_cb, NULL);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
+
+       fplug = cfs_plug_func_new(".vmlist", 0440, create_dot_vmlist_cb, NULL);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
+
+       fplug = cfs_plug_func_new(".rrd", 0440, create_dot_rrd_cb, NULL);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
+
+       fplug = cfs_plug_func_new(".clusterlog", 0440, create_dot_clusterlog_cb, NULL);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
+
+       fplug = cfs_plug_func_new(".debug", 0640, read_debug_setting_cb, write_debug_setting_cb);
+       cfs_plug_base_insert(bplug, (cfs_plug_t*)fplug);
+
+
+}
+
+static char *
+lookup_node_ip(const char *nodename)
+{
+       char buf[INET6_ADDRSTRLEN];
+       struct addrinfo *ainfo;
+       struct addrinfo ahints;
+       char *res = NULL;
+       memset(&ahints, 0, sizeof(ahints));
+
+       if (getaddrinfo(nodename, NULL, &ahints, &ainfo))
+               return NULL;
+
+       if (ainfo->ai_family == AF_INET) {
+               struct sockaddr_in *sa = (struct sockaddr_in *)ainfo->ai_addr;
+               inet_ntop(ainfo->ai_family, &sa->sin_addr, buf, sizeof(buf));
+               if (strncmp(buf, "127.", 4) != 0) {
+                       res = g_strdup(buf);
+               }
+       } else if (ainfo->ai_family == AF_INET6) {
+               struct sockaddr_in6 *sa = (struct sockaddr_in6 *)ainfo->ai_addr;
+               inet_ntop(ainfo->ai_family, &sa->sin6_addr, buf, sizeof(buf));
+               if (strcmp(buf, "::1") != 0) {
+                       res = g_strdup(buf);
+               }
+       }
+
+       freeaddrinfo(ainfo);
+
+       return res;
+}
+
+static const char*
+log_tags_stringify(uint32_t tags) {
+       if (qb_bit_is_set(tags, QB_LOG_TAG_LIBQB_MSG_BIT)) {
+               return "libqb";
+       } else {
+               GQuark quark = tags;
+               const char *domain = g_quark_to_string(quark);
+               if (domain != NULL) {
+                       return domain;
+               } else {
+                       return "main";
+               }
+       }
+}
+
+int main(int argc, char *argv[])
+{
+       int ret = -1;
+       int lockfd = -1;
+       int pipefd[2];
+
+       gboolean foreground = FALSE;
+       gboolean force_local_mode = FALSE;
+       gboolean wrote_pidfile = FALSE;
+       memdb_t *memdb = NULL;
+       dfsm_t *dcdb = NULL;
+       dfsm_t *status_fsm = NULL;
+
+       qb_log_init("pmxcfs", LOG_DAEMON, LOG_DEBUG);
+       /* remove default filter */
+       qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_REMOVE,
+                         QB_LOG_FILTER_FILE, "*", LOG_DEBUG);
+
+       qb_log_tags_stringify_fn_set(log_tags_stringify);
+
+       qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
+
+       update_qb_log_settings();
+
+       g_set_print_handler(glib_print_handler);
+       g_set_printerr_handler(glib_print_handler);
+       g_log_set_default_handler(glib_log_handler, NULL);
+
+       GOptionContext *context;
+
+       GOptionEntry entries[] = {
+               { "debug", 'd', 0, G_OPTION_ARG_NONE, &cfs.debug, "Turn on debug messages", NULL },
+               { "foreground", 'f', 0, G_OPTION_ARG_NONE, &foreground, "Do not daemonize server", NULL },
+               { "local", 'l', 0, G_OPTION_ARG_NONE, &force_local_mode,
+                 "Force local mode (ignore corosync.conf, force quorum)", NULL },
+               { NULL },
+       };
+
+       context = g_option_context_new ("");
+       g_option_context_add_main_entries (context, entries, NULL);
+
+       GError *err = NULL;
+       if (!g_option_context_parse (context, &argc, &argv, &err))
+       {
+               cfs_critical("option parsing failed: %s", err->message);
+               g_error_free (err);
+               qb_log_fini();
+               exit (1);
+       }
+       g_option_context_free(context);
+
+       if (optind < argc) {
+               cfs_critical("too many arguments");
+               qb_log_fini();
+               exit(-1);
+       }
+
+       if (cfs.debug) {
+               update_qb_log_settings();
+       }
+
+       struct utsname utsname;
+       if (uname(&utsname) != 0) {
+               cfs_critical("Unable to read local node name");
+               qb_log_fini();
+               exit (-1);
+       }
+
+       char *dot = strchr(utsname.nodename, '.');
+       if (dot)
+               *dot = 0;
+
+       cfs.nodename = g_strdup(utsname.nodename);
+
+       if (!(cfs.ip = lookup_node_ip(cfs.nodename))) {
+               cfs_critical("Unable to get local IP address");
+               qb_log_fini();
+               exit(-1);
+       }
+
+       struct group *www_data = getgrnam("www-data");
+       if (!www_data) {
+               cfs_critical("Unable to get www-data group ID");
+               qb_log_fini();
+               exit (-1);
+       }
+       cfs.gid = www_data->gr_gid;
+
+       umask(027);
+
+       mkdir(VARLIBDIR, 0755);
+       mkdir(RUNDIR, 0755);
+       chown(RUNDIR, 0, cfs.gid);
+
+       if ((lockfd = open(LOCKFILE, O_RDWR|O_CREAT|O_APPEND, 0600)) == -1) {
+               cfs_critical("unable to create lock '%s': %s", LOCKFILE, strerror (errno));
+               goto err;
+       }
+
+       for (int i = 10; i >= 0; i--) {
+               if (flock(lockfd, LOCK_EX|LOCK_NB) != 0) {
+                       if (!i) {
+                               cfs_critical("unable to acquire pmxcfs lock: %s", strerror (errno));
+                               goto err;
+                       }
+                       if (i == 10)
+                               cfs_message("unable to acquire pmxcfs lock - trying again");
+
+                       sleep(1);
+               }
+       }
+
+       cfs_status_init();
+
+       gboolean create = !g_file_test(DBFILENAME, G_FILE_TEST_EXISTS);
+
+       if (!(memdb = memdb_open (DBFILENAME))) {
+               cfs_critical("memdb_open failed - unable to open database '%s'", DBFILENAME);
+               goto err;
+       }
+
+       // automatically import corosync.conf from host
+       if (create && !force_local_mode) {
+               char *cdata = NULL;
+               gsize clen = 0;
+               if (g_file_get_contents(HOST_CLUSTER_CONF_FN, &cdata, &clen, NULL)) {
+
+                       guint32 mtime = time(NULL);
+
+                       memdb_create(memdb, "/corosync.conf", 0, mtime);
+                       if (memdb_write(memdb, "/corosync.conf", 0, mtime, cdata, clen, 0, 1) < 0) {
+                               cfs_critical("memdb_write failed - unable to import corosync.conf");
+                               goto err;
+                       }
+               }
+       }
+
+       // does corosync.conf exist?
+       gpointer conf_data = NULL;
+       int len = memdb_read(memdb, "corosync.conf", &conf_data);
+       if (len >= 0) {
+               if (force_local_mode) {
+                       cfs_message("forcing local mode (although corosync.conf exists)");
+                       cfs_set_quorate(1, TRUE);
+               } else {
+                       if (!(dcdb = dcdb_new(memdb)))
+                               goto err;
+                       dcdb_sync_corosync_conf(memdb, 1);
+               }
+       } else {
+               cfs_debug("using local mode (corosync.conf does not exist)");
+               cfs_set_quorate(1, TRUE);
+       }
+       if (conf_data) g_free(conf_data);
+
+       cfs_plug_memdb_t *config = cfs_plug_memdb_new("memdb", memdb, dcdb);
+
+       cfs_plug_base_t *bplug = cfs_plug_base_new("", (cfs_plug_t *)config);
+
+       create_symlinks(bplug, cfs.nodename);
+
+       root_plug = (cfs_plug_t *)bplug;
+
+       umount2(CFSDIR, MNT_FORCE);
+
+       mkdir(CFSDIR, 0755);
+
+       char *fa[] = { "-f", "-odefault_permissions", "-oallow_other", NULL};
+
+       struct fuse_args fuse_args = FUSE_ARGS_INIT(sizeof (fa)/sizeof(gpointer) - 1, fa);
+
+       struct fuse_chan *fuse_chan = fuse_mount(CFSDIR, &fuse_args);
+       if (!fuse_chan) {
+               cfs_critical("fuse_mount error: %s", strerror(errno));
+               goto err;
+       }
+
+       if (!(fuse = fuse_new(fuse_chan, &fuse_args, &fuse_ops, sizeof(fuse_ops), NULL))) {
+               cfs_critical("fuse_new error: %s", strerror(errno));
+               goto err;
+       }
+
+       fuse_set_signal_handlers(fuse_get_session(fuse));
+
+       if (!foreground) {
+               if (pipe(pipefd) == -1) {
+                       cfs_critical("pipe error: %s", strerror(errno));
+                       goto err;
+               }
+
+               pid_t cpid = fork();
+
+               if (cpid == -1) {
+                       cfs_critical("failed to daemonize program - %s", strerror (errno));
+                       goto err;
+               } else if (cpid) {
+                       int readbytes, errno_tmp;
+                       char readbuffer;
+                       close(pipefd[1]);
+                       readbytes = read(pipefd[0], &readbuffer, sizeof(readbuffer));
+                       errno_tmp = errno;
+                       close(pipefd[0]);
+                       if (readbytes == -1) {
+                               cfs_critical("read error: %s", strerror(errno_tmp));
+                               kill(cpid, SIGKILL);
+                               goto err;
+                       } else if (readbytes != 1 || readbuffer != '1') {
+                               cfs_critical("child failed to send '1'");
+                               kill(cpid, SIGKILL);
+                               goto err;
+                       }
+                       /* child finished starting up */
+                       write_pidfile(cpid);
+                       qb_log_fini();
+                       _exit (0);
+               } else {
+                       int nullfd;
+                       close(pipefd[0]);
+
+                       chroot("/");
+
+                       if ((nullfd = open("/dev/null", O_RDWR, 0)) != -1) {
+                               dup2(nullfd, 0);
+                               dup2(nullfd, 1);
+                               dup2(nullfd, 2);
+                               if (nullfd > 2)
+                                       close (nullfd);
+                       }
+
+                       // do not print to the console after this point
+                       qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE);
+
+                       setsid();
+               }
+       } else {
+               write_pidfile(getpid());
+       }
+
+       wrote_pidfile = TRUE;
+
+       cfs_loop_t *corosync_loop = cfs_loop_new(fuse);
+
+       cfs_service_t *service_quorum = NULL;
+       cfs_service_t *service_confdb = NULL;
+       cfs_service_t *service_dcdb = NULL;
+       cfs_service_t *service_status = NULL;
+
+       if (dcdb) {
+
+               service_quorum = service_quorum_new();
+
+               cfs_loop_add_service(corosync_loop, service_quorum, QB_LOOP_HIGH);
+
+               service_confdb = service_confdb_new();
+
+               cfs_loop_add_service(corosync_loop, service_confdb, QB_LOOP_MED);
+
+               service_dcdb = service_dfsm_new(dcdb);
+               cfs_service_set_timer(service_dcdb, DCDB_VERIFY_TIME);
+
+               cfs_loop_add_service(corosync_loop, service_dcdb, QB_LOOP_MED);
+
+               status_fsm = cfs_status_dfsm_new();
+               service_status = service_dfsm_new(status_fsm);
+
+               cfs_loop_add_service(corosync_loop, service_status, QB_LOOP_LOW);
+
+       }
+
+       cfs_loop_start_worker(corosync_loop);
+
+       server_start(memdb);
+
+       unlink(RESTART_FLAG_FILE);
+
+       if (!foreground) {
+               /* finished starting up, signaling parent */
+               write(pipefd[1], "1", 1);
+               close(pipefd[1]);
+       }
+
+       ret = fuse_loop_mt(fuse);
+
+       open(RESTART_FLAG_FILE, O_CREAT|O_NOCTTY|O_NONBLOCK, S_IRUSR | S_IRGRP);
+       chown(RESTART_FLAG_FILE, 0, cfs.gid);
+
+       cfs_message("teardown filesystem");
+
+       server_stop();
+
+       fuse_unmount(CFSDIR, fuse_chan);
+
+       fuse_destroy(fuse);
+
+       cfs_debug("set stop event loop flag");
+
+       cfs_loop_stop_worker(corosync_loop);
+
+       cfs_loop_destroy(corosync_loop);
+
+       cfs_debug("worker finished");
+
+       if (service_dcdb)
+               service_dfsm_destroy(service_dcdb);
+
+       if (service_confdb)
+               service_confdb_destroy(service_confdb);
+
+       if (service_quorum)
+               service_quorum_destroy(service_quorum);
+
+       if (service_status)
+               service_dfsm_destroy(service_status);
+
+ ret:
+
+       if (status_fsm)
+               dfsm_destroy(status_fsm);
+
+       if (dcdb)
+               dfsm_destroy(dcdb);
+
+       if (memdb)
+               memdb_close(memdb);
+
+       if (wrote_pidfile)
+               unlink(CFS_PID_FN);
+
+       cfs_message("exit proxmox configuration filesystem (%d)", ret);
+
+       cfs_status_cleanup();
+
+       qb_log_fini();
+
+       exit(ret);
+
+ err:
+       goto ret;
+}
diff --git a/src/pmxcfs/quorum.c b/src/pmxcfs/quorum.c
new file mode 100644 (file)
index 0000000..9df0e90
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define G_LOG_DOMAIN "quorum"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include <corosync/quorum.h>
+
+#include "cfs-utils.h"
+#include "loop.h"
+#include "status.h"
+
+typedef struct {
+       quorum_handle_t handle;
+} qs_private_t;
+
+static void quorum_notification_fn(
+       quorum_handle_t handle,
+       uint32_t quorate,
+       uint64_t ring_id,
+       uint32_t view_list_entries,
+       uint32_t *view_list)
+{
+       cs_error_t result;
+
+       cfs_debug("quorum notification called, quorate = %d, "
+                 "number of nodes = %d", quorate, view_list_entries);
+
+       qs_private_t *private = NULL;
+
+       result = quorum_context_get(handle, (gconstpointer *)&private);
+       if (result != CS_OK || !private) {
+               cfs_critical("quorum_context_get error: %d (%p)", result, (void *) private);
+               return;
+       }
+
+       cfs_set_quorate(quorate, FALSE);
+}
+
+static quorum_callbacks_t quorum_callbacks = {
+       .quorum_notify_fn = quorum_notification_fn,
+};
+
+static gboolean service_quorum_finalize(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_val_if_fail(service != NULL, FALSE);
+       g_return_val_if_fail(context != NULL, FALSE);
+
+       qs_private_t *private = (qs_private_t *)context;
+       quorum_handle_t handle = private->handle;
+
+       cs_error_t result;
+
+       cfs_set_quorate(0, TRUE);
+
+       result = quorum_finalize(handle);
+       private->handle = 0;
+       if (result != CS_OK) {
+               cfs_critical("quorum_finalize failed: %d", result);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static int service_quorum_initialize(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_val_if_fail(service != NULL, FALSE);
+       g_return_val_if_fail(context != NULL, FALSE);
+
+       qs_private_t *private = (qs_private_t *)context;
+
+       quorum_handle_t handle = private->handle;
+       cs_error_t result;
+
+       if (!private->handle) {
+            
+                uint32_t quorum_type;
+            
+                result = quorum_initialize(&handle, &quorum_callbacks, &quorum_type);
+                if (result != CS_OK) {
+                        cfs_critical("quorum_initialize failed: %d", result);
+                       goto err_reset_handle;
+               }
+
+               if (quorum_type != QUORUM_SET) {
+                       cfs_critical("quorum_initialize returned wrong quorum_type: %d", quorum_type);
+                       goto err_finalize;
+               }
+
+               result = quorum_context_set(handle, private);
+               if (result != CS_OK) {
+                       cfs_critical("quorum_context_set failed: %d", result);
+                       goto err_finalize;
+               }
+
+               private->handle = handle;
+       }
+       
+
+       result = quorum_trackstart(handle, CS_TRACK_CHANGES);
+       if (result == CS_ERR_LIBRARY || result == CS_ERR_BAD_HANDLE) {
+               cfs_critical("quorum_trackstart failed: %d - closing handle", result);
+               goto err_finalize;
+       } else if (result != CS_OK) {
+               cfs_critical("quorum_trackstart failed: %d - trying again", result);
+               return -1;
+       }
+       
+       int quorum_fd = -1;
+       if ((result = quorum_fd_get(handle, &quorum_fd)) != CS_OK) {
+               cfs_critical("quorum_fd_get failed %d - trying again", result);
+               return -1;
+       }
+
+       return quorum_fd;
+
+ err_finalize:
+       cfs_set_quorate(0, FALSE);
+       quorum_finalize(handle);
+ err_reset_handle:
+       private->handle = 0;
+       return -1;
+}
+
+static gboolean service_quorum_dispatch(
+       cfs_service_t *service,
+       gpointer context)
+{
+       g_return_val_if_fail(service != NULL, FALSE);
+       g_return_val_if_fail(context != NULL, FALSE);
+
+       qs_private_t *private = (qs_private_t *)context;
+       quorum_handle_t handle =  private->handle;
+
+       cs_error_t result;
+
+       int retries = 0;
+loop:
+       result = quorum_dispatch(handle, CS_DISPATCH_ALL);
+       if (result == CS_ERR_TRY_AGAIN) {
+               usleep(100000);
+               ++retries;
+               if ((retries % 100) == 0)
+                       cfs_message("quorum_dispatch retry %d", retries);
+               goto loop;
+       }
+
+
+       if (result == CS_OK || result == CS_ERR_TRY_AGAIN)
+               return TRUE;
+
+       cfs_critical("quorum_dispatch failed: %d", result);
+
+       cfs_set_quorate(0, FALSE);
+       quorum_finalize(handle);
+       private->handle = 0;
+       return FALSE;
+}
+
+static cfs_service_callbacks_t cfs_quorum_callbacks = {
+       .cfs_service_initialize_fn =  service_quorum_initialize,
+       .cfs_service_finalize_fn = service_quorum_finalize,
+       .cfs_service_dispatch_fn = service_quorum_dispatch,
+};
+
+cfs_service_t *service_quorum_new(void)
+{
+       cfs_service_t *service;
+
+       qs_private_t *private = g_new0(qs_private_t, 1);
+       if (!private)
+               return NULL;
+
+       service = cfs_service_new(&cfs_quorum_callbacks, G_LOG_DOMAIN, private); 
+
+       return service;
+}
+
+void service_quorum_destroy(cfs_service_t *service) 
+{
+       g_return_if_fail(service != NULL);
+
+       qs_private_t *private = 
+               (qs_private_t *)cfs_service_get_context(service);
+
+       g_free(private);
+       g_free(service);
+}
diff --git a/src/pmxcfs/quorum.h b/src/pmxcfs/quorum.h
new file mode 100644 (file)
index 0000000..9ee4d52
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_QUORUM_H_
+#define _PVE_QUORUM_H_
+
+#include <glib.h>
+
+#include "loop.h"
+
+cfs_service_t *service_quorum_new(void);
+
+void service_quorum_destroy(
+       cfs_service_t *service);
+
+#endif /* _PVE_QUORUM_H_ */
diff --git a/src/pmxcfs/server.c b/src/pmxcfs/server.c
new file mode 100644 (file)
index 0000000..80f838b
--- /dev/null
@@ -0,0 +1,663 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define G_LOG_DOMAIN "ipcs"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/syslog.h>
+#include <sys/uio.h>
+
+#include <qb/qbdefs.h>
+#include <qb/qbutil.h>
+#include <qb/qbloop.h>
+#include <qb/qbipcs.h>
+
+#include <glib.h>
+
+#include "cfs-utils.h"
+#include "cfs-ipc-ops.h"
+#include "status.h"
+#include "memdb.h"
+#include "logger.h"
+
+static GThread *worker;
+static qb_loop_t *loop;
+static qb_ipcs_service_t* s1;
+static GString *outbuf;
+static memdb_t *memdb;
+
+static int server_started = 0;   /* protect with server_started_mutex */
+static int terminate_server = 0; /* protect with server_started_mutex */
+static GCond server_started_cond;
+static GCond server_stopped_cond;
+static GMutex server_started_mutex;
+
+
+typedef struct {
+       struct qb_ipc_request_header req_header;
+       char name[256];
+} cfs_status_update_request_header_t;
+
+typedef struct {
+       struct qb_ipc_request_header req_header;
+       char name[256];
+       char nodename[256];
+} cfs_status_get_request_header_t;
+
+typedef struct {
+       struct qb_ipc_request_header req_header;
+       uint8_t priority;
+       uint8_t ident_len;
+       uint8_t tag_len;
+       char data[];
+} cfs_log_msg_request_header_t;
+
+typedef struct {
+       struct qb_ipc_request_header req_header;
+       uint32_t max_entries;
+       uint32_t res1;
+       uint32_t res2;
+       uint32_t res3;
+} cfs_log_get_request_header_t;
+
+typedef struct {
+       struct qb_ipc_request_header req_header;
+       uint32_t vmid;
+       char property[];
+} cfs_guest_config_propery_get_request_header_t;
+
+typedef struct {
+       struct qb_ipc_request_header req_header;
+       uint32_t vmid;
+       uint8_t num_props;
+       char props[]; /* list of \0 terminated properties */
+} cfs_guest_config_properties_get_request_header_t;
+
+typedef struct {
+       struct qb_ipc_request_header req_header;
+       char token[];
+} cfs_verify_token_request_header_t;
+
+struct s1_context {
+       int32_t client_pid;
+       uid_t uid;
+       gid_t gid;      
+       gboolean read_only;
+};
+static int32_t s1_connection_accept_fn(
+       qb_ipcs_connection_t *c, 
+       uid_t uid, 
+       gid_t gid)
+{
+       if ((uid == 0 && gid == 0) || (gid == cfs.gid)) {
+               cfs_debug("authenticated connection %d/%d", uid, gid);
+               struct s1_context *ctx = g_new0(struct s1_context, 1);
+               ctx->uid = uid;
+               ctx->gid = gid;
+               ctx->read_only = (gid == cfs.gid);
+
+               struct qb_ipcs_connection_stats stats;
+               qb_ipcs_connection_stats_get(c, &stats, QB_FALSE);
+               ctx->client_pid = stats.client_pid;
+
+               qb_ipcs_context_set(c, ctx);
+               return 0;
+       }
+       cfs_critical("connection from bad user %d! - rejected", uid);
+       return 1;
+}
+
+static void s1_connection_created_fn(
+       qb_ipcs_connection_t *c)
+{
+       struct qb_ipcs_stats srv_stats;
+
+       qb_ipcs_stats_get(s1, &srv_stats, QB_FALSE);
+
+       cfs_debug("Connection created > active:%d > closed:%d",
+                   srv_stats.active_connections,
+                   srv_stats.closed_connections);
+}
+
+static void s1_connection_destroyed_fn(
+       qb_ipcs_connection_t *c)
+{
+       cfs_debug("connection about to be freed");
+       
+       gpointer ctx;
+       if ((ctx = qb_ipcs_context_get(c)))
+               g_free(ctx);
+
+}
+
+static int32_t s1_connection_closed_fn(
+       qb_ipcs_connection_t *c)
+{
+       struct qb_ipcs_connection_stats stats;
+
+       qb_ipcs_connection_stats_get(c, &stats, QB_FALSE);
+
+       cfs_debug("Connection to pid:%d destroyed", stats.client_pid);
+
+       return 0;
+}
+
+static int32_t s1_msg_process_fn(
+       qb_ipcs_connection_t *c,
+       void *data,
+       size_t size)
+{
+       struct qb_ipc_request_header *req_pt = 
+               (struct qb_ipc_request_header *)data;
+
+       struct s1_context *ctx = (struct s1_context *)qb_ipcs_context_get(c);
+
+       if (!ctx) {
+               cfs_critical("qb_ipcs_context_get failed");
+               qb_ipcs_disconnect(c);
+               return 0;
+       }
+
+       int32_t request_id __attribute__ ((aligned(8))) = req_pt->id;
+       int32_t request_size __attribute__ ((aligned(8))) = req_pt->size;
+       cfs_debug("process msg:%d, size:%d", request_id, request_size);
+
+       char *resp = NULL;
+
+       g_string_truncate(outbuf, 0);
+
+       int32_t result = -ECHRNG;
+       if (request_id == CFS_IPC_GET_FS_VERSION) {
+
+               if (request_size != sizeof(struct qb_ipc_request_header)) {
+                       result = -EINVAL;
+               } else {
+                       result = cfs_create_version_msg(outbuf);
+               }
+
+       } else if (request_id == CFS_IPC_GET_CLUSTER_INFO) {
+
+               if (request_size != sizeof(struct qb_ipc_request_header)) {
+                       result = -EINVAL;
+               } else {
+                       result = cfs_create_memberlist_msg(outbuf);
+               }
+
+       } else if (request_id == CFS_IPC_GET_GUEST_LIST) {
+               
+               if (request_size != sizeof(struct qb_ipc_request_header)) {
+                       result = -EINVAL;
+               } else {
+                       result = cfs_create_vmlist_msg(outbuf);
+               }
+       } else if (request_id == CFS_IPC_SET_STATUS) {
+
+               cfs_status_update_request_header_t *rh = 
+                       (cfs_status_update_request_header_t *)data;
+
+               int datasize = request_size - sizeof(cfs_status_update_request_header_t);
+
+               if (ctx->read_only) {
+                       result = -EPERM;
+               } else if (datasize < 0) {
+                       result = -EINVAL;
+               } else {        
+                       /* make sure name is 0 terminated */
+                       rh->name[sizeof(rh->name) - 1] = 0;
+
+                       char *dataptr = (char*) data + sizeof(cfs_status_update_request_header_t);
+
+                       result = cfs_status_set(rh->name, dataptr, datasize);
+               }
+       } else if (request_id == CFS_IPC_GET_STATUS) {
+
+               cfs_status_get_request_header_t *rh =
+                       (cfs_status_get_request_header_t *)data;
+
+               int datasize = request_size - sizeof(cfs_status_get_request_header_t);
+
+               if (datasize < 0) {
+                       result = -EINVAL;
+               } else {        
+                       /* make sure all names are 0 terminated */
+                       rh->name[sizeof(rh->name) - 1] = 0;
+                       rh->nodename[sizeof(rh->nodename) - 1] = 0;
+
+                       result = cfs_create_status_msg(outbuf, rh->nodename, rh->name);
+               }
+       } else if (request_id == CFS_IPC_GET_CONFIG) {
+
+               int pathlen = request_size - sizeof(struct qb_ipc_request_header);
+
+               if (pathlen <= 0) {
+                       result = -EINVAL;
+               } else {
+                       /* make sure path is 0 terminated */
+                       ((char *)data)[request_size - 1] = 0;
+                       char *path = (char*) data + sizeof(struct qb_ipc_request_header);
+
+                       if (ctx->read_only &&  path_is_private(path)) {
+                               result = -EPERM;
+                       } else {
+                               gpointer tmp = NULL;
+                               result = memdb_read(memdb, path, &tmp);
+                               if (result > 0) {
+                                       g_string_append_len(outbuf, tmp, result);
+                                       g_free(tmp);
+                               }
+                       }
+               }                       
+       } else if (request_id == CFS_IPC_LOG_CLUSTER_MSG) {
+
+               cfs_log_msg_request_header_t *rh = 
+                       (cfs_log_msg_request_header_t *)data;
+
+               int datasize = request_size - G_STRUCT_OFFSET(cfs_log_msg_request_header_t, data);
+               int msg_len = datasize - rh->ident_len - rh->tag_len;
+
+               if (ctx->read_only) {
+                       result = -EPERM;
+               } else if (msg_len  < 1) {
+                       result = -EINVAL;
+               } else {
+                       char *msg = rh->data;
+                       if ((msg[rh->ident_len - 1] == 0) &&
+                           (msg[rh->ident_len + rh->tag_len - 1] == 0) &&
+                           (((char *)data)[request_size] == 0)) {
+
+                               char *ident = msg;
+                               char *tag = msg + rh->ident_len;
+                               msg = msg + rh->ident_len + rh->tag_len;
+
+                               time_t ctime = time(NULL);
+                               clog_entry_t *entry = (clog_entry_t *)alloca(CLOG_MAX_ENTRY_SIZE);
+                               if (clog_pack(entry, cfs.nodename, ident, tag, ctx->client_pid,
+                                             ctime, rh->priority, msg)) {
+                                       cfs_cluster_log(entry);
+                               }
+
+                               result = 0;
+
+                       } else {
+                               result = -EINVAL;
+                       }
+               }
+       } else if (request_id == CFS_IPC_GET_CLUSTER_LOG) {
+
+               cfs_log_get_request_header_t *rh = 
+                       (cfs_log_get_request_header_t *)data;
+
+               int userlen = request_size - sizeof(cfs_log_get_request_header_t);
+
+               if (userlen <= 0) {
+                       result = -EINVAL;
+               } else {
+                       /* make sure user string is 0 terminated */
+                       ((char *)data)[request_size - 1] = 0;
+                       char *user = (char*) data + sizeof(cfs_log_get_request_header_t);
+
+                       uint32_t max = rh->max_entries ?  rh->max_entries : 50;
+                       cfs_cluster_log_dump(outbuf, user, max);
+                       result = 0;
+               }
+       } else if (request_id == CFS_IPC_GET_RRD_DUMP) {
+       
+               if (request_size != sizeof(struct qb_ipc_request_header)) {
+                       result = -EINVAL;
+               } else {
+                       cfs_rrd_dump(outbuf);
+                       result = 0;
+               }
+       } else if (request_id == CFS_IPC_GET_GUEST_CONFIG_PROPERTY) {
+
+               cfs_guest_config_propery_get_request_header_t *rh =
+                       (cfs_guest_config_propery_get_request_header_t *) data;
+
+               int proplen = request_size - G_STRUCT_OFFSET(cfs_guest_config_propery_get_request_header_t, property);
+
+               result = 0;
+               if (rh->vmid < 100 && rh->vmid != 0) {
+                       cfs_debug("vmid out of range %u", rh->vmid);
+                       result = -EINVAL;
+               } else if (rh->vmid >= 100 && !vmlist_vm_exists(rh->vmid)) {
+                       result = -ENOENT;
+               } else if (proplen <= 0) {
+                       cfs_debug("proplen <= 0, %d", proplen);
+                       result = -EINVAL;
+               } else {
+                       ((char *)data)[request_size - 1] = 0; // ensure property is 0 terminated
+
+                       cfs_debug("cfs_get_guest_config_property: basic valid checked, do request");
+
+                       result = cfs_create_guest_conf_property_msg(outbuf, memdb, rh->property, rh->vmid);
+               }
+       } else if (request_id == CFS_IPC_GET_GUEST_CONFIG_PROPERTIES) {
+
+               cfs_guest_config_properties_get_request_header_t *rh =
+                       (cfs_guest_config_properties_get_request_header_t *) data;
+
+               size_t remaining = request_size - G_STRUCT_OFFSET(cfs_guest_config_properties_get_request_header_t, props);
+
+               result = 0;
+               if (rh->vmid < 100 && rh->vmid != 0) {
+                       cfs_debug("vmid out of range %u", rh->vmid);
+                       result = -EINVAL;
+               } else if (rh->vmid >= 100 && !vmlist_vm_exists(rh->vmid)) {
+                       result = -ENOENT;
+               } else if (rh->num_props == 0) {
+                       cfs_debug("num_props == 0");
+                       result = -EINVAL;
+               } else if (remaining <= 1) {
+                       cfs_debug("property length <= 1, %ld", remaining);
+                       result = -EINVAL;
+               } else {
+                       const char **properties = malloc(sizeof(char*) * rh->num_props);
+                       char *current = (rh->props);
+                       for (uint8_t i = 0; i < rh->num_props; i++) {
+                           size_t proplen = strnlen(current, remaining);
+                           if (proplen == 0) {
+                               cfs_debug("property length 0");
+                               result = -EINVAL;
+                               break;
+                           }
+                           if (proplen == remaining || current[proplen] != '\0') {
+                               cfs_debug("property not \\0 terminated");
+                               result = -EINVAL;
+                               break;
+                           }
+                           if (current[0] < 'a' || current[0] > 'z') {
+                               cfs_debug("property does not start with [a-z]");
+                               result = -EINVAL;
+                               break;
+                           }
+                           properties[i] = current;
+                           remaining -= (proplen + 1);
+                           current += proplen + 1;
+                       }
+
+                       if (remaining != 0) {
+                           cfs_debug("leftover data after parsing %u properties", rh->num_props);
+                           result = -EINVAL;
+                       }
+
+                       if (result == 0) {
+                           cfs_debug("cfs_get_guest_config_properties: basic validity checked, do request");
+                           result = cfs_create_guest_conf_properties_msg(outbuf, memdb, properties, rh->num_props, rh->vmid);
+                       }
+
+                       free(properties);
+               }
+       } else if (request_id == CFS_IPC_VERIFY_TOKEN) {
+
+               cfs_verify_token_request_header_t *rh = (cfs_verify_token_request_header_t *) data;
+               int tokenlen = request_size - G_STRUCT_OFFSET(cfs_verify_token_request_header_t, token) - 1;
+
+               if (tokenlen <= 0) {
+                       cfs_debug("tokenlen <= 0, %d", tokenlen);
+                       result = -EINVAL;
+               } else if (memchr(rh->token, '\n', tokenlen) != NULL) {
+                       cfs_debug("token contains newline");
+                       result = -EINVAL;
+               } else if (rh->token[tokenlen] != '\0') {
+                       cfs_debug("token not NULL-terminated");
+                       result = -EINVAL;
+               } else if (strnlen(rh->token, tokenlen) != tokenlen) {
+                       cfs_debug("token contains NULL-byte");
+                       result = -EINVAL;
+               } else {
+                       cfs_debug("cfs_verify_token: basic validity checked, reading token.cfg");
+                       gpointer tmp = NULL;
+                       int bytes_read = memdb_read(memdb, "priv/token.cfg", &tmp);
+                       size_t remaining = bytes_read > 0 ? bytes_read : 0;
+                       if (tmp != NULL && remaining >= tokenlen) {
+                               const char *line = (char *) tmp;
+                               const char *next_line;
+                               const char *const end = line + remaining;
+                               size_t linelen;
+
+                               while (line != NULL) {
+                                       next_line = memchr(line, '\n', remaining);
+                                       linelen = next_line == NULL ? remaining : next_line - line;
+                                       if (linelen == tokenlen && strncmp(line, rh->token, linelen) == 0) {
+                                               result = 0;
+                                               break;
+                                       }
+                                       line = next_line;
+                                       if (line != NULL) {
+                                               line += 1;
+                                               remaining = end - line;
+                                       }
+                               }
+                               if (line == NULL) {
+                                       result = -ENOENT;
+                               }
+                               g_free(tmp);
+                       } else {
+                               cfs_debug("token: token.cfg does not exist - ENOENT");
+                               result = -ENOENT;
+                       }
+               }
+       }
+
+       cfs_debug("process result %d", result);
+
+       if (result >= 0) {
+               resp = outbuf->str;
+               result = 0;
+       }
+
+       int iov_len = 2;
+       struct iovec iov[iov_len];
+       struct qb_ipc_response_header res_header;
+
+       int resp_data_len = resp ? outbuf->len : 0;
+
+       res_header.id = request_id;
+       res_header.size = sizeof(res_header) + resp_data_len;
+       res_header.error = result;
+
+       iov[0].iov_base = (char *)&res_header;
+       iov[0].iov_len = sizeof(res_header);
+       iov[1].iov_base = resp;
+       iov[1].iov_len = resp_data_len;
+
+       ssize_t res = qb_ipcs_response_sendv(c, iov, iov_len);
+       if (res < 0) {
+               cfs_critical("qb_ipcs_response_send: %s", strerror(errno));
+               qb_ipcs_disconnect(c);
+       }
+
+       return 0;
+}
+
+static int32_t my_job_add(
+       enum qb_loop_priority p, 
+       void *data, 
+       qb_loop_job_dispatch_fn fn)
+{
+       return qb_loop_job_add(loop, p, data, fn);
+}
+
+static int32_t my_dispatch_add(
+       enum qb_loop_priority p, 
+       int32_t fd, 
+       int32_t evts,
+       void *data, 
+       qb_ipcs_dispatch_fn_t fn)
+{
+       return qb_loop_poll_add(loop, p, fd, evts, data, fn);
+}
+
+static int32_t my_dispatch_mod(
+       enum qb_loop_priority p, 
+       int32_t fd, 
+       int32_t evts,
+       void *data, 
+       qb_ipcs_dispatch_fn_t fn)
+{
+       return qb_loop_poll_mod(loop, p, fd, evts, data, fn);
+}
+
+static int32_t my_dispatch_del(
+       int32_t fd)
+{
+       return qb_loop_poll_del(loop, fd);
+}
+
+static struct qb_ipcs_service_handlers service_handlers = {
+       .connection_accept = s1_connection_accept_fn,
+       .connection_created = s1_connection_created_fn,
+       .msg_process = s1_msg_process_fn,
+       .connection_destroyed = s1_connection_destroyed_fn,
+       .connection_closed = s1_connection_closed_fn,
+};
+
+static struct qb_ipcs_poll_handlers poll_handlers = {
+       .job_add = my_job_add,
+       .dispatch_add = my_dispatch_add,
+       .dispatch_mod = my_dispatch_mod,
+       .dispatch_del = my_dispatch_del,
+};
+
+static void timer_job(void *data)
+{
+       gboolean terminate = FALSE;
+
+       g_mutex_lock (&server_started_mutex);
+
+       if (terminate_server) {
+               cfs_debug ("got terminate request");
+
+               if (loop)
+                       qb_loop_stop (loop);
+               
+               if (s1) {
+                       qb_ipcs_destroy (s1);
+                       s1 = 0;
+               }
+               server_started = 0;
+
+               g_cond_signal (&server_stopped_cond);
+               
+               terminate = TRUE;
+       } else if (!server_started) {
+               server_started = 1;
+               g_cond_signal (&server_started_cond);
+       }
+       
+       g_mutex_unlock (&server_started_mutex);
+
+       if (terminate)
+               return;
+                              
+       qb_loop_timer_handle th;
+       qb_loop_timer_add(loop, QB_LOOP_LOW, 1000000000, NULL, timer_job, &th);
+}
+
+static gpointer worker_thread(gpointer data)
+{
+       g_return_val_if_fail(loop != NULL, NULL);
+
+       cfs_debug("start event loop");
+
+       qb_ipcs_run(s1);
+
+       qb_loop_timer_handle th;
+       qb_loop_timer_add(loop, QB_LOOP_LOW, 1000, NULL, timer_job, &th);
+
+       qb_loop_run(loop);
+
+       cfs_debug("event loop finished - exit worker thread");
+       
+       return NULL;
+}
+
+gboolean server_start(memdb_t *db)
+{
+       g_return_val_if_fail(loop == NULL, FALSE);
+       g_return_val_if_fail(worker == NULL, FALSE);
+       g_return_val_if_fail(db != NULL, FALSE);
+
+       terminate_server = 0;
+       server_started = 0;
+       
+       memdb = db;
+
+       outbuf = g_string_sized_new(8192*8);
+
+       if (!(loop = qb_loop_create())) {
+               cfs_critical("cant create event loop");
+               return FALSE;
+       }
+       
+       s1 = qb_ipcs_create("pve2", 1, QB_IPC_SHM, &service_handlers);
+       if (s1 == 0) {
+               cfs_critical("qb_ipcs_create failed: %s", strerror(errno));
+               return FALSE;
+       }
+       qb_ipcs_poll_handlers_set(s1, &poll_handlers);
+
+       worker = g_thread_new ("server", worker_thread, NULL);
+
+       g_mutex_lock (&server_started_mutex);
+       while (!server_started)
+               g_cond_wait (&server_started_cond, &server_started_mutex);
+       g_mutex_unlock (&server_started_mutex);
+       
+       cfs_debug("server started");
+       
+       return TRUE;
+}
+
+void server_stop(void)
+{
+       cfs_debug("server stop");
+
+       g_mutex_lock (&server_started_mutex);
+       terminate_server = 1;
+       while (server_started)
+               g_cond_wait (&server_stopped_cond, &server_started_mutex);
+       g_mutex_unlock (&server_started_mutex);
+
+       if (worker) {
+               g_thread_join(worker);
+               worker = NULL;
+       }
+       
+       cfs_debug("worker thread finished");
+
+       if (loop) {
+               qb_loop_destroy(loop);
+
+               loop = NULL;
+       }
+
+       if (outbuf) {
+               g_string_free(outbuf, TRUE);
+               outbuf = NULL;
+       }
+}
diff --git a/src/pmxcfs/server.h b/src/pmxcfs/server.h
new file mode 100644 (file)
index 0000000..de33fbd
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_IPCS_H_
+#define _PVE_IPCS_H_
+
+
+gboolean server_start(memdb_t *memdb);
+void server_stop(void);
+
+
+#endif /* _PVE_IPCS_H_ */
diff --git a/src/pmxcfs/status.c b/src/pmxcfs/status.c
new file mode 100644 (file)
index 0000000..8d62986
--- /dev/null
@@ -0,0 +1,1980 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define G_LOG_DOMAIN "status"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <glib.h>
+#include <sys/syslog.h>
+#include <rrd.h>
+#include <rrd_client.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "cfs-utils.h"
+#include "status.h"
+#include "memdb.h"
+#include "logger.h"
+
+#define KVSTORE_CPG_GROUP_NAME "pve_kvstore_v1"
+
+typedef enum {
+       KVSTORE_MESSAGE_UPDATE = 1,
+       KVSTORE_MESSAGE_UPDATE_COMPLETE = 2,
+       KVSTORE_MESSAGE_LOG = 3,
+} kvstore_message_t;
+
+static uint32_t vminfo_version_counter;
+
+typedef struct {
+       uint32_t vmid;
+       char *nodename;
+       int vmtype;
+       uint32_t version;
+} vminfo_t;
+
+typedef struct {
+       char *key;
+       gpointer data;
+       size_t len;
+       uint32_t version;
+} kventry_t;
+
+typedef struct {
+       char *key;
+       gpointer data;
+       size_t len;
+       uint32_t time;
+} rrdentry_t;
+
+typedef struct {
+       char *path;
+       uint32_t version;
+} memdb_change_t;
+
+static memdb_change_t memdb_change_array[] = {
+       { .path = "corosync.conf" },
+       { .path = "corosync.conf.new" },
+       { .path = "storage.cfg" },
+       { .path = "user.cfg" },
+       { .path = "domains.cfg" },
+       { .path = "priv/shadow.cfg" },
+       { .path = "priv/acme/plugins.cfg" },
+       { .path = "priv/tfa.cfg" },
+       { .path = "priv/token.cfg" },
+       { .path = "priv/ipam.db" },
+       { .path = "datacenter.cfg" },
+       { .path = "vzdump.cron" },
+       { .path = "vzdump.conf" },
+       { .path = "jobs.cfg" },
+       { .path = "ha/crm_commands" },
+       { .path = "ha/manager_status" },
+       { .path = "ha/resources.cfg" },
+       { .path = "ha/groups.cfg" },
+       { .path = "ha/fence.cfg" },
+       { .path = "status.cfg" },
+       { .path = "replication.cfg" },
+       { .path = "ceph.conf" },
+       { .path = "sdn/vnets.cfg" },
+       { .path = "sdn/zones.cfg" },
+       { .path = "sdn/controllers.cfg" },
+       { .path = "sdn/subnets.cfg" },
+       { .path = "sdn/ipams.cfg" },
+       { .path = "sdn/dns.cfg" },
+       { .path = "sdn/.running-config" },
+       { .path = "virtual-guest/cpu-models.conf" },
+       { .path = "firewall/cluster.fw" },
+};
+
+static GMutex mutex;
+
+typedef struct {
+       time_t start_time;
+
+       uint32_t quorate;
+
+       cfs_clinfo_t *clinfo;
+       uint32_t clinfo_version;
+
+       GHashTable *vmlist;
+       uint32_t vmlist_version;
+
+       dfsm_t *kvstore;
+       GHashTable *kvhash;
+       GHashTable *rrdhash;
+       GHashTable *iphash;
+
+       GHashTable *memdb_changes;
+
+       clusterlog_t *clusterlog;
+} cfs_status_t;
+
+static cfs_status_t cfs_status;
+
+struct cfs_clnode {
+       char *name;
+       uint32_t nodeid;
+       uint32_t votes;
+       gboolean online;
+       GHashTable *kvhash;
+};
+
+struct cfs_clinfo {
+       char *cluster_name;
+       uint32_t cman_version;
+
+       GHashTable *nodes_byid;
+       GHashTable *nodes_byname;
+};
+
+static guint
+g_int32_hash (gconstpointer v)
+{
+       return *(const uint32_t *) v;
+}
+
+static gboolean
+g_int32_equal (gconstpointer v1,
+              gconstpointer v2)
+{
+       return *((const uint32_t*) v1) == *((const uint32_t*) v2);
+}
+
+static void vminfo_free(vminfo_t *vminfo)
+{
+       g_return_if_fail(vminfo != NULL);
+
+       if (vminfo->nodename)
+               g_free(vminfo->nodename);
+
+
+       g_free(vminfo);
+}
+
+static const char *vminfo_type_to_string(vminfo_t *vminfo)
+{
+       if (vminfo->vmtype == VMTYPE_QEMU) {
+               return "qemu";
+       } else if (vminfo->vmtype == VMTYPE_OPENVZ) {
+               // FIXME: remove openvz stuff for 7.x
+               return "openvz";
+       } else if (vminfo->vmtype == VMTYPE_LXC) {
+               return "lxc";
+       } else {
+               return "unknown";
+       }
+}
+
+static const char *vminfo_type_to_path_type(vminfo_t *vminfo)
+{
+       if (vminfo->vmtype == VMTYPE_QEMU) {
+               return "qemu-server"; // special case..
+       } else {
+               return vminfo_type_to_string(vminfo);
+       }
+}
+
+int vminfo_to_path(vminfo_t *vminfo, GString *path)
+{
+       g_return_val_if_fail(vminfo != NULL, -1);
+       g_return_val_if_fail(path != NULL, -1);
+
+       if (!vminfo->nodename)
+               return 0;
+
+       const char *type = vminfo_type_to_path_type(vminfo);
+       g_string_printf(path, "/nodes/%s/%s/%u.conf", vminfo->nodename, type, vminfo->vmid);
+
+       return 1;
+}
+
+void cfs_clnode_destroy(
+       cfs_clnode_t *clnode)
+{
+       g_return_if_fail(clnode != NULL);
+
+       if (clnode->kvhash)
+               g_hash_table_destroy(clnode->kvhash);
+
+       if (clnode->name)
+               g_free(clnode->name);
+
+       g_free(clnode);
+}
+
+cfs_clnode_t *cfs_clnode_new(
+       const char *name,
+       uint32_t nodeid,
+       uint32_t votes)
+{
+       g_return_val_if_fail(name != NULL, NULL);
+
+       cfs_clnode_t *clnode = g_new0(cfs_clnode_t, 1);
+       if (!clnode)
+               return NULL;
+
+       clnode->name = g_strdup(name);
+       clnode->nodeid = nodeid;
+       clnode->votes = votes;
+
+       return clnode;
+}
+
+gboolean cfs_clinfo_destroy(
+       cfs_clinfo_t *clinfo)
+{
+       g_return_val_if_fail(clinfo != NULL, FALSE);
+
+       if (clinfo->cluster_name)
+               g_free(clinfo->cluster_name);
+
+       if (clinfo->nodes_byname)
+               g_hash_table_destroy(clinfo->nodes_byname);
+
+       if (clinfo->nodes_byid)
+               g_hash_table_destroy(clinfo->nodes_byid);
+
+       g_free(clinfo);
+
+       return TRUE;
+}
+
+cfs_clinfo_t *cfs_clinfo_new(
+       const char *cluster_name,
+       uint32_t cman_version)
+{
+       g_return_val_if_fail(cluster_name != NULL, NULL);
+
+       cfs_clinfo_t *clinfo = g_new0(cfs_clinfo_t, 1);
+       if (!clinfo)
+               return NULL;
+
+       clinfo->cluster_name = g_strdup(cluster_name);
+       clinfo->cman_version = cman_version;
+
+       if (!(clinfo->nodes_byid = g_hash_table_new_full(
+                     g_int32_hash, g_int32_equal, NULL,
+                     (GDestroyNotify)cfs_clnode_destroy)))
+               goto fail;
+
+       if (!(clinfo->nodes_byname = g_hash_table_new(g_str_hash, g_str_equal)))
+               goto fail;
+
+       return clinfo;
+
+fail:
+       cfs_clinfo_destroy(clinfo);
+
+       return NULL;
+}
+
+gboolean cfs_clinfo_add_node(
+       cfs_clinfo_t *clinfo,
+       cfs_clnode_t *clnode)
+{
+       g_return_val_if_fail(clinfo != NULL, FALSE);
+       g_return_val_if_fail(clnode != NULL, FALSE);
+
+       g_hash_table_replace(clinfo->nodes_byid, &clnode->nodeid, clnode);
+       g_hash_table_replace(clinfo->nodes_byname, clnode->name, clnode);
+
+       return TRUE;
+}
+
+int
+cfs_create_memberlist_msg(
+       GString *str)
+{
+       g_return_val_if_fail(str != NULL, -EINVAL);
+
+       g_mutex_lock (&mutex);
+
+       g_string_append_printf(str,"{\n");
+
+       guint nodecount = 0;
+
+       cfs_clinfo_t *clinfo = cfs_status.clinfo;
+
+       if (clinfo && clinfo->nodes_byid)
+               nodecount = g_hash_table_size(clinfo->nodes_byid);
+
+       if (nodecount) {
+               g_string_append_printf(str, "\"nodename\": \"%s\",\n", cfs.nodename);
+               g_string_append_printf(str, "\"version\": %u,\n", cfs_status.clinfo_version);
+
+               g_string_append_printf(str, "\"cluster\": { ");
+               g_string_append_printf(str, "\"name\": \"%s\", \"version\": %d, "
+                                      "\"nodes\": %d, \"quorate\": %d ",
+                                      clinfo->cluster_name, clinfo->cman_version,
+                                      nodecount, cfs_status.quorate);
+
+               g_string_append_printf(str,"},\n");
+               g_string_append_printf(str,"\"nodelist\": {\n");
+
+               GHashTable *ht = clinfo->nodes_byid;
+               GHashTableIter iter;
+               gpointer key, value;
+
+               g_hash_table_iter_init (&iter, ht);
+
+               int i = 0;
+               while (g_hash_table_iter_next (&iter, &key, &value)) {
+                       cfs_clnode_t *node = (cfs_clnode_t *)value;
+                       if (i) g_string_append_printf(str, ",\n");
+                       i++;
+
+                       g_string_append_printf(str, "  \"%s\": { \"id\": %d, \"online\": %d",
+                                              node->name, node->nodeid, node->online);
+
+
+                       char *ip = (char *)g_hash_table_lookup(cfs_status.iphash, node->name);
+                       if (ip) {
+                               g_string_append_printf(str, ", \"ip\": \"%s\"", ip);
+                       }
+
+                       g_string_append_printf(str, "}");
+
+               }
+               g_string_append_printf(str,"\n  }\n");
+       } else {
+               g_string_append_printf(str, "\"nodename\": \"%s\",\n", cfs.nodename);
+               g_string_append_printf(str, "\"version\": %u\n", cfs_status.clinfo_version);
+       }
+
+       g_string_append_printf(str,"}\n");
+
+       g_mutex_unlock (&mutex);
+
+       return 0;
+}
+
+static void
+kventry_free(kventry_t *entry)
+{
+       g_return_if_fail(entry != NULL);
+
+       g_free(entry->key);
+       g_free(entry->data);
+       g_free(entry);
+}
+
+static GHashTable *
+kventry_hash_new(void)
+{
+       return g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+                                    (GDestroyNotify)kventry_free);
+}
+
+static void
+rrdentry_free(rrdentry_t *entry)
+{
+       g_return_if_fail(entry != NULL);
+
+       g_free(entry->key);
+       g_free(entry->data);
+       g_free(entry);
+}
+
+static GHashTable *
+rrdentry_hash_new(void)
+{
+       return g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+                                    (GDestroyNotify)rrdentry_free);
+}
+
+void
+cfs_cluster_log_dump(GString *str, const char *user, guint max_entries)
+{
+       clusterlog_dump(cfs_status.clusterlog, str, user, max_entries);
+}
+
+void
+cfs_cluster_log(clog_entry_t *entry)
+{
+       g_return_if_fail(entry != NULL);
+
+       clusterlog_insert(cfs_status.clusterlog, entry);
+
+       if (cfs_status.kvstore) {
+               struct iovec iov[1];
+               iov[0].iov_base = (char *)entry;
+               iov[0].iov_len = clog_entry_size(entry);
+
+               if (dfsm_is_initialized(cfs_status.kvstore))
+                       dfsm_send_message(cfs_status.kvstore, KVSTORE_MESSAGE_LOG, iov, 1);
+       }
+}
+
+void cfs_status_init(void)
+{
+       g_mutex_lock (&mutex);
+
+       cfs_status.start_time = time(NULL);
+
+       cfs_status.vmlist = vmlist_hash_new();
+
+       cfs_status.kvhash = kventry_hash_new();
+
+       cfs_status.rrdhash = rrdentry_hash_new();
+
+       cfs_status.iphash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+       cfs_status.memdb_changes = g_hash_table_new(g_str_hash, g_str_equal);
+
+       for (int i = 0; i < G_N_ELEMENTS(memdb_change_array); i++) {
+               g_hash_table_replace(cfs_status.memdb_changes,
+                                    memdb_change_array[i].path,
+                                    &memdb_change_array[i]);
+       }
+
+       cfs_status.clusterlog = clusterlog_new();
+
+       // fixme:
+       clusterlog_add(cfs_status.clusterlog, "root", "cluster", getpid(),
+                      LOG_INFO, "starting cluster log");
+
+       g_mutex_unlock (&mutex);
+}
+
+void cfs_status_cleanup(void)
+{
+       g_mutex_lock (&mutex);
+
+       cfs_status.clinfo_version++;
+
+       if (cfs_status.clinfo) {
+               cfs_clinfo_destroy(cfs_status.clinfo);
+               cfs_status.clinfo = NULL;
+       }
+
+       if (cfs_status.vmlist) {
+               g_hash_table_destroy(cfs_status.vmlist);
+               cfs_status.vmlist = NULL;
+       }
+
+       if (cfs_status.kvhash) {
+               g_hash_table_destroy(cfs_status.kvhash);
+               cfs_status.kvhash = NULL;
+       }
+
+       if (cfs_status.rrdhash) {
+               g_hash_table_destroy(cfs_status.rrdhash);
+               cfs_status.rrdhash = NULL;
+       }
+
+       if (cfs_status.iphash) {
+               g_hash_table_destroy(cfs_status.iphash);
+               cfs_status.iphash = NULL;
+       }
+
+       if (cfs_status.clusterlog)
+               clusterlog_destroy(cfs_status.clusterlog);
+
+       g_mutex_unlock (&mutex);
+}
+
+void cfs_status_set_clinfo(
+       cfs_clinfo_t *clinfo)
+{
+       g_return_if_fail(clinfo != NULL);
+
+       g_mutex_lock (&mutex);
+
+       cfs_status.clinfo_version++;
+
+       cfs_clinfo_t *old = cfs_status.clinfo;
+
+       cfs_status.clinfo = clinfo;
+
+       cfs_message("update cluster info (cluster name  %s, version = %d)",
+                   clinfo->cluster_name, clinfo->cman_version);
+
+
+       if (old && old->nodes_byid && clinfo->nodes_byid) {
+               /* copy kvstore */
+               GHashTable *ht = clinfo->nodes_byid;
+               GHashTableIter iter;
+               gpointer key, value;
+
+               g_hash_table_iter_init (&iter, ht);
+
+               while (g_hash_table_iter_next (&iter, &key, &value)) {
+                       cfs_clnode_t *node = (cfs_clnode_t *)value;
+                       cfs_clnode_t *oldnode;
+                       if ((oldnode = g_hash_table_lookup(old->nodes_byid, key))) {
+                               node->online = oldnode->online;
+                               node->kvhash = oldnode->kvhash;
+                               oldnode->kvhash = NULL;
+                       }
+               }
+
+       }
+
+       if (old)
+               cfs_clinfo_destroy(old);
+
+
+       g_mutex_unlock (&mutex);
+}
+
+static void
+dump_kvstore_versions(
+       GString *str,
+       GHashTable *kvhash,
+       const char *nodename)
+{
+       g_return_if_fail(kvhash != NULL);
+       g_return_if_fail(str != NULL);
+       g_return_if_fail(nodename != NULL);
+
+       GHashTable *ht = kvhash;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_string_append_printf(str, "\"%s\": {\n", nodename);
+
+       g_hash_table_iter_init (&iter, ht);
+
+       int i = 0;
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               kventry_t *entry = (kventry_t *)value;
+               if (i) g_string_append_printf(str, ",\n");
+               i++;
+               g_string_append_printf(str,"\"%s\": %u", entry->key, entry->version);
+       }
+
+       g_string_append_printf(str, "}\n");
+}
+
+int
+cfs_create_version_msg(GString *str)
+{
+       g_return_val_if_fail(str != NULL, -EINVAL);
+
+       g_mutex_lock (&mutex);
+
+       g_string_append_printf(str,"{\n");
+
+       g_string_append_printf(str, "\"starttime\": %lu,\n", (unsigned long)cfs_status.start_time);
+
+       g_string_append_printf(str, "\"clinfo\": %u,\n", cfs_status.clinfo_version);
+
+       g_string_append_printf(str, "\"vmlist\": %u,\n", cfs_status.vmlist_version);
+
+       for (int i = 0; i < G_N_ELEMENTS(memdb_change_array); i++) {
+               g_string_append_printf(str, "\"%s\": %u,\n",
+                                      memdb_change_array[i].path,
+                                      memdb_change_array[i].version);
+       }
+
+       g_string_append_printf(str, "\"kvstore\": {\n");
+
+       dump_kvstore_versions(str, cfs_status.kvhash, cfs.nodename);
+
+       cfs_clinfo_t *clinfo = cfs_status.clinfo;
+
+       if (clinfo && clinfo->nodes_byid) {
+               GHashTable *ht = clinfo->nodes_byid;
+               GHashTableIter iter;
+               gpointer key, value;
+
+               g_hash_table_iter_init (&iter, ht);
+
+               while (g_hash_table_iter_next (&iter, &key, &value)) {
+                       cfs_clnode_t *node = (cfs_clnode_t *)value;
+                       if (!node->kvhash)
+                               continue;
+                       g_string_append_printf(str, ",\n");
+                       dump_kvstore_versions(str, node->kvhash, node->name);
+               }
+       }
+
+       g_string_append_printf(str,"}\n");
+
+       g_string_append_printf(str,"}\n");
+
+       g_mutex_unlock (&mutex);
+
+       return 0;
+}
+
+GHashTable *
+vmlist_hash_new(void)
+{
+       return g_hash_table_new_full(g_int_hash, g_int_equal, NULL,
+                                    (GDestroyNotify)vminfo_free);
+}
+
+gboolean
+vmlist_hash_insert_vm(
+       GHashTable *vmlist,
+       int vmtype,
+       guint32 vmid,
+       const char *nodename,
+       gboolean replace)
+{
+       g_return_val_if_fail(vmlist != NULL, FALSE);
+       g_return_val_if_fail(nodename != NULL, FALSE);
+       g_return_val_if_fail(vmid != 0, FALSE);
+       // FIXME: remove openvz stuff for 7.x
+       g_return_val_if_fail(vmtype == VMTYPE_QEMU || vmtype == VMTYPE_OPENVZ ||
+                            vmtype == VMTYPE_LXC, FALSE);
+
+       if (!replace && g_hash_table_lookup(vmlist, &vmid)) {
+               cfs_critical("detected duplicate VMID %d", vmid);
+               return FALSE;
+       }
+
+       vminfo_t *vminfo = g_new0(vminfo_t, 1);
+
+       vminfo->vmid = vmid;
+       vminfo->vmtype = vmtype;
+       vminfo->nodename = g_strdup(nodename);
+
+       vminfo->version = ++vminfo_version_counter;
+
+       g_hash_table_replace(vmlist, &vminfo->vmid, vminfo);
+
+       return TRUE;
+}
+
+void
+vmlist_register_vm(
+       int vmtype,
+       guint32 vmid,
+       const char *nodename)
+{
+       g_return_if_fail(cfs_status.vmlist != NULL);
+       g_return_if_fail(nodename != NULL);
+       g_return_if_fail(vmid != 0);
+       // FIXME: remove openvz stuff for 7.x
+       g_return_if_fail(vmtype == VMTYPE_QEMU || vmtype == VMTYPE_OPENVZ ||
+                        vmtype == VMTYPE_LXC);
+
+       cfs_debug("vmlist_register_vm: %s/%u %d", nodename, vmid, vmtype);
+
+       g_mutex_lock (&mutex);
+
+       cfs_status.vmlist_version++;
+
+       vmlist_hash_insert_vm(cfs_status.vmlist, vmtype, vmid, nodename, TRUE);
+
+       g_mutex_unlock (&mutex);
+}
+
+gboolean
+vmlist_different_vm_exists(
+       int vmtype,
+       guint32 vmid,
+       const char *nodename)
+{
+       g_return_val_if_fail(cfs_status.vmlist != NULL, FALSE);
+       g_return_val_if_fail(vmid != 0, FALSE);
+
+       gboolean res = FALSE;
+
+       g_mutex_lock (&mutex);
+
+       vminfo_t *vminfo;
+       if ((vminfo = (vminfo_t *)g_hash_table_lookup(cfs_status.vmlist, &vmid))) {
+               if (!(vminfo->vmtype == vmtype && strcmp(vminfo->nodename, nodename) == 0))
+                       res = TRUE;
+       }
+       g_mutex_unlock (&mutex);
+
+       return res;
+}
+
+gboolean
+vmlist_vm_exists(
+       guint32 vmid)
+{
+       g_return_val_if_fail(cfs_status.vmlist != NULL, FALSE);
+       g_return_val_if_fail(vmid != 0, FALSE);
+
+       g_mutex_lock (&mutex);
+
+       gpointer res = g_hash_table_lookup(cfs_status.vmlist, &vmid);
+
+       g_mutex_unlock (&mutex);
+
+       return res != NULL;
+}
+
+void
+vmlist_delete_vm(
+       guint32 vmid)
+{
+       g_return_if_fail(cfs_status.vmlist != NULL);
+       g_return_if_fail(vmid != 0);
+
+       g_mutex_lock (&mutex);
+
+       cfs_status.vmlist_version++;
+
+       g_hash_table_remove(cfs_status.vmlist, &vmid);
+
+       g_mutex_unlock (&mutex);
+}
+
+void cfs_status_set_vmlist(
+       GHashTable *vmlist)
+{
+       g_return_if_fail(vmlist != NULL);
+
+       g_mutex_lock (&mutex);
+
+       cfs_status.vmlist_version++;
+
+       if (cfs_status.vmlist)
+               g_hash_table_destroy(cfs_status.vmlist);
+
+       cfs_status.vmlist = vmlist;
+
+       g_mutex_unlock (&mutex);
+}
+
+int
+cfs_create_vmlist_msg(GString *str)
+{
+       g_return_val_if_fail(cfs_status.vmlist != NULL, -EINVAL);
+       g_return_val_if_fail(str != NULL, -EINVAL);
+
+       g_mutex_lock (&mutex);
+
+       g_string_append_printf(str,"{\n");
+
+       GHashTable *ht = cfs_status.vmlist;
+
+       guint count = g_hash_table_size(ht);
+
+       if (!count) {
+               g_string_append_printf(str,"\"version\": %u\n", cfs_status.vmlist_version);
+       } else {
+               g_string_append_printf(str,"\"version\": %u,\n", cfs_status.vmlist_version);
+
+               g_string_append_printf(str,"\"ids\": {\n");
+
+               GHashTableIter iter;
+               gpointer key, value;
+
+               g_hash_table_iter_init (&iter, ht);
+
+               int first = 1;
+               while (g_hash_table_iter_next (&iter, &key, &value)) {
+                       vminfo_t *vminfo = (vminfo_t *)value;
+                       const char *type = vminfo_type_to_string(vminfo);
+
+                       if (!first)
+                               g_string_append_printf(str, ",\n");
+                       first = 0;
+
+                       g_string_append_printf(str,"\"%u\": { \"node\": \"%s\", \"type\": \"%s\", \"version\": %u }",
+                                              vminfo->vmid, vminfo->nodename, type, vminfo->version);
+               }
+
+               g_string_append_printf(str,"}\n");
+       }
+       g_string_append_printf(str,"\n}\n");
+
+       g_mutex_unlock (&mutex);
+
+       return 0;
+}
+
+// checks if a config line starts with the given prop. if yes, writes a '\0'
+// at the end of the value, and returns the pointer where the value starts
+// note: line[line_end] needs to be guaranteed a null byte
+char *
+_get_property_value_from_line(char *line, size_t line_len, const char *prop, size_t prop_len)
+{
+       if (line_len <= prop_len + 1) return NULL;
+
+       if (line[prop_len] == ':' && memcmp(line, prop, prop_len) == 0) { // found
+               char *v_start = &line[prop_len + 1];
+               char *v_end = &line[line_len - 1];
+
+               // drop initial value whitespaces here already
+               while (v_start < v_end && *v_start && isspace(*v_start)) v_start++;
+
+               if (!*v_start) return NULL;
+
+               while (v_end > v_start && isspace(*v_end)) v_end--;
+               if (v_end < &line[line_len - 1]) {
+                   v_end[1] = '\0';
+               }
+
+               return v_start;
+       }
+
+       return NULL;
+}
+
+// checks the conf for lines starting with the given props and
+// writes the pointers into the correct positions into the 'found' array
+// afterwards, without initial whitespace(s), we only deal with the format
+// restriction imposed by our perl VM config parser, main reference is
+// PVE::QemuServer::parse_vm_config this allows to be very fast and still
+// relatively simple
+// main restrictions used for our advantage is the properties match regex:
+// ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) from parse_vm_config
+// currently we only look at the current configuration in place, i.e., *no*
+// snapshot and *no* pending changes
+//
+// min..max is the char range of the first character of the given props,
+// so that we can return early when checking the line
+// note: conf must end with a newline
+void
+_get_property_values(char **found, char *conf, int conf_size, const char **props, uint8_t num_props, char min, char max)
+{
+       const char *const conf_end = conf + conf_size;
+       char *line = conf;
+       size_t remaining_size = conf_size;
+       uint8_t count = 0;
+
+       if (conf_size == 0) {
+           return;
+       }
+
+       char *next_newline = memchr(conf, '\n', conf_size);
+       if (next_newline == NULL) {
+               return; // valid property lines end with \n, but none in the config
+       }
+       *next_newline = '\0';
+
+       while (line != NULL) {
+               if (!line[0]) goto next;
+
+               // snapshot or pending section start, but nothing found yet -> not found
+               if (line[0] == '[') return;
+               // continue early if line does not begin with the min/max char of the properties
+               if (line[0] < min || line[0] > max) goto next;
+
+               size_t line_len = next_newline - line;
+               for (uint8_t i = 0; i < num_props; i++) {
+                       char * value = _get_property_value_from_line(line, line_len, props[i], strlen(props[i]));
+                       if (value != NULL) {
+                               count += (found[i] != NULL) & 0x1; // count newly found lines
+                               found[i] = value;
+                       }
+               }
+               if (count == num_props) {
+                       return; // found all
+               }
+next:
+               line = next_newline + 1;
+               remaining_size = conf_end - line;
+               next_newline = memchr(line, '\n', remaining_size);
+               if (next_newline == NULL) {
+                       return; // valid property lines end with \n, but none in the config
+               }
+               *next_newline = '\0';
+       }
+
+       return;
+}
+
+static void
+_g_str_append_kv_jsonescaped(GString *str, const char *k, const char *v)
+{
+       g_string_append_printf(str, "\"%s\": \"", k);
+
+       for (; *v; v++) {
+               if (*v == '\\' || *v == '"') {
+                       g_string_append_c(str, '\\');
+               }
+               g_string_append_c(str, *v);
+       }
+
+       g_string_append_c(str, '"');
+}
+
+int
+_print_found_properties(
+       GString *str,
+       gpointer conf,
+       int size,
+       const char **props,
+       uint8_t num_props,
+       uint32_t vmid,
+       char **values,
+       char min,
+       char max,
+       int first)
+{
+       _get_property_values(values, conf, size, props, num_props, min, max);
+
+       uint8_t found = 0;
+       for (uint8_t i = 0; i < num_props; i++) {
+               if (values[i] == NULL) {
+                       continue;
+               }
+               if (found) {
+                       g_string_append_c(str, ',');
+               } else {
+                       if (!first) {
+                               g_string_append_printf(str, ",\n");
+                       } else {
+                               first = 0;
+                       }
+                       g_string_append_printf(str, "\"%u\":{", vmid);
+                       found = 1;
+               }
+               _g_str_append_kv_jsonescaped(str, props[i], values[i]);
+       }
+
+       if (found) {
+               g_string_append_c(str, '}');
+       }
+
+       return first;
+}
+
+int
+cfs_create_guest_conf_properties_msg(GString *str, memdb_t *memdb, const char **props, uint8_t num_props, uint32_t vmid)
+{
+       g_return_val_if_fail(cfs_status.vmlist != NULL, -EINVAL);
+       g_return_val_if_fail(str != NULL, -EINVAL);
+
+       // Prelock &memdb->mutex in order to enable the usage of memdb_read_nolock
+       // to prevent Deadlocks as in #2553
+       g_mutex_lock (&memdb->mutex);
+       g_mutex_lock (&mutex);
+
+       g_string_printf(str, "{\n");
+
+       GHashTable *ht = cfs_status.vmlist;
+
+       int res = 0;
+       GString *path = NULL;
+       gpointer tmp = NULL;
+       char **values = calloc(num_props, sizeof(char*));
+       char min = 'z', max = 'a';
+
+       for (uint8_t i = 0; i < num_props; i++) {
+               if (props[i][0] > max) {
+                       max = props[i][0];
+               }
+
+               if (props[i][0] < min) {
+                       min = props[i][0];
+               }
+       }
+
+       if (!g_hash_table_size(ht)) {
+               goto ret;
+       }
+
+       if ((path = g_string_sized_new(256)) == NULL) {
+               res = -ENOMEM;
+               goto ret;
+       }
+
+       if (vmid >= 100) {
+               vminfo_t *vminfo = (vminfo_t *) g_hash_table_lookup(cfs_status.vmlist, &vmid);
+               if (vminfo == NULL) goto enoent;
+
+               if (!vminfo_to_path(vminfo, path)) goto err;
+
+               // use memdb_read_nolock because lock is handled here
+               int size = memdb_read_nolock(memdb, path->str, &tmp);
+               if (tmp == NULL) goto err;
+
+               // conf needs to be newline terminated
+               if (((char *)tmp)[size - 1] != '\n') {
+                       gpointer new = realloc(tmp, size + 1);
+                       if (new == NULL) goto err;
+                       tmp = new;
+                       ((char *)tmp)[size++] = '\n';
+               }
+               _print_found_properties(str, tmp, size, props, num_props, vmid, values, min, max, 1);
+       } else {
+               GHashTableIter iter;
+               g_hash_table_iter_init (&iter, ht);
+
+               gpointer key, value;
+               int first = 1;
+               while (g_hash_table_iter_next (&iter, &key, &value)) {
+                       vminfo_t *vminfo = (vminfo_t *)value;
+
+                       if (!vminfo_to_path(vminfo, path)) goto err;
+
+                       g_free(tmp); // no-op if already null
+                       tmp = NULL;
+                       // use memdb_read_nolock because lock is handled here
+                       int size = memdb_read_nolock(memdb, path->str, &tmp);
+                       if (tmp == NULL) continue;
+
+                       // conf needs to be newline terminated
+                       if (((char *)tmp)[size - 1] != '\n') {
+                           gpointer new = realloc(tmp, size + 1);
+                           if (new == NULL) continue;
+                           tmp = new;
+                           ((char *)tmp)[size++] = '\n';
+                       }
+
+                       memset(values, 0, sizeof(char*)*num_props); // reset array
+                       first = _print_found_properties(str, tmp, size, props, num_props,
+                                       vminfo->vmid, values, min, max, first);
+               }
+       }
+ret:
+       g_free(tmp);
+       free(values);
+       if (path != NULL) {
+               g_string_free(path, TRUE);
+       }
+       g_string_append_printf(str,"\n}\n");
+       g_mutex_unlock (&mutex);
+       g_mutex_unlock (&memdb->mutex);
+       return res;
+err:
+       res = -EIO;
+       goto ret;
+enoent:
+       res = -ENOENT;
+       goto ret;
+}
+
+int
+cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid)
+{
+       return cfs_create_guest_conf_properties_msg(str, memdb, &prop, 1, vmid);
+}
+
+void
+record_memdb_change(const char *path)
+{
+       g_return_if_fail(cfs_status.memdb_changes != 0);
+
+       memdb_change_t *ce;
+
+       if ((ce = (memdb_change_t *)g_hash_table_lookup(cfs_status.memdb_changes, path))) {
+               ce->version++;
+       }
+}
+
+void
+record_memdb_reload(void)
+{
+       for (int i = 0; i < G_N_ELEMENTS(memdb_change_array); i++) {
+               memdb_change_array[i].version++;
+       }
+}
+
+static gboolean
+kventry_hash_set(
+       GHashTable *kvhash,
+       const char *key,
+       gconstpointer data,
+       size_t len)
+{
+       g_return_val_if_fail(kvhash != NULL, FALSE);
+       g_return_val_if_fail(key != NULL, FALSE);
+       g_return_val_if_fail(data != NULL, FALSE);
+
+       kventry_t *entry;
+       if (!len) {
+               g_hash_table_remove(kvhash, key);
+       } else if ((entry = (kventry_t *)g_hash_table_lookup(kvhash, key))) {
+               g_free(entry->data);
+               entry->data = g_memdup2(data, len);
+               entry->len = len;
+               entry->version++;
+       } else {
+               kventry_t *entry = g_new0(kventry_t, 1);
+
+               entry->key = g_strdup(key);
+               entry->data = g_memdup2(data, len);
+               entry->len = len;
+
+               g_hash_table_replace(kvhash, entry->key, entry);
+       }
+
+       return TRUE;
+}
+
+static const char *rrd_def_node[] = {
+       "DS:loadavg:GAUGE:120:0:U",
+       "DS:maxcpu:GAUGE:120:0:U",
+       "DS:cpu:GAUGE:120:0:U",
+       "DS:iowait:GAUGE:120:0:U",
+       "DS:memtotal:GAUGE:120:0:U",
+       "DS:memused:GAUGE:120:0:U",
+       "DS:swaptotal:GAUGE:120:0:U",
+       "DS:swapused:GAUGE:120:0:U",
+       "DS:roottotal:GAUGE:120:0:U",
+       "DS:rootused:GAUGE:120:0:U",
+       "DS:netin:DERIVE:120:0:U",
+       "DS:netout:DERIVE:120:0:U",
+
+       "RRA:AVERAGE:0.5:1:70", // 1 min avg - one hour
+       "RRA:AVERAGE:0.5:30:70", // 30 min avg - one day
+       "RRA:AVERAGE:0.5:180:70", // 3 hour avg - one week
+       "RRA:AVERAGE:0.5:720:70", // 12 hour avg - one month
+       "RRA:AVERAGE:0.5:10080:70", // 7 day avg - ony year
+
+       "RRA:MAX:0.5:1:70", // 1 min max - one hour
+       "RRA:MAX:0.5:30:70", // 30 min max - one day
+       "RRA:MAX:0.5:180:70",  // 3 hour max - one week
+       "RRA:MAX:0.5:720:70", // 12 hour max - one month
+       "RRA:MAX:0.5:10080:70", // 7 day max - ony year
+       NULL,
+};
+
+static const char *rrd_def_vm[] = {
+       "DS:maxcpu:GAUGE:120:0:U",
+       "DS:cpu:GAUGE:120:0:U",
+       "DS:maxmem:GAUGE:120:0:U",
+       "DS:mem:GAUGE:120:0:U",
+       "DS:maxdisk:GAUGE:120:0:U",
+       "DS:disk:GAUGE:120:0:U",
+       "DS:netin:DERIVE:120:0:U",
+       "DS:netout:DERIVE:120:0:U",
+       "DS:diskread:DERIVE:120:0:U",
+       "DS:diskwrite:DERIVE:120:0:U",
+
+       "RRA:AVERAGE:0.5:1:70", // 1 min avg - one hour
+       "RRA:AVERAGE:0.5:30:70", // 30 min avg - one day
+       "RRA:AVERAGE:0.5:180:70", // 3 hour avg - one week
+       "RRA:AVERAGE:0.5:720:70", // 12 hour avg - one month
+       "RRA:AVERAGE:0.5:10080:70", // 7 day avg - ony year
+
+       "RRA:MAX:0.5:1:70", // 1 min max - one hour
+       "RRA:MAX:0.5:30:70", // 30 min max - one day
+       "RRA:MAX:0.5:180:70",  // 3 hour max - one week
+       "RRA:MAX:0.5:720:70", // 12 hour max - one month
+       "RRA:MAX:0.5:10080:70", // 7 day max - ony year
+       NULL,
+};
+
+static const char *rrd_def_storage[] = {
+       "DS:total:GAUGE:120:0:U",
+       "DS:used:GAUGE:120:0:U",
+
+       "RRA:AVERAGE:0.5:1:70", // 1 min avg - one hour
+       "RRA:AVERAGE:0.5:30:70", // 30 min avg - one day
+       "RRA:AVERAGE:0.5:180:70", // 3 hour avg - one week
+       "RRA:AVERAGE:0.5:720:70", // 12 hour avg - one month
+       "RRA:AVERAGE:0.5:10080:70", // 7 day avg - ony year
+
+       "RRA:MAX:0.5:1:70", // 1 min max - one hour
+       "RRA:MAX:0.5:30:70", // 30 min max - one day
+       "RRA:MAX:0.5:180:70",  // 3 hour max - one week
+       "RRA:MAX:0.5:720:70", // 12 hour max - one month
+       "RRA:MAX:0.5:10080:70", // 7 day max - ony year
+       NULL,
+};
+
+#define RRDDIR "/var/lib/rrdcached/db"
+
+static void
+create_rrd_file(
+       const char *filename,
+       int argcount,
+       const char *rrddef[])
+{
+       /* start at day boundary */
+       time_t ctime;
+       time(&ctime);
+       struct tm *ltm = localtime(&ctime);
+       ltm->tm_sec = 0;
+       ltm->tm_min = 0;
+       ltm->tm_hour = 0;
+
+       rrd_clear_error();
+       if (rrd_create_r(filename, 60, timelocal(ltm), argcount, rrddef)) {
+               cfs_message("RRD create error %s: %s", filename, rrd_get_error());
+       }
+}
+
+static inline const char *
+rrd_skip_data(
+       const char *data,
+       int count)
+{
+       int found = 0;
+       while (*data && found < count) {
+               if (*data++ == ':')
+                       found++;
+       }
+       return data;
+}
+
+static void
+update_rrd_data(
+       const char *key,
+       gconstpointer data,
+       size_t len)
+{
+       g_return_if_fail(key != NULL);
+       g_return_if_fail(data != NULL);
+       g_return_if_fail(len > 0);
+       g_return_if_fail(len < 4096);
+
+       static const char *rrdcsock = "unix:/var/run/rrdcached.sock";
+
+       int use_daemon = 1;
+        if (rrdc_connect(rrdcsock) != 0)
+               use_daemon = 0;
+
+       char *filename = NULL;
+
+       int skip = 0;
+
+       if (strncmp(key, "pve2-node/", 10) == 0) {
+               const char *node = key + 10;
+
+               skip = 2;
+
+               if (strchr(node, '/') != NULL)
+                       goto keyerror;
+
+               if (strlen(node) < 1)
+                       goto keyerror;
+
+               filename = g_strdup_printf(RRDDIR "/%s", key);
+
+               if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
+
+                       mkdir(RRDDIR "/pve2-node", 0755);
+                       int argcount = sizeof(rrd_def_node)/sizeof(void*) - 1;
+                       create_rrd_file(filename, argcount, rrd_def_node);
+               }
+
+       } else if ((strncmp(key, "pve2-vm/", 8) == 0) ||
+                  (strncmp(key, "pve2.3-vm/", 10) == 0)) {
+               const char *vmid;
+
+               if (strncmp(key, "pve2-vm/", 8) == 0) {
+                       vmid = key + 8;
+                       skip = 2;
+               } else {
+                       vmid = key + 10;
+                       skip = 4;
+               }
+
+               if (strchr(vmid, '/') != NULL)
+                       goto keyerror;
+
+               if (strlen(vmid) < 1)
+                       goto keyerror;
+
+               filename = g_strdup_printf(RRDDIR "/%s/%s", "pve2-vm", vmid);
+
+               if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
+
+                       mkdir(RRDDIR "/pve2-vm", 0755);
+                       int argcount = sizeof(rrd_def_vm)/sizeof(void*) - 1;
+                       create_rrd_file(filename, argcount, rrd_def_vm);
+               }
+
+       } else if (strncmp(key, "pve2-storage/", 13) == 0) {
+               const char *node = key + 13;
+
+               const char *storage = node;
+               while (*storage && *storage != '/')
+                       storage++;
+
+               if (*storage != '/' || ((storage - node) < 1))
+                       goto keyerror;
+
+               storage++;
+
+               if (strchr(storage, '/') != NULL)
+                       goto keyerror;
+
+               if (strlen(storage) < 1)
+                       goto keyerror;
+
+               filename = g_strdup_printf(RRDDIR "/%s", key);
+
+               if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
+
+                       mkdir(RRDDIR "/pve2-storage", 0755);
+
+                       char *dir = g_path_get_dirname(filename);
+                       mkdir(dir, 0755);
+                       g_free(dir);
+
+                       int argcount = sizeof(rrd_def_storage)/sizeof(void*) - 1;
+                       create_rrd_file(filename, argcount, rrd_def_storage);
+               }
+
+       } else {
+               goto keyerror;
+       }
+
+       const char *dp = skip ? rrd_skip_data(data, skip) : data;
+
+       const char *update_args[] = { dp, NULL };
+
+       if (use_daemon) {
+               int status;
+               if ((status = rrdc_update(filename, 1, update_args)) != 0) {
+                       cfs_message("RRDC update error %s: %d", filename, status);
+                       rrdc_disconnect();
+                       rrd_clear_error();
+                       if (rrd_update_r(filename, NULL, 1, update_args) != 0) {
+                               cfs_message("RRD update error %s: %s", filename, rrd_get_error());
+                       }
+               }
+
+       } else {
+               rrd_clear_error();
+               if (rrd_update_r(filename, NULL, 1, update_args) != 0) {
+                       cfs_message("RRD update error %s: %s", filename, rrd_get_error());
+               }
+       }
+
+ret:
+       if (filename) 
+               g_free(filename);
+
+       return;
+
+keyerror:
+       cfs_critical("RRD update error: unknown/wrong key %s", key);
+       goto ret;
+}
+
+static gboolean
+rrd_entry_is_old(
+       gpointer key,
+       gpointer value,
+       gpointer user_data)
+{
+       rrdentry_t *entry = (rrdentry_t *)value;
+       uint32_t ctime = GPOINTER_TO_UINT(user_data);
+
+       int diff = ctime - entry->time;
+
+       /* remove everything older than 5 minutes */
+       int expire = 60*5;
+
+       return (diff > expire) ? TRUE : FALSE;
+}
+
+static char *rrd_dump_buf = NULL;
+static time_t rrd_dump_last = 0;
+
+void
+cfs_rrd_dump(GString *str)
+{
+       time_t ctime;
+
+       g_mutex_lock (&mutex);
+
+       time(&ctime);
+       if (rrd_dump_buf && (ctime - rrd_dump_last) < 2) {
+               g_string_assign(str, rrd_dump_buf);
+               g_mutex_unlock (&mutex);
+               return;
+       }
+
+       /* remove old data */
+       g_hash_table_foreach_remove(cfs_status.rrdhash, rrd_entry_is_old,
+                                   GUINT_TO_POINTER(ctime));
+
+       g_string_set_size(str, 0);
+
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_hash_table_iter_init (&iter, cfs_status.rrdhash);
+
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               rrdentry_t *entry = (rrdentry_t *)value;
+               g_string_append(str, key);
+               g_string_append(str, ":");
+               g_string_append(str, entry->data);
+               g_string_append(str, "\n");
+       }
+
+       g_string_append_c(str, 0); // never return undef
+
+       rrd_dump_last = ctime;
+       if (rrd_dump_buf)
+               g_free(rrd_dump_buf);
+       rrd_dump_buf = g_strdup(str->str);
+
+       g_mutex_unlock (&mutex);
+}
+
+static gboolean
+nodeip_hash_set(
+       GHashTable *iphash,
+       const char *nodename,
+       const char *ip,
+       size_t len)
+{
+       g_return_val_if_fail(iphash != NULL, FALSE);
+       g_return_val_if_fail(nodename != NULL, FALSE);
+       g_return_val_if_fail(ip != NULL, FALSE);
+       g_return_val_if_fail(len > 0, FALSE);
+       g_return_val_if_fail(len < 256, FALSE);
+       g_return_val_if_fail(ip[len-1] == 0, FALSE);
+
+       char *oldip = (char *)g_hash_table_lookup(iphash, nodename);
+
+       if (!oldip || (strcmp(oldip, ip) != 0)) {
+               cfs_status.clinfo_version++;
+               g_hash_table_replace(iphash, g_strdup(nodename), g_strdup(ip));
+       }
+
+       return TRUE;
+}
+
+static gboolean
+rrdentry_hash_set(
+       GHashTable *rrdhash,
+       const char *key,
+       const char *data,
+       size_t len)
+{
+       g_return_val_if_fail(rrdhash != NULL, FALSE);
+       g_return_val_if_fail(key != NULL, FALSE);
+       g_return_val_if_fail(data != NULL, FALSE);
+       g_return_val_if_fail(len > 0, FALSE);
+       g_return_val_if_fail(len < 4096, FALSE);
+       g_return_val_if_fail(data[len-1] == 0, FALSE);
+
+       rrdentry_t *entry;
+       if ((entry = (rrdentry_t *)g_hash_table_lookup(rrdhash, key))) {
+               g_free(entry->data);
+               entry->data = g_memdup2(data, len);
+               entry->len = len;
+               entry->time = time(NULL);
+       } else {
+               rrdentry_t *entry = g_new0(rrdentry_t, 1);
+
+               entry->key = g_strdup(key);
+               entry->data = g_memdup2(data, len);
+               entry->len = len;
+               entry->time = time(NULL);
+
+               g_hash_table_replace(rrdhash, entry->key, entry);
+       }
+
+       update_rrd_data(key, data, len);
+
+       return TRUE;
+}
+
+static int
+kvstore_send_update_message(
+       dfsm_t *dfsm,
+       const char *key,
+       gpointer data,
+       guint32 len)
+{
+       if (!dfsm_is_initialized(dfsm))
+               return -EACCES;
+
+       struct iovec iov[2];
+
+       char name[256];
+       g_strlcpy(name, key, sizeof(name));
+
+       iov[0].iov_base = &name;
+       iov[0].iov_len = sizeof(name);
+
+       iov[1].iov_base = (char *)data;
+       iov[1].iov_len = len;
+
+       if (dfsm_send_message(dfsm, KVSTORE_MESSAGE_UPDATE, iov, 2) == CS_OK)
+               return 0;
+
+       return -EACCES;
+}
+
+static clog_entry_t *
+kvstore_parse_log_message(
+       const void *msg,
+       size_t msg_len)
+{
+       g_return_val_if_fail(msg != NULL, NULL);
+
+       if (msg_len < sizeof(clog_entry_t)) {
+               cfs_critical("received short log message (%zu < %zu)", msg_len, sizeof(clog_entry_t));
+               return NULL;
+       }
+
+       clog_entry_t *entry = (clog_entry_t *)msg;
+
+       uint32_t size = sizeof(clog_entry_t) + entry->node_len +
+               entry->ident_len + entry->tag_len + entry->msg_len;
+
+       if (msg_len != size) {
+               cfs_critical("received log message with wrong size (%zu != %u)", msg_len, size);
+               return NULL;
+       }
+
+       char *msgptr = entry->data;
+
+       if (*((char *)msgptr + entry->node_len - 1)) {
+               cfs_critical("unterminated string in log message");
+               return NULL;
+       }
+       msgptr += entry->node_len;
+
+       if (*((char *)msgptr + entry->ident_len - 1)) {
+               cfs_critical("unterminated string in log message");
+               return NULL;
+       }
+       msgptr += entry->ident_len;
+
+       if (*((char *)msgptr + entry->tag_len - 1)) {
+               cfs_critical("unterminated string in log message");
+               return NULL;
+       }
+       msgptr += entry->tag_len;
+
+       if (*((char *)msgptr + entry->msg_len - 1)) {
+               cfs_critical("unterminated string in log message");
+               return NULL;
+       }
+
+       return entry;
+}
+
+static gboolean
+kvstore_parse_update_message(
+       const void *msg,
+       size_t msg_len,
+       const char **key,
+       gconstpointer *data,
+       guint32 *len)
+{
+       g_return_val_if_fail(msg != NULL, FALSE);
+       g_return_val_if_fail(key != NULL, FALSE);
+       g_return_val_if_fail(data != NULL, FALSE);
+       g_return_val_if_fail(len != NULL, FALSE);
+
+       if (msg_len < 256) {
+               cfs_critical("received short kvstore message (%zu < 256)", msg_len);
+               return FALSE;
+       }
+
+       /* test if key is null terminated */
+       int i = 0;
+       for (i = 0; i < 256; i++)
+               if (((char *)msg)[i] == 0)
+                       break;
+
+       if (i == 256)
+               return FALSE;
+
+
+       *len = msg_len - 256;
+       *key = msg;
+       *data = (char *) msg + 256;
+
+       return TRUE;
+}
+
+int
+cfs_create_status_msg(
+       GString *str,
+       const char *nodename,
+       const char *key)
+{
+       g_return_val_if_fail(str != NULL, -EINVAL);
+       g_return_val_if_fail(key != NULL, -EINVAL);
+
+       int res = -ENOENT;
+
+       GHashTable *kvhash = NULL;
+
+       g_mutex_lock (&mutex);
+
+       if (!nodename || !nodename[0] || !strcmp(nodename, cfs.nodename)) {
+               kvhash = cfs_status.kvhash;
+       } else if (cfs_status.clinfo && cfs_status.clinfo->nodes_byname) {
+               cfs_clnode_t *clnode;
+               if ((clnode = g_hash_table_lookup(cfs_status.clinfo->nodes_byname, nodename)))
+                       kvhash = clnode->kvhash;
+       }
+
+       kventry_t *entry;
+       if (kvhash && (entry = (kventry_t *)g_hash_table_lookup(kvhash, key))) {
+               g_string_append_len(str, entry->data, entry->len);
+               res = 0;
+       }
+
+       g_mutex_unlock (&mutex);
+
+       return res;
+}
+
+int
+cfs_status_set(
+       const char *key,
+       gpointer data,
+       size_t len)
+{
+       g_return_val_if_fail(key != NULL, FALSE);
+       g_return_val_if_fail(data != NULL, FALSE);
+       g_return_val_if_fail(cfs_status.kvhash != NULL, FALSE);
+
+       if (len > CFS_MAX_STATUS_SIZE)
+               return -EFBIG;
+
+       g_mutex_lock (&mutex);
+
+       gboolean res;
+
+       if (strncmp(key, "rrd/", 4) == 0) {
+               res = rrdentry_hash_set(cfs_status.rrdhash, key + 4, data, len);
+       } else if (!strcmp(key, "nodeip")) {
+               res = nodeip_hash_set(cfs_status.iphash, cfs.nodename, data, len);
+       } else {
+               res = kventry_hash_set(cfs_status.kvhash, key, data, len);
+       }
+       g_mutex_unlock (&mutex);
+
+       if (cfs_status.kvstore)
+               kvstore_send_update_message(cfs_status.kvstore, key, data, len);
+
+       return res ? 0 : -ENOMEM;
+}
+
+gboolean
+cfs_kvstore_node_set(
+       uint32_t nodeid,
+       const char *key,
+       gconstpointer data,
+       size_t len)
+{
+       g_return_val_if_fail(nodeid != 0, FALSE);
+       g_return_val_if_fail(key != NULL, FALSE);
+       g_return_val_if_fail(data != NULL, FALSE);
+
+       g_mutex_lock (&mutex);
+
+       if (!cfs_status.clinfo || !cfs_status.clinfo->nodes_byid)
+               goto ret; /* ignore */
+
+       cfs_clnode_t *clnode = g_hash_table_lookup(cfs_status.clinfo->nodes_byid, &nodeid);
+       if (!clnode)
+               goto ret; /* ignore */
+
+       cfs_debug("got node %d status update %s", nodeid, key);
+
+       if (strncmp(key, "rrd/", 4) == 0) {
+               rrdentry_hash_set(cfs_status.rrdhash, key + 4, data, len);
+       } else if (!strcmp(key, "nodeip")) {
+               nodeip_hash_set(cfs_status.iphash, clnode->name, data, len);
+       } else {
+               if (!clnode->kvhash) {
+                       if (!(clnode->kvhash = kventry_hash_new())) {
+                               goto ret; /*ignore */
+                       }
+               }
+
+               kventry_hash_set(clnode->kvhash, key, data, len);
+
+       }
+ret:
+       g_mutex_unlock (&mutex);
+
+       return TRUE;
+}
+
+static gboolean
+cfs_kvstore_sync(void)
+{
+       g_return_val_if_fail(cfs_status.kvhash != NULL, FALSE);
+       g_return_val_if_fail(cfs_status.kvstore != NULL, FALSE);
+
+       gboolean res = TRUE;
+
+       g_mutex_lock (&mutex);
+
+       GHashTable *ht = cfs_status.kvhash;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       g_hash_table_iter_init (&iter, ht);
+
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               kventry_t *entry = (kventry_t *)value;
+               kvstore_send_update_message(cfs_status.kvstore, entry->key, entry->data, entry->len);
+       }
+
+       g_mutex_unlock (&mutex);
+
+       return res;
+}
+
+static int
+dfsm_deliver(
+       dfsm_t *dfsm,
+       gpointer data,
+       int *res_ptr,
+       uint32_t nodeid,
+       uint32_t pid,
+       uint16_t msg_type,
+       uint32_t msg_time,
+       const void *msg,
+       size_t msg_len)
+{
+       g_return_val_if_fail(dfsm != NULL, -1);
+       g_return_val_if_fail(msg != NULL, -1);
+       g_return_val_if_fail(res_ptr != NULL, -1);
+
+       /* ignore message for ourself */
+       if (dfsm_nodeid_is_local(dfsm, nodeid, pid))
+               goto ret;
+
+       if (msg_type == KVSTORE_MESSAGE_UPDATE) {
+               const char *key;
+               gconstpointer data;
+               guint32 len;
+               if (kvstore_parse_update_message(msg, msg_len, &key, &data, &len)) {
+                       cfs_kvstore_node_set(nodeid, key, data, len);
+               } else {
+                       cfs_critical("cant parse update message");
+               }
+       } else if (msg_type == KVSTORE_MESSAGE_LOG) {
+               cfs_message("received log"); // fixme: remove
+               const clog_entry_t *entry;
+               if ((entry = kvstore_parse_log_message(msg, msg_len))) {
+                       clusterlog_insert(cfs_status.clusterlog, entry);
+               } else {
+                       cfs_critical("cant parse log message");
+               }
+       } else {
+               cfs_critical("received unknown message type %d\n", msg_type);
+               goto fail;
+       }
+
+ret:
+       *res_ptr = 0;
+       return 1;
+
+fail:
+       *res_ptr = -EACCES;
+       return 1;
+}
+
+static void
+dfsm_confchg(
+       dfsm_t *dfsm,
+       gpointer data,
+       const struct cpg_address *member_list,
+       size_t member_list_entries)
+{
+       g_return_if_fail(dfsm != NULL);
+       g_return_if_fail(member_list != NULL);
+
+       cfs_debug("enter %s", __func__);
+
+       g_mutex_lock (&mutex);
+
+       cfs_clinfo_t *clinfo = cfs_status.clinfo;
+
+       if (clinfo && clinfo->nodes_byid) {
+
+               GHashTable *ht = clinfo->nodes_byid;
+               GHashTableIter iter;
+               gpointer key, value;
+
+               g_hash_table_iter_init (&iter, ht);
+
+               while (g_hash_table_iter_next (&iter, &key, &value)) {
+                       cfs_clnode_t *node = (cfs_clnode_t *)value;
+                       node->online = FALSE;
+               }
+
+               for (int i = 0; i < member_list_entries; i++) {
+                       cfs_clnode_t *node;
+                       if ((node = g_hash_table_lookup(clinfo->nodes_byid, &member_list[i].nodeid))) {
+                               node->online = TRUE;
+                       }
+               }
+
+               cfs_status.clinfo_version++;
+       }
+
+       g_mutex_unlock (&mutex);
+}
+
+static gpointer
+dfsm_get_state(
+       dfsm_t *dfsm,
+       gpointer data,
+       unsigned int *res_len)
+{
+       g_return_val_if_fail(dfsm != NULL, NULL);
+
+       gpointer msg = clusterlog_get_state(cfs_status.clusterlog, res_len);
+
+       return msg;
+}
+
+static int
+dfsm_process_update(
+       dfsm_t *dfsm,
+       gpointer data,
+       dfsm_sync_info_t *syncinfo,
+       uint32_t nodeid,
+       uint32_t pid,
+       const void *msg,
+       size_t msg_len)
+{
+       cfs_critical("%s: received unexpected update message", __func__);
+
+       return -1;
+}
+
+static int
+dfsm_process_state_update(
+       dfsm_t *dfsm,
+       gpointer data,
+       dfsm_sync_info_t *syncinfo)
+{
+       g_return_val_if_fail(dfsm != NULL, -1);
+       g_return_val_if_fail(syncinfo != NULL, -1);
+
+       clog_base_t *clog[syncinfo->node_count];
+
+       int local_index = -1;
+       for (int i = 0; i < syncinfo->node_count; i++) {
+               dfsm_node_info_t *ni = &syncinfo->nodes[i];
+               ni->synced = 1;
+
+               if (syncinfo->local == ni)
+                       local_index = i;
+
+               clog_base_t *base = (clog_base_t *)ni->state;
+               if (ni->state_len > 8 && ni->state_len == clog_size(base)) {
+                       clog[i] = ni->state;
+               } else {
+                       cfs_critical("received log with wrong size %u", ni->state_len);
+                       clog[i] = NULL;
+               }
+       }
+
+       if (!clusterlog_merge(cfs_status.clusterlog, clog, syncinfo->node_count, local_index)) {
+               cfs_critical("unable to merge log files");
+       }
+
+       cfs_kvstore_sync();
+
+       return 1;
+}
+
+static int
+dfsm_commit(
+       dfsm_t *dfsm,
+       gpointer data,
+       dfsm_sync_info_t *syncinfo)
+{
+       g_return_val_if_fail(dfsm != NULL, -1);
+       g_return_val_if_fail(syncinfo != NULL, -1);
+
+       return 1;
+}
+
+static void
+dfsm_synced(dfsm_t *dfsm)
+{
+       g_return_if_fail(dfsm != NULL);
+
+       char *ip = (char *)g_hash_table_lookup(cfs_status.iphash, cfs.nodename);
+       if (!ip) 
+               ip = cfs.ip;
+
+       cfs_status_set("nodeip", ip, strlen(ip) + 1);
+}
+
+static int
+dfsm_cleanup(
+       dfsm_t *dfsm,
+       gpointer data,
+       dfsm_sync_info_t *syncinfo)
+{
+       return 1;
+}
+
+static dfsm_callbacks_t kvstore_dfsm_callbacks = {
+       .dfsm_deliver_fn = dfsm_deliver,
+       .dfsm_confchg_fn = dfsm_confchg,
+
+       .dfsm_get_state_fn = dfsm_get_state,
+       .dfsm_process_state_update_fn = dfsm_process_state_update,
+       .dfsm_process_update_fn = dfsm_process_update,
+       .dfsm_commit_fn = dfsm_commit,
+       .dfsm_cleanup_fn = dfsm_cleanup,
+       .dfsm_synced_fn = dfsm_synced,
+};
+
+dfsm_t *
+cfs_status_dfsm_new(void)
+{
+       g_mutex_lock (&mutex);
+
+       cfs_status.kvstore = dfsm_new(NULL, KVSTORE_CPG_GROUP_NAME, G_LOG_DOMAIN,
+                                     0, &kvstore_dfsm_callbacks);
+       g_mutex_unlock (&mutex);
+
+       return cfs_status.kvstore;
+}
+
+gboolean
+cfs_is_quorate(void)
+{
+       g_mutex_lock (&mutex);
+       gboolean res =  cfs_status.quorate;
+       g_mutex_unlock (&mutex);
+
+       return res;
+}
+
+void
+cfs_set_quorate(
+       uint32_t quorate,
+       gboolean quiet)
+{
+       g_mutex_lock (&mutex);
+
+       uint32_t prev_quorate = cfs_status.quorate;
+       cfs_status.quorate = quorate;
+
+       if (!prev_quorate && cfs_status.quorate) {
+               if (!quiet)
+                       cfs_message("node has quorum");
+       }
+
+       if (prev_quorate && !cfs_status.quorate) {
+               if (!quiet)
+                       cfs_message("node lost quorum");
+       }
+
+       g_mutex_unlock (&mutex);
+}
diff --git a/src/pmxcfs/status.h b/src/pmxcfs/status.h
new file mode 100644 (file)
index 0000000..041cb34
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+  Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#ifndef _PVE_STATUS_H_
+#define _PVE_STATUS_H_
+
+#include <glib.h>
+
+#include "dfsm.h"
+#include "logger.h"
+#include "memdb.h"
+
+#define VMTYPE_QEMU 1
+// FIXME: remove openvz stuff for 7.x
+#define VMTYPE_OPENVZ 2
+#define VMTYPE_LXC 3
+
+#define CFS_MAX_STATUS_SIZE (32*1024)
+
+typedef struct cfs_clnode cfs_clnode_t;
+typedef struct cfs_clinfo cfs_clinfo_t;
+
+void 
+cfs_status_init(void);
+
+void 
+cfs_status_cleanup(void);
+
+dfsm_t *
+cfs_status_dfsm_new(void);
+
+void
+cfs_cluster_log(clog_entry_t *entry);
+
+void 
+cfs_cluster_log_dump(
+       GString *str, 
+       const char *user, 
+       guint max_entries);
+
+void
+cfs_rrd_dump(GString *str); 
+
+int
+cfs_status_set(
+       const char *key,
+       gpointer data,
+       size_t len);
+
+void 
+cfs_status_set_clinfo(
+       cfs_clinfo_t *clinfo);
+
+void 
+cfs_status_set_vmlist(
+       GHashTable *vmlist);
+
+cfs_clnode_t *
+cfs_clnode_new(
+       const char *name, 
+       uint32_t nodeid, 
+       uint32_t votes);
+
+void 
+cfs_clnode_destroy(
+       cfs_clnode_t *clnode);
+
+cfs_clinfo_t *
+cfs_clinfo_new(
+       const char *cluster_name, 
+       uint32_t cman_version);
+
+gboolean 
+cfs_clinfo_destroy(
+       cfs_clinfo_t *clinfo);
+
+gboolean 
+cfs_clinfo_add_node(
+       cfs_clinfo_t *clinfo,
+       cfs_clnode_t *clnode);
+
+void 
+cfs_set_quorate(
+       uint32_t quorate, 
+       gboolean quiet);
+
+gboolean
+cfs_is_quorate(void);
+
+GHashTable *
+vmlist_hash_new(void);
+
+gboolean 
+vmlist_hash_insert_vm(
+       GHashTable *vmlist,
+       int vmtype, 
+       guint32 vmid, 
+       const char *nodename,
+       gboolean replace);
+
+void 
+vmlist_register_vm(
+       int vmtype, 
+       guint32 vmid, 
+       const char *nodename);
+
+void
+vmlist_delete_vm(
+       guint32 vmid);
+
+gboolean
+vmlist_vm_exists(
+       guint32 vmid);
+
+gboolean
+vmlist_different_vm_exists(
+       int vmtype,
+       guint32 vmid,
+       const char *nodename);
+
+void 
+record_memdb_change(const char *path);
+
+void 
+record_memdb_reload(void);
+
+
+int
+cfs_create_status_msg(
+       GString *str,
+       const char *nodename,
+       const char *key);
+
+int
+cfs_create_version_msg(
+       GString *str);
+
+int
+cfs_create_vmlist_msg(
+       GString *str);
+
+int
+cfs_create_memberlist_msg(
+       GString *str);
+
+int
+cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid);
+
+int
+cfs_create_guest_conf_properties_msg(GString *str, memdb_t *memdb, const char **props, uint8_t num_props, uint32_t vmid);
+
+#endif /* _PVE_STATUS_H_ */
diff --git a/src/test/Makefile b/src/test/Makefile
new file mode 100644 (file)
index 0000000..757f35f
--- /dev/null
@@ -0,0 +1,11 @@
+all:
+
+cpgtest: cpgtest.c
+       gcc -Wall cpgtest.c $(shell pkg-config --cflags --libs libcpg libqb) -o cpgtest
+
+.PHONY: check install clean distclean
+check:
+       ./corosync_parser_test.pl
+
+distclean: clean
+clean:
diff --git a/src/test/corosync_configs/multiple-nodes.conf b/src/test/corosync_configs/multiple-nodes.conf
new file mode 100644 (file)
index 0000000..c951f5b
--- /dev/null
@@ -0,0 +1,56 @@
+logging {
+  debug: off
+  to_syslog: yes
+}
+
+nodelist {
+  node {
+    name: prox1
+    nodeid: 1
+    quorum_votes: 1
+    ring0_addr: prox1
+    ring1_addr: prox1-ring1
+  }
+  node {
+    name: prox2
+    nodeid: 1
+    quorum_votes: 1
+    ring0_addr: prox2
+    ring1_addr: prox2-ring1
+  }
+  node {
+    name: prox3
+    nodeid: 1
+    quorum_votes: 1
+    ring0_addr: prox3
+    ring1_addr: prox3-ring1
+  }
+  node {
+    name: prox4
+    nodeid: 1
+    quorum_votes: 1
+    ring0_addr: prox4
+    ring1_addr: prox4-ring1
+  }
+}
+
+quorum {
+  provider: corosync_votequorum
+}
+
+totem {
+  cluster_name: cloud
+  config_version: 1
+  interface {
+    bindnetaddr: 192.168.0.42
+    ringnumber: 0
+  }
+  interface {
+    bindnetaddr: 192.168.1.42
+    ringnumber: 1
+  }
+  ip_version: ipv4
+  secauth: on
+  version: 2
+}
+
diff --git a/src/test/corosync_configs/old.conf b/src/test/corosync_configs/old.conf
new file mode 100644 (file)
index 0000000..7b12644
--- /dev/null
@@ -0,0 +1,29 @@
+logging {
+  debug: off
+  to_syslog: yes
+}
+
+nodelist {
+  node {
+    nodeid: 1
+    quorum_votes: 1
+    ring0_addr: prox1
+  }
+}
+
+quorum {
+  provider: corosync_votequorum
+}
+
+totem {
+  cluster_name: sea
+  config_version: 1
+  interface {
+    bindnetaddr: 192.168.0.42
+    ringnumber: 0
+  }
+  ip_version: ipv4
+  secauth: on
+  version: 2
+}
+
diff --git a/src/test/corosync_configs/single-node.conf b/src/test/corosync_configs/single-node.conf
new file mode 100644 (file)
index 0000000..c65a3e5
--- /dev/null
@@ -0,0 +1,30 @@
+logging {
+  debug: off
+  to_syslog: yes
+}
+
+nodelist {
+  node {
+    name: prox1
+    nodeid: 1
+    quorum_votes: 1
+    ring0_addr: prox1
+  }
+}
+
+quorum {
+  provider: corosync_votequorum
+}
+
+totem {
+  cluster_name: cloud
+  config_version: 1
+  interface {
+    bindnetaddr: 192.168.0.42
+    ringnumber: 0
+  }
+  ip_version: ipv4
+  secauth: on
+  version: 2
+}
+
diff --git a/src/test/corosync_parser_test.pl b/src/test/corosync_parser_test.pl
new file mode 100755 (executable)
index 0000000..6999d3a
--- /dev/null
@@ -0,0 +1,84 @@
+#!/usr/bin/perl
+
+use lib '..';
+
+use strict;
+use warnings;
+
+use Test::MockModule;
+use Test::More;
+
+use PVE::Corosync;
+
+my $known_hosts = {
+    "prox1" => "127.0.1.1",
+    "prox1-ring1" => "127.0.2.1",
+    "prox2" => "127.0.1.2",
+    "prox2-ring1" => "127.0.2.2",
+    "prox3" => "127.0.1.3",
+    "prox3-ring1" => "127.0.2.3",
+    "prox4" => "127.0.1.4",
+    "prox4-ring1" => "127.0.2.4",
+};
+
+sub mocked_resolve {
+    my ($hostname) = @_;
+
+    foreach my $host (keys %$known_hosts) {
+       return $known_hosts->{$host} if $hostname eq $host;
+    }
+
+    die "got unknown hostname '$hostname' during mocked resolve_hostname_like_corosync";
+}
+
+my $mocked_corosync_module = new Test::MockModule('PVE::Corosync');
+$mocked_corosync_module->mock('resolve_hostname_like_corosync', \&mocked_resolve);
+
+sub parser_self_check {
+    my $cfg_fn = shift;
+
+    my $outfile = "$cfg_fn.write";
+    my ($config1, $config2, $raw1, $raw2);
+
+    eval {
+       # read first time
+       $raw1 = PVE::Tools::file_get_contents($cfg_fn);
+       $config1 = PVE::Corosync::parse_conf($cfg_fn, $raw1);
+
+       # write config
+       $raw2 = PVE::Corosync::write_conf(undef, $config1);
+       # do not actually write cfg, but you can outcomment to do so, e.g. if
+       # you want to use diff for easy comparision
+       #PVE::Tools::file_set_contents($outfile, $raw2);
+
+       # reparse written config (must be the same as config1)
+       $config2 = PVE::Corosync::parse_conf(undef, $raw2);
+    }; warn $@ if $@;
+
+    # test verify_config
+    my ($err, $warn) = PVE::Corosync::verify_conf($config1);
+    die "verify_conf failed: " . join(" ++ ", @$err) if scalar(@$err);
+
+    # do not care for whitespace differences
+    delete $config1->{digest};
+    delete $config2->{digest};
+
+    is_deeply($config1, $config2, "self check hash: $cfg_fn");
+
+    # do not care about extra new lines
+    $raw1 =~ s/^\s*\n+//mg;
+    $raw2 =~ s/^\s*\n+//mg;
+
+    is($raw1, $raw2, "self check raw: $cfg_fn");
+}
+
+# exec tests
+if (my $file = shift) {
+    parser_self_check($file);
+} else {
+    foreach my $file (<corosync_configs/*.conf>) {
+       parser_self_check($file);
+    }
+}
+
+done_testing();
diff --git a/src/test/cpgtest.c b/src/test/cpgtest.c
new file mode 100644 (file)
index 0000000..2cffe83
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+  Copyright (C) 2009 Proxmox Server Solutions GmbH
+
+  Copyright: This program is under GNU GPL, the GNU General Public License.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 dated June, 1991.
+  
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <corosync/corotypes.h>
+#include <corosync/cpg.h>
+
+static int cpg_mode_leave;
+
+static void my_cpg_deliver_callback (
+        cpg_handle_t handle,
+        const struct cpg_name *groupName,
+        uint32_t nodeid,
+        uint32_t pid,
+        void *msg,
+        size_t msg_len)
+{
+       printf("got message form %d/%d\n", nodeid, pid);
+
+       cpg_mode_leave = 1;
+
+       return;
+}
+
+static void my_cpg_confchg_callback (
+        cpg_handle_t handle,
+        const struct cpg_name *groupName,
+        const struct cpg_address *member_list, size_t member_list_entries,
+        const struct cpg_address *left_list, size_t left_list_entries,
+        const struct cpg_address *joined_list, size_t joined_list_entries)
+{
+        int i;
+
+       printf("cpg_confchg_callback %ld joined, %ld left, %ld members\n",
+              joined_list_entries, left_list_entries, member_list_entries);
+
+       for (i = 0; i < member_list_entries; i++) {
+               printf("cpg member %d/%d\n", member_list[i].nodeid, member_list[i].pid);
+       }
+
+       /* send update message */
+       char *inbuf = "This is just a test message\n";
+       struct iovec iov;
+       iov.iov_base = inbuf;
+       iov.iov_len = strlen(inbuf)+1;
+
+       cs_error_t result;
+loop:
+       result = cpg_mcast_joined(handle, CPG_TYPE_AGREED, &iov, 1);
+       if (result == CS_ERR_TRY_AGAIN) {
+               usleep(1000);
+               printf("cpg_send_message retry");
+               goto loop;
+       }
+
+       if (result != CS_OK) 
+               printf("cpg_send_message failed: %d\n", result);
+
+}
+
+static cpg_callbacks_t callbacks = {
+        .cpg_deliver_fn =            my_cpg_deliver_callback,
+        .cpg_confchg_fn =            my_cpg_confchg_callback,
+};
+
+
+int main(int argc, char *argv[])
+{
+       struct cpg_name group_name;
+       char *gn = "TESTGROUP";
+       strcpy(group_name.value, gn);
+       group_name.length = strlen(gn) + 1;
+
+       cs_error_t result;
+       cpg_handle_t handle;
+
+start:
+       printf("starting cpgtest\n");
+
+       cpg_mode_leave = 0;
+       handle = 0;
+
+       printf("calling cpg_initialize\n");
+       result = cpg_initialize(&handle, &callbacks);
+       if (result != CS_OK) {
+               printf("cpg_initialize failed: %d\n", result);
+               goto retry;
+       }
+
+       printf("calling cpg_join\n");
+       while ((result = cpg_join(handle, &group_name)) == CS_ERR_TRY_AGAIN) { 
+               printf("cpg_join returned %d\n", result);
+               sleep (1);
+       }
+
+       if (result != CS_OK) {
+               printf("cpg_join failed: %d\n", result);
+               exit(-1);               
+       }
+
+       fd_set read_fds;
+       FD_ZERO(&read_fds);
+       int cpg_fd;
+
+       cpg_fd_get(handle, &cpg_fd);
+
+       printf("starting main loop\n");
+
+       do {
+               FD_SET(cpg_fd, &read_fds);
+               struct timeval timeout = { 1, 0};
+               result = select(cpg_fd + 1, &read_fds, 0, 0, &timeout);
+
+               if (result == -1) {
+                       printf("select error: %d\n", result);
+                       break;
+               } 
+               if (result > 0) {
+
+                       if (FD_ISSET(cpg_fd, &read_fds)) {                      
+                               cs_error_t res = cpg_dispatch(handle, CS_DISPATCH_ALL);
+                               if (res != CS_OK) {
+                                       printf("cpg_dispatch failed: %d\n", res);
+                                       break;
+                               }
+                       }
+               }
+
+               if (cpg_mode_leave)
+                       break;
+
+       } while(1);
+
+retry:
+
+       printf("end loop - trying to restart\n");
+
+       usleep (1000);
+
+       if (handle) {
+
+               result = cpg_finalize(handle);
+               if (result != CS_OK) {
+                       printf("cpg_finalize failed: %d\n", result);
+                       exit(-1);
+               }
+       }
+
+       goto start;
+
+       exit(0);
+}
diff --git a/src/test/scripts/benchmark-config-get-property.pl b/src/test/scripts/benchmark-config-get-property.pl
new file mode 100755 (executable)
index 0000000..f009c5a
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/perl
+
+use lib '../../';
+
+use strict;
+use warnings;
+
+#use Data::Dumper;
+use Time::HiRes qw( gettimeofday tv_interval );
+
+use PVE::Tools;
+use PVE::Cluster;
+use PVE::QemuConfig;
+use PVE::LXC::Config;
+
+sub sec_to_unit {
+       my $sec = shift;
+
+       my $unit_index = 0;
+       while ($sec < 1) {
+               $sec *= 1000;
+               $unit_index++;
+       }
+
+       my $unit = @{['s', 'ms', 'us', 'ns', 'ps']}[$unit_index];
+
+       return wantarray ? ($sec, $unit) : "$sec $unit";
+
+}
+
+my $results = {};
+
+sub perf {
+    my ($name, $loops, $code) = @_;
+
+    return if !defined($loops) || $loops <= 0;
+
+    my $loop = 0;
+    eval {
+       my $t0 = [gettimeofday];
+
+       for (my $i = 0; $i<$loops; $i++) {
+               $code->();
+       }
+
+       my $elapsed = tv_interval ($t0, [gettimeofday]);
+
+       my $total = sec_to_unit($elapsed);
+       my $per_loop = $elapsed/$loops;
+       $loop = sec_to_unit($per_loop);
+
+       $results->{$name} = [ $elapsed * 1000, $per_loop * 1000 ];
+
+       print STDERR "elapsed['$name' x $loops]: $total => $loop/loop\n";
+    }; warn $@ if $@;
+
+    return $loop;
+}
+
+my $loops = shift // 3;
+my $vmid = shift // 0;
+my $prop = shift // 'lock';
+
+perf('cfg-get-prop', $loops, sub {
+    my $res = PVE::Cluster::get_guest_config_property($prop, $vmid);
+});
+
+PVE::Cluster::cfs_update();
+perf('perl-manual', $loops, sub {
+    my $res = {};
+
+    # modeled after the manager API cluster/resource call
+    my $vmlist = PVE::Cluster::get_vmlist() || {};
+    my $idlist = $vmlist->{ids} || {};
+    foreach my $vmid (keys %$idlist) {
+
+       my $data = $idlist->{$vmid};
+       my $typedir = $data->{type} eq 'qemu' ? 'qemu-server' : 'lxc';
+
+       my $conf = PVE::Cluster::cfs_read_file("nodes/$data->{node}/$typedir/$vmid.conf");
+
+       my $v = $conf->{$prop};
+       $res->{$vmid} = { $prop => $v } if defined($v);
+    }
+});
+#PVE::Cluster::get_tasklist('dev5');
+
+my $a = $results->{'cfg-get-prop'};
+my $b = $results->{'perl-manual'};
+printf("$loops\t%.2f\t%.2f\t%.2f\t%.2f\n", $a->[0], $a->[1], $b->[0], $b->[1]);
+
+#print "res: " . Dumper($res) ."\n";
diff --git a/src/test/scripts/create_large_files.pl b/src/test/scripts/create_large_files.pl
new file mode 100755 (executable)
index 0000000..0dd595a
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/perl -w
+
+use strict;
+use POSIX;
+use File::Path qw(make_path remove_tree);
+use IO::File;
+
+my (undef, $nodename) = POSIX::uname();
+
+sub safe_mkdir {
+    my $dir = shift;
+    (mkdir $dir) || die "safe_mkdir $dir failed - $!\n";
+}
+
+my $data = "0" x (1024*100);
+
+sub create_file {
+    my ($filename) = shift;
+
+    my $fh = new IO::File $filename, O_RDWR|O_CREAT|O_EXCL;
+    die "cant create file $filename - $!" if !defined $fh;
+
+    #my $data = "$filename\n" x 30;
+
+    (print $fh $data) || die "write $filename failed\n";
+    close ($fh);
+
+    #system("cat $filename");
+    #system("df -h /etc/pve");
+}
+
+my $testdir = "/etc/pve/manyfilestest/";
+
+remove_tree($testdir);
+
+safe_mkdir $testdir;
+
+for (my $i = 0; $i < 300; $i++) {
+
+    create_file("$testdir/test$i.dat");
+}
diff --git a/src/test/scripts/create_many_files.pl b/src/test/scripts/create_many_files.pl
new file mode 100755 (executable)
index 0000000..483e83f
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/perl -w
+
+use strict;
+use POSIX;
+use File::Path qw(make_path remove_tree);
+use IO::File;
+
+my (undef, $nodename) = POSIX::uname();
+
+sub safe_mkdir {
+    my $dir = shift;
+    (mkdir $dir) || die "safe_mkdir $dir failed - $!\n";
+}
+
+sub create_file {
+    my ($filename) = shift;
+
+    my $fh = new IO::File $filename, O_RDWR|O_CREAT|O_EXCL;
+    die "cant create file $filename - $!" if !defined $fh;
+
+    #my $data = "$filename\n" x 30;
+    my $data = "0" x 2048;
+
+    (print $fh $data) || die "write $filename failed\n";
+    close ($fh);
+
+    #system("cat $filename");
+    #system("df -h /etc/pve");
+}
+
+my $testdir = "/etc/pve/manyfilestest/";
+
+remove_tree($testdir);
+
+safe_mkdir $testdir;
+
+for (my $i = 0; $i < 100; $i++) {
+
+    safe_mkdir "$testdir/$i";
+
+    for (my $j = 0; $j < 90; $j++) {
+       create_file("$testdir/$i/test$j.dat");
+    }
+}
diff --git a/src/test/scripts/create_vm_test.pl b/src/test/scripts/create_vm_test.pl
new file mode 100755 (executable)
index 0000000..6af97a7
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/bin/perl -w
+
+use strict;
+use POSIX;
+use File::Path qw(make_path remove_tree);
+use IO::File;
+
+my (undef, $nodename) = POSIX::uname();
+
+sub safe_mkdir {
+    my $dir = shift;
+    (mkdir $dir) || die "safe_mkdir $dir failed - $!\n";
+}
+
+sub safe_rmdir {
+    my $dir = shift;
+    (rmdir $dir) || die "safe_rmdir $dir failed - $!\n";
+}
+
+sub safe_unlink {
+    my $file = shift;
+    (unlink $file) || die "safe_unlink $file failed - $!\n";
+}
+
+sub create_vmfile {
+    my ($filename) = shift;
+
+    my $fh = new IO::File $filename, O_RDWR|O_CREAT|O_EXCL;
+    die "cant create file $filename - $!" if !defined $fh;
+
+    #my $data = "$filename\n" x 30;
+    my $data = "0" x 1024;
+
+    (print $fh $data) || die "write $filename failed\n";
+    close ($fh);
+
+    #system("cat $filename");
+    #system("df -h /etc/pve");
+}
+
+sub start_vmtest {
+    my ($subdir) = @_;
+
+    for (my $i = 1000; $i < 1100; $i++) {
+       my $filename = "$subdir/${i}.conf";
+       create_vmfile($filename);
+    }
+
+    for (my $i = 1000; $i < 1100; $i++) {
+       my $filename = "$subdir/${i}.conf";
+       safe_unlink($filename);
+    }
+}
+
+sub start_subtest {
+    my ($subdir) = @_;
+
+    safe_mkdir $subdir;
+
+    start_vmtest($subdir);
+
+    safe_rmdir $subdir;
+}
+
+sub start_test {
+    my ($subdir) = @_;
+
+    safe_mkdir $subdir;
+
+    start_subtest("$subdir/qemu-server");
+
+    safe_rmdir $subdir;
+}
+
+
+my $basedir = "/etc/pve/nodes/";
+
+my $testdir = "$basedir/${nodename}-test1";
+
+remove_tree($testdir);
+
+while(1) {
+    eval {
+       local $SIG{INT} = sub { die "interrupted" };
+       start_test("$testdir");
+    };
+    my $err = $@;
+
+    system("date; df -h /etc/pve");
+    die $err if $err;
+}
diff --git a/src/test/scripts/test-broadcast-kv.pl b/src/test/scripts/test-broadcast-kv.pl
new file mode 100755 (executable)
index 0000000..d2166a2
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+
+use lib '../../';
+
+use strict;
+use warnings;
+
+use JSON;
+
+use PVE::Tools;
+use PVE::Cluster;
+
+## first broadcast a value for a key then you can check if you get it back by
+# omitting the value, or directly querys an already exisitng value (e.g., ceph
+# stats)
+
+my $k = shift // die "no key";
+my $v = shift;
+
+if (defined $v) {
+    print "broadcasting kv pair ($k, $v)\n";
+    PVE::Cluster::broadcast_node_kv($k, $v);
+}
+
+print "querying value for key: $k\n";
+my $res = PVE::Cluster::get_node_kv($k);
+
+print "res: " . to_json($res, {utf8 => 1, pretty => 1}) ."\n";
diff --git a/src/test/scripts/test-config-get-property.pl b/src/test/scripts/test-config-get-property.pl
new file mode 100755 (executable)
index 0000000..80a726c
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use lib '../../';
+
+use strict;
+use warnings;
+
+use JSON;
+
+use PVE::Tools;
+use PVE::Cluster;
+
+## for quick test do:
+# echo 'lock: test' >> /etc/pve/lxc/104.conf
+
+my $vmid = shift // 104;
+my $prop = shift // 'lock';
+
+my $res = PVE::Cluster::get_guest_config_property($prop, $vmid);
+
+print "res: " . to_json($res, {utf8 => 1, pretty => 1}) ."\n";
diff --git a/src/test/scripts/test-verify-token.pl b/src/test/scripts/test-verify-token.pl
new file mode 100755 (executable)
index 0000000..11f07dc
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use lib '../../';
+
+use strict;
+use warnings;
+
+use Data::Dumper;
+
+use PVE::Tools;
+use PVE::Cluster;
+
+## For quick test you can add arbitrary fake tokens to token.cfg:
+# echo 'root@pam 1234512345XXXXX' >> /etc/pve/priv/token.cfg
+
+my $token = shift // '1234512345XXXXX';
+my $userid = shift // 'root@pam';
+
+my $res = PVE::Cluster::verify_token($userid, $token);
+
+print "token '$userid $token' ". ($res ? '' : "not " ) ."found\n";