<commit_before>/* Copyright (C) 2006-2008 <NAME> <<EMAIL>>
 * Copyright (C) 2012-2014 <NAME> <<EMAIL>>
 *
 * 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; either version 2 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 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
 */

/*
 * TODO: on setuid and capabilites(7)
 *
 * right now this program runs as setuid root
 * it might be possible to drop the wholesale root privs by using
 * capabilites(7). in particular:
 *
 * CAP_SYS_RAWIO   open /dev/mem and /dev/kmem & Perform I/O port operations
 * CAP_SYS_NICE    set real-time scheduling policies, set CPU affinity
 * CAP_SYS_MODULE  Load  and  unload  kernel  modules
 *
 * NB:  Capabilities are a per-thread attribute,
 * so this might need to be done on a per-thread basis
 * see also CAP_SETPCAP, CAP_INHERITABLE and 'inheritable set'
 *
 * see also:
 * http://stackoverflow.com/questions/13183327/drop-root-uid-while-retaining-cap-sys-nice
 * http://stackoverflow.com/questions/12141420/losing-capabilities-after-setuid
 */

#include "config.h"


#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <uuid/uuid.h>
#include <dlfcn.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <sys/resource.h>
#include <linux/capability.h>
#include <sys/io.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <errno.h>
#include <malloc.h>
#include <assert.h>
#include <syslog_async.h>
#include <limits.h>
#include <sys/prctl.h>
#include <inifile.h>

#include <czmq.h>
#include <google/protobuf/text_format.h>

#include <machinetalk/generated/message.pb.h>
#include <pbutil.hh>  // note_printf(pb::Container &c, const char *fmt, ...)

using namespace google::protobuf;
typedef ::google::protobuf::RepeatedPtrField< ::std::string> pbstringarray_t;

#include "rtapi.h"
#include "rtapi_global.h"
#include "rtapi_compat.h"
#include "rtapi_export.h"
#include "hal.h"
#include "hal_priv.h"
#include "rtapi/shmdrv/shmdrv.h"

#include "mk-backtrace.h"
#include "setup_signals.h"
#include "mk-zeroconf.hh"
#include "select_interface.h"

#define BACKGROUND_TIMER 1000

using namespace std;

/* Pre-allocation size. Must be enough for the whole application life to avoid
 * pagefaults by new memory requested from the system. */
#define PRE_ALLOC_SIZE		(30 * 1024 * 1024)

template<class T> T DLSYM(void *handle, const string &name) {
    return (T)(dlsym(handle, name.c_str()));
}

template<class T> T DLSYM(void *handle, const char *name) {
    return (T)(dlsym(handle, name));
}
typedef int (*hal_call_usrfunct_t)(const char *name,
				   const int argc,
				   const char **argv,
				   int *ureturn);
static hal_call_usrfunct_t call_usrfunct;

static std::map<string, void*> modules;
static std::vector<string> loading_order;
static void remove_module(std::string name);

static struct rusage rusage;
static unsigned long minflt, majflt;
static int instance_id;
flavor_ptr flavor;
static int use_drivers = 0;
static int foreground;
static int debug;
static int signal_fd;
static bool interrupted;
static bool trap_signals = true;
int shmdrv_loaded;
long page_size;
static const char *progname;
static const char *z_uri;
static int z_port;
static pb::Container command, reply;
static uuid_t process_uuid;
static char process_uuid_str[40];
static register_context_t *rtapi_publisher;
static const char *service_uuid;

#ifdef NOTYET
static int remote = 0; // announce and bind a TCP socket
static const char *ipaddr = "127.0.0.1";
static const char *z_uri_dsn;
#endif

static const char *interfaces;
static const char *inifile;
static FILE *inifp;

#ifdef NOTYET
static AvahiCzmqPoll *av_loop;
#endif

// the following two variables, despite extern, are in fact private to rtapi_app
// in the sense that they are not visible in the RT space (the namespace 
// of dlopen'd modules); these are supposed to be 'ships in the night'
// relative to any symbols exported by rtapi_app.
//
// global_data is set in attach_global_segment() which was already 
// created by rtapi_msgd
// rtapi_switch is set once rtapi.so has been loaded by calling the 
// rtapi_get_handle() method in rtapi.so.
// Once set, rtapi methods in rtapi.so can be called normally through
// the rtapi_switch redirection (see rtapi.h).

// NB: do _not_ call any rtapi_* methods before these variables are set
// except for rtapi_msg* and friends (those do not go through the rtapi_switch).
rtapi_switch_t *rtapi_switch;
global_data_t *global_data; 

static int init_actions(int instance);
static void exit_actions(int instance);
static int harden_rt(void);
static void rtapi_app_msg_handler(msg_level_t level, const char *fmt, va_list ap);
static void stderr_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap);

static int do_one_item(char item_type_char,
		       const string &param_name,
		       const string &param_value,
		       void *vitem,
		       int idx,
		       pb::Container &pbreply)
{
    char *endp;
    switch(item_type_char) {
    case 'l': {
	long *litem = *(long**) vitem;
	litem[idx] = strtol(param_value.c_str(), &endp, 0);
	if(*endp) {
	    note_printf(pbreply, "`%s' invalid for parameter `%s'",
			param_value.c_str(), param_name.c_str());
	    return -1;
	}
	return 0;
    }
    case 'i': {
	int *iitem = *(int**) vitem;
	iitem[idx] = strtol(param_value.c_str(), &endp, 0);
	if(*endp) {
	    note_printf(pbreply,
			"`%s' invalid for parameter `%s'",
			param_value.c_str(), param_name.c_str());
	    return -1;
	}
	return 0;
    }
    case 's': {
	char **sitem = *(char***) vitem;
	sitem[idx] = strdup(param_value.c_str());
	return 0;
    }
    default:
	note_printf(pbreply,
		    "%s: Invalid type character `%c'\n",
		    param_name.c_str(), item_type_char);
	return -1;
    }
    return 0;
}

void remove_quotes(string &s)
{
    s.erase(remove_copy(s.begin(), s.end(), s.begin(), '"'), s.end());
}

static int do_module_args(void *module,
			  pbstringarray_t args,
			  const string &symprefix,
			  pb::Container &pbreply)
{
    for(int i = 0; i < args.size(); i++) {
        string s(args.Get(i));
	remove_quotes(s);
        size_t idx = s.find('=');
        if(idx == string::npos) {
	    note_printf(pbreply, "Invalid parameter `%s'",
			s.c_str());
            return -1;
        }
        string param_name(s, 0, idx);
        string param_value(s, idx+1);

        void *item = DLSYM<void*>(module,
				  symprefix +
				  "address_" +
				  param_name);
        if (!item) {
	    note_printf(pbreply,
			"Unknown parameter `%s'",
			s.c_str());
            return -1;
        }
	dlerror();
        char **item_type = DLSYM<char**>(module,
					 symprefix +
					 "type_" +
					 param_name);
        if (!item_type || !*item_type) {
	    const char *err = dlerror();
	    if (err)
		note_printf(pbreply, "BUG: %s:", err);
	    note_printf(pbreply,
			"Unknown parameter `%s' (type information missing)",
			s.c_str());
            return -1;
        }
        string item_type_string = *item_type;

        if (item_type_string.size() > 1) {
            int a, b;
            char item_type_char;
            int r = sscanf(item_type_string.c_str(), "%d-%d%c",
			   &a, &b, &item_type_char);
            if(r != 3) {
		note_printf(pbreply,
			    "Unknown parameter `%s'"
			    " (corrupt array type information): %s",
			    s.c_str(), item_type_string.c_str());
                return -1;
            }
            size_t idx = 0;
            int i = 0;
            while(idx != string::npos) {
                if(i == b) {
		    note_printf(pbreply,
				"%s: can only take %d arguments",
				s.c_str(), b);
                    return -1;
                }
                size_t idx1 = param_value.find(",", idx);
                string substr(param_value, idx, idx1 - idx);
                int result = do_one_item(item_type_char, s, substr, item, i, pbreply);
                if(result != 0) return result;
                i++;
                idx = idx1 == string::npos ? idx1 : idx1 + 1;
            }
        } else {
            char item_type_char = item_type_string[0];
            int result = do_one_item(item_type_char, s, param_value, item, 0, pbreply);
            if (result != 0) return result;
        }
    }
    return 0;
}

// kthreads:
// only instance args are exported in sysfs, module params are not
// see RTAPI_IP_MODEin src/rtapi/rtapi.h
// therefore, if we see a param on kthreads newinst, we just
// overwrite the previous value via sysfs
static int do_kmodinst_args(const string &comp,
			  pbstringarray_t args,
			  pb::Container &pbreply)
{
    for (int i = 0; i < args.size(); i++) {
        string s(args.Get(i));
	remove_quotes(s);
        size_t idx = s.find('=');
        if(idx == string::npos) {
	    note_printf(pbreply, "Invalid parameter `%s'",
			s.c_str());
            return -1;
        }
        string param_name(s, 0, idx);
        string param_value(s, idx+1);

	// ls /sys/module/brd/parameters/
	// max_part  rd_nr  rd_size

	string path = "/sys/module/" + comp + "/parameters/" + param_name;
	struct stat sb;
	if (stat(path.c_str(), &sb) < 0) {
	    // if param_name is an instance param, it's exported in sysfs
	    note_printf(pbreply, "newinst '%s': no such instance parameter '%s'",
			comp.c_str(),
			param_name.c_str());
	    return -ENOENT;
	}
	int retval = procfs_cmd(path.c_str(), param_value.c_str());
	if (retval < 0) {
	    note_printf(pbreply, "newinst %s: setting param %s to %s failed:  %d - %s",
			comp.c_str(),
			param_name.c_str(),
			param_value.c_str(),
			retval,
			strerror(-retval));
	    return retval;
	}
    }
    return 0;
}

static void pbconcat(string &s, const pbstringarray_t &args)
{
    for (int i = 0; i < args.size(); i++) {
	s += args.Get(i);
	if (i < args.size()-1)
	    s += " ";
    }
}

static const char **pbargv(const pbstringarray_t &args)
{
    const char **argv, **s;
    s = argv = (const char **) calloc(sizeof(char *), args.size() + 1);
    for (int i = 0; i < args.size(); i++) {
	*s++ = args.Get(i).c_str();
    }
    *s = NULL;
    return argv;
}

static inline bool kernel_threads(flavor_ptr f) {
    assert(f);
    return (f->flags & FLAVOR_KERNEL_BUILD) != 0;
}

static void usrfunct_error(const int retval,
			   const string &func,
			   pbstringarray_t args,
			   pb::Container &pbreply)
{
    if (retval >= 0) return;
    string s;
    pbconcat(s, args);
    note_printf(pbreply, "hal_call_usrfunct(%s,%s) failed: %d - %s",
		func.c_str(), s.c_str(), retval, strerror(-retval));
}

// split arg array into key=value, others
static void separate_kv(pbstringarray_t &kvpairs,
		    pbstringarray_t &leftovers,
		    const pbstringarray_t &args)
{
    for(int i = 0; i < args.size(); i++) {
        string s(args.Get(i));
	remove_quotes(s);
        if (s.find('=') == string::npos)
	    leftovers.Add()->assign(s);
	else
	    kvpairs.Add()->assign(s);
    }
}

static int do_newinst_cmd(int instance,
			  string comp,
			  string instname,
			  pbstringarray_t args,
			  pb::Container &pbreply)
{
    int retval = -1;


    if (kernel_threads(flavor)) {
	string s;
	pbconcat(s, args);
	retval = do_kmodinst_args(comp,args,pbreply);
	if (retval) return retval;
	return procfs_cmd(PROCFS_RTAPICMD,"call newinst %s %s %s",
			  comp.c_str(),
			  instname.c_str(),
			  s.c_str());
    } else {
	if (call_usrfunct == NULL) {
	    pbreply.set_retcode(1);
	    pbreply.add_note("this HAL library version does not support user functions - version problem?");
	    return -1;
	}
	void *w = modules[comp];
	if (w == NULL) {
	    // if newinst via halcmd, it should have been automatically loaded already
	    note_printf(pbreply,
			"newinst: component '%s' not loaded",
			comp.c_str());
	    return -1;
	}
	dlerror();
	string s;
	pbconcat(s, args);

	pbstringarray_t kvpairs, leftovers;

	separate_kv(kvpairs, leftovers, args);

	// set the instance parameters
	retval = do_module_args(w, kvpairs, RTAPI_IP_SYMPREFIX, pbreply);
	if (retval < 0) {
	    note_printf(pbreply,
			"passing args for '%s' failed: '%s'",
			instname.c_str(), s.c_str());
	    return retval;
	}
	rtapi_print_msg(RTAPI_MSG_DBG,
			"%s: instargs='%s'\n",__FUNCTION__,
			s.c_str());

	// massage the argv for the newinst user function,
	// and call it
	pbstringarray_t a;
	a.Add()->assign(comp);
	a.Add()->assign(instname);
	a.MergeFrom(leftovers);
	const char **argv = pbargv(a); // pass non-kv pairs only
	int ureturn = 0;
	retval = call_usrfunct("newinst", a.size(), argv, &ureturn );
	if (argv) free(argv);
	if (retval == 0) retval = ureturn;
	usrfunct_error(retval, "newinst", args, pbreply);
    }
    return retval;
}

static int do_delinst_cmd(int instance,
			  string instname,
			  pb::Container &pbreply)
{
    int retval = -1;
    string s;


    if (kernel_threads(flavor)) {
	return procfs_cmd(PROCFS_RTAPICMD,"call delinst %s", instname.c_str());
    } else {
	if (call_usrfunct == NULL) {
	    pbreply.set_retcode(1);
	    pbreply.add_note("this HAL library version does not support user functions - version problem?");
	    return -1;
	}
	pbstringarray_t a;
	a.Add()->assign(instname);
	const char **argv = pbargv(a);
	int ureturn = 0;
	retval = call_usrfunct("delinst", a.size(), argv, &ureturn);
	if (argv) free(argv);
	if (retval == 0) retval = ureturn;
	usrfunct_error(retval, "delinst", a, pbreply);
    }
    return retval;
 }

static int do_callfunc_cmd(int instance,
			   string func,
			   pbstringarray_t args,
			   pb::Container &pbreply)
{
    int retval = -1;

    if (kernel_threads(flavor)) {
	string s;
	pbconcat(s, args);
	return procfs_cmd(PROCFS_RTAPICMD,"call %s %s", func.c_str(), s.c_str());
    } else {
	if (call_usrfunct == NULL) {
	    pbreply.set_retcode(1);
	    pbreply.add_note("this HAL library version does not support user functions - version problem?");
	    return -1;
	}
	const char **argv = pbargv(args);
	int ureturn = 0;
	retval = call_usrfunct(func.c_str(),
			       args.size(),
			       argv,
			       &ureturn);
	if (argv) free(argv);
	if (retval == 0) retval = ureturn;
	usrfunct_error(retval, func, args, pbreply);
    }
    return retval;
}


static int do_load_cmd(int instance,
		       string name,
		       pbstringarray_t args,
		       pb::Container &pbreply)
{
    void *w = modules[name];
    char module_name[PATH_MAX];
    void *module;
    int retval;

    if (w == NULL) {
	if (kernel_threads(flavor)) {
	    string cmdargs;
	    pbconcat(cmdargs, args);
	    retval = run_module_helper("insert %s %s", name.c_str(), cmdargs.c_str());
	    if (retval) {
		rtapi_print_msg(RTAPI_MSG_ERR, "couldnt insmod %s - see dmesg\n",
				name.c_str());
	    } else {
		modules[name] = (void *) -1;  // so 'if (modules[name])' works
		loading_order.push_back(name);
	    }
	    return retval;
	} else {
	    strncpy(module_name, (name + flavor->mod_ext).c_str(),
		    PATH_MAX);
	    module = modules[name] = dlopen(module_name, RTLD_GLOBAL |RTLD_NOW);
	    if (!module) {
		rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlopen: %s\n",
				name.c_str(), dlerror());
		return -1;
	    }
	    // retrieve the address of rtapi_switch_struct
	    // so rtapi functions can be called and members
	    // accessed
	    if (rtapi_switch == NULL) {
		rtapi_get_handle_t rtapi_get_handle;
		dlerror();
		rtapi_get_handle = (rtapi_get_handle_t) dlsym(module,
							      "rtapi_get_handle");
		if (rtapi_get_handle != NULL) {
		    rtapi_switch = rtapi_get_handle();
		    assert(rtapi_switch != NULL);
		}
	    }
	    /// XXX handle arguments
	    int (*start)(void) = DLSYM<int(*)(void)>(module, "rtapi_app_main");
	    if(!start) {
		rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlsym: %s\n",
				name.c_str(), dlerror());
		return -1;
	    }
	    int result;

	    result = do_module_args(module, args, RTAPI_MP_SYMPREFIX, pbreply);
	    if(result < 0) { dlclose(module); return -1; }

	    // need to call rtapi_app_main with as root
	    // RT thread creation and hardening requires this
	    if ((result = start()) < 0) {
		rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app_main(%s): %d %s\n",
				name.c_str(), result, strerror(-result));
		modules.erase(modules.find(name));
		return result;
	    }
	    loading_order.push_back(name);

	    rtapi_print_msg(RTAPI_MSG_DBG, "%s: loaded from %s\n",
			    name.c_str(), module_name);
	    return 0;
	}
    } else {
	rtapi_print_msg(RTAPI_MSG_ERR, "%s: already loaded\n", name.c_str());
	return -1;
    }
}

static int do_unload_cmd(int instance, string name)
{
    void *w = modules[name];
    int retval = 0;

    if (w == NULL) {
        // rtapi_print_msg(RTAPI_MSG_ERR, "unload: '%s' not loaded\n",
	// 		name.c_str());
	return -1;
    } else {
	if (kernel_threads(flavor)) {
	    retval = run_module_helper("remove %s", name.c_str());
	    if (retval) {
		rtapi_print_msg(RTAPI_MSG_ERR, "couldnt rmmod %s - see dmesg\n",
				name.c_str());
		return retval;
	    } else {
		modules.erase(modules.find(name));
		remove_module(name);
	    }
	} else {
	    int (*stop)(void) = DLSYM<int(*)(void)>(w, "rtapi_app_exit");
	    if (stop)
		stop();
	    modules.erase(modules.find(name));
	    remove_module(name);
	    dlclose(w);
	}
    }
    rtapi_print_msg(RTAPI_MSG_DBG, " '%s' unloaded\n", name.c_str());
    return retval;
}

// shut down the stack in reverse loading order
static void exit_actions(int instance)
{
    size_t index = loading_order.size() - 1;
    for(std::vector<std::string>::reverse_iterator rit = loading_order.rbegin();
	rit != loading_order.rend(); ++rit, --index) {
	do_unload_cmd(instance, *rit);
    }
}

static int init_actions(int instance)
{
    int retval;
    char moddir[PATH_MAX];

    get_rtapi_config(moddir,"MODULES",PATH_MAX);

    if (kernel_threads(flavor)) {
	// kthreads cant possibly run without shmdrv, so bail
	// also, cannot load it here because rtapi_msgd already needs this
	// so it'd be too late here
	if (!is_module_loaded("shmdrv")) {
	    rtapi_print_msg(RTAPI_MSG_ERR, "shmdrv not loaded");
	    return -1;
	}
	// leftovers or running session?
	if (is_module_loaded("rtapi")) {
	    rtapi_print_msg(RTAPI_MSG_ERR, "rtapi already loaded");
	    return -1;
	}
	if (is_module_loaded("hal_lib")) {
	    rtapi_print_msg(RTAPI_MSG_ERR, "hal_lib already loaded");
	    return -1;
	}
	char *m = strtok(moddir, "\t ");
	while (m != NULL) {
	    char cmdline[PATH_MAX];
	    if (!strcmp(m, "rtapi")) {
		snprintf(cmdline, sizeof(cmdline),
			 "insert %s rtapi_instance=%d", m, instance_id);
	    } else {
		snprintf(cmdline, sizeof(cmdline), "insert %s", m);
	    }

	    rtapi_print_msg(RTAPI_MSG_DBG, "running '%s'", cmdline);
	    retval = run_module_helper(cmdline);
	    if (retval) {
		rtapi_print_msg(RTAPI_MSG_ERR,
				"linuxcnc_module_helper '%s' failed - see dmesg\n",
				cmdline);
		return retval;
	    } else
		rtapi_print_msg(RTAPI_MSG_DBG, "'%s' loaded\n", m);
	    m = strtok(NULL,  "\t ");
	}
    }
    pb::Container c;
    if ((retval =  do_load_cmd(instance, "rtapi", pbstringarray_t(),c)))
	return retval;
    if ((retval = do_load_cmd(instance, "hal_lib", pbstringarray_t(), c)))
	return retval;

    if (!kernel_threads(flavor)) {
	// resolve the "hal_call_usrfunct" for later - callfunc, newinst & delinst need it
	void *hallib = modules["hal_lib"];
	dlerror();
	call_usrfunct = (hal_call_usrfunct_t) dlsym(hallib, "hal_call_usrfunct");

	if (call_usrfunct == NULL) {
	    rtapi_print_msg(RTAPI_MSG_ERR,
			    "cant resolve 'hal_call_usrfunct' in hal_lib - version problem?");
	    char *s = dlerror();
	    if (s)
		rtapi_print_msg(RTAPI_MSG_ERR, "dlsym(hal_call_usrfunct): '%s'", s);
	    return -1;
	}
    }
    return 0;
}


static int attach_global_segment()
{
    int retval = 0;
    int globalkey = OS_KEY(GLOBAL_KEY, instance_id);
    int size = 0;
    int tries = 10; // 5 sec deadline for msgd/globaldata to come up

    shm_common_init();
    do {
	retval = shm_common_new(globalkey, &size,
				instance_id, (void **) &global_data, 0);
	if (retval < 0) {
	    tries--;
	    if (tries == 0) {
		syslog_async(LOG_ERR,
		       "rtapi_app:%d: ERROR: cannot attach global segment key=0x%x %s\n",
		       instance_id, globalkey, strerror(-retval));
		return retval;
	    }
	    struct timespec ts = {0, 500 * 1000 * 1000}; //ms
	    nanosleep(&ts, NULL);
	}
    } while (retval < 0);

    if (size != sizeof(global_data_t)) {
	syslog_async(LOG_ERR,
	       "rtapi_app:%d global segment size mismatch: expect %zu got %d\n",
	       instance_id, sizeof(global_data_t), size);
	return -EINVAL;
    }

    tries = 10;
    while  (global_data->magic !=  GLOBAL_READY) {
	tries--;
	if (tries == 0) {
	    syslog_async(LOG_ERR,
		   "rtapi_app:%d: ERROR: global segment magic not changing to ready: magic=0x%x\n",
		   instance_id, global_data->magic);
	    return -EINVAL;
	}
	struct timespec ts = {0, 500 * 1000 * 1000}; //ms
	nanosleep(&ts, NULL);
    }
    return retval;
}


// handle commands from zmq socket
static int rtapi_request(zloop_t *loop, zmq_pollitem_t *poller, void *arg)
{
    zmsg_t *r = zmsg_recv(poller->socket);
    char *origin = zmsg_popstr (r);
    zframe_t *request_frame  = zmsg_pop (r);
    static bool force_exit = false;

    pb::Container pbreq, pbreply;

    if (!pbreq.ParseFromArray(zframe_data(request_frame),
			      zframe_size(request_frame))) {
	rtapi_print_msg(RTAPI_MSG_ERR, "cant decode request from %s (size %zu)",
			origin ? origin : "NULL",
			zframe_size(request_frame));
	zmsg_destroy(&r);
	return 0;
    }
    if (debug) {
	string buffer;
	if (TextFormat::PrintToString(pbreq, &buffer)) {
	    fprintf(stderr, "request: %s\n",buffer.c_str());
	}
    }

    pbreply.set_type(pb::MT_RTAPI_APP_REPLY);

    switch (pbreq.type()) {
    case pb::MT_RTAPI_APP_PING:
	char buffer[LINELEN];
	snprintf(buffer, sizeof(buffer),
		 "pid=%d flavor=%s gcc=%s git=%s",
		 getpid(),flavor->name,  __VERSION__, GIT_VERSION);
	pbreply.add_note(buffer);
	pbreply.set_retcode(0);
	break;

    case pb::MT_RTAPI_APP_EXIT:
	assert(pbreq.has_rtapicmd());
	exit_actions(pbreq.rtapicmd().instance());
	force_exit = true;
	pbreply.set_retcode(0);
	break;

    case pb::MT_RTAPI_APP_CALLFUNC:

	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_func());
	assert(pbreq.rtapicmd().has_instance());
	pbreply.set_retcode(do_callfunc_cmd(pbreq.rtapicmd().instance(),
					      pbreq.rtapicmd().func(),
					      pbreq.rtapicmd().argv(),
					      pbreply));
	break;

    case pb::MT_RTAPI_APP_NEWINST:
	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_comp());
	assert(pbreq.rtapicmd().has_instname());
	assert(pbreq.rtapicmd().has_instance());
	pbreply.set_retcode(do_newinst_cmd(pbreq.rtapicmd().instance(),
					   pbreq.rtapicmd().comp(),
					   pbreq.rtapicmd().instname(),
					   pbreq.rtapicmd().argv(),
					   pbreply));
	break;

    case pb::MT_RTAPI_APP_DELINST:

	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_instname());
	assert(pbreq.rtapicmd().has_instance());
	pbreply.set_retcode(do_delinst_cmd(pbreq.rtapicmd().instance(),
					   pbreq.rtapicmd().instname(),
					   pbreply));
	break;


    case pb::MT_RTAPI_APP_LOADRT:
	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_modname());
	assert(pbreq.rtapicmd().has_instance());
	pbreply.set_retcode(do_load_cmd(pbreq.rtapicmd().instance(),
					pbreq.rtapicmd().modname(),
					pbreq.rtapicmd().argv(),
					pbreply));
	break;

    case pb::MT_RTAPI_APP_UNLOADRT:
	assert(pbreq.rtapicmd().has_modname());
	assert(pbreq.rtapicmd().has_instance());

	pbreply.set_retcode(do_unload_cmd(pbreq.rtapicmd().instance(),
					  pbreq.rtapicmd().modname()));
	break;

    case pb::MT_RTAPI_APP_LOG:
	assert(pbreq.has_rtapicmd());
	if (pbreq.rtapicmd().has_rt_msglevel()) {
	    global_data->rt_msg_level = pbreq.rtapicmd().rt_msglevel();
	}
	if (pbreq.rtapicmd().has_user_msglevel()) {
	    global_data->user_msg_level = pbreq.rtapicmd().user_msglevel();
	}
	pbreply.set_retcode(0);
	break;

    case pb::MT_RTAPI_APP_NEWTHREAD:
	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_threadname());
	assert(pbreq.rtapicmd().has_threadperiod());
	assert(pbreq.rtapicmd().has_cpu());
	assert(pbreq.rtapicmd().has_use_fp());
	assert(pbreq.rtapicmd().has_instance());

	if (kernel_threads(flavor)) {
	    int retval =  procfs_cmd(PROCFS_RTAPICMD,"newthread %s %d %d %d",
					   pbreq.rtapicmd().threadname().c_str(),
					   pbreq.rtapicmd().threadperiod(),
					   pbreq.rtapicmd().use_fp(),
					   pbreq.rtapicmd().cpu());
	    pbreply.set_retcode(retval < 0 ? retval:0);

	} else {
	    void *w = modules["hal_lib"];<commit_msg>rtapi_app: report more error detail back to halcmd_rtapiapp<commit_after>/* Copyright (C) 2006-2008 <NAME> <<EMAIL>>
 * Copyright (C) 2012-2014 <NAME> <<EMAIL>>
 *
 * 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; either version 2 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 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
 */

/*
 * TODO: on setuid and capabilites(7)
 *
 * right now this program runs as setuid root
 * it might be possible to drop the wholesale root privs by using
 * capabilites(7). in particular:
 *
 * CAP_SYS_RAWIO   open /dev/mem and /dev/kmem & Perform I/O port operations
 * CAP_SYS_NICE    set real-time scheduling policies, set CPU affinity
 * CAP_SYS_MODULE  Load  and  unload  kernel  modules
 *
 * NB:  Capabilities are a per-thread attribute,
 * so this might need to be done on a per-thread basis
 * see also CAP_SETPCAP, CAP_INHERITABLE and 'inheritable set'
 *
 * see also:
 * http://stackoverflow.com/questions/13183327/drop-root-uid-while-retaining-cap-sys-nice
 * http://stackoverflow.com/questions/12141420/losing-capabilities-after-setuid
 */

#include "config.h"


#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <uuid/uuid.h>
#include <dlfcn.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <sys/resource.h>
#include <linux/capability.h>
#include <sys/io.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <errno.h>
#include <malloc.h>
#include <assert.h>
#include <syslog_async.h>
#include <limits.h>
#include <sys/prctl.h>
#include <inifile.h>

#include <czmq.h>
#include <google/protobuf/text_format.h>

#include <machinetalk/generated/message.pb.h>
#include <pbutil.hh>  // note_printf(pb::Container &c, const char *fmt, ...)

using namespace google::protobuf;

#include "rtapi.h"
#include "rtapi_global.h"
#include "rtapi_compat.h"
#include "rtapi_export.h"
#include "hal.h"
#include "hal_priv.h"
#include "rtapi/shmdrv/shmdrv.h"

#include "mk-backtrace.h"
#include "setup_signals.h"
#include "mk-zeroconf.hh"
#include "select_interface.h"

#define BACKGROUND_TIMER 1000

using namespace std;

/* Pre-allocation size. Must be enough for the whole application life to avoid
 * pagefaults by new memory requested from the system. */
#define PRE_ALLOC_SIZE		(30 * 1024 * 1024)

template<class T> T DLSYM(void *handle, const string &name) {
    return (T)(dlsym(handle, name.c_str()));
}

template<class T> T DLSYM(void *handle, const char *name) {
    return (T)(dlsym(handle, name));
}
typedef int (*hal_call_usrfunct_t)(const char *name,
				   const int argc,
				   const char **argv,
				   int *ureturn);
static hal_call_usrfunct_t call_usrfunct;

static std::map<string, void*> modules;
static std::vector<string> loading_order;
static void remove_module(std::string name);

static struct rusage rusage;
static unsigned long minflt, majflt;
static int instance_id;
flavor_ptr flavor;
static int use_drivers = 0;
static int foreground;
static int debug;
static int signal_fd;
static bool interrupted;
static bool trap_signals = true;
int shmdrv_loaded;
long page_size;
static const char *progname;
static const char *z_uri;
static int z_port;
static pb::Container command, reply;
static uuid_t process_uuid;
static char process_uuid_str[40];
static register_context_t *rtapi_publisher;
static const char *service_uuid;

#ifdef NOTYET
static int remote = 0; // announce and bind a TCP socket
static const char *ipaddr = "127.0.0.1";
static const char *z_uri_dsn;
#endif

static const char *interfaces;
static const char *inifile;
static FILE *inifp;

#ifdef NOTYET
static AvahiCzmqPoll *av_loop;
#endif

// the following two variables, despite extern, are in fact private to rtapi_app
// in the sense that they are not visible in the RT space (the namespace 
// of dlopen'd modules); these are supposed to be 'ships in the night'
// relative to any symbols exported by rtapi_app.
//
// global_data is set in attach_global_segment() which was already 
// created by rtapi_msgd
// rtapi_switch is set once rtapi.so has been loaded by calling the 
// rtapi_get_handle() method in rtapi.so.
// Once set, rtapi methods in rtapi.so can be called normally through
// the rtapi_switch redirection (see rtapi.h).

// NB: do _not_ call any rtapi_* methods before these variables are set
// except for rtapi_msg* and friends (those do not go through the rtapi_switch).
rtapi_switch_t *rtapi_switch;
global_data_t *global_data; 

static int init_actions(int instance);
static void exit_actions(int instance);
static int harden_rt(void);
static void rtapi_app_msg_handler(msg_level_t level, const char *fmt, va_list ap);
static void stderr_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap);

static int do_one_item(char item_type_char,
		       const string &param_name,
		       const string &param_value,
		       void *vitem,
		       int idx,
		       pb::Container &pbreply)
{
    char *endp;
    switch(item_type_char) {
    case 'l': {
	long *litem = *(long**) vitem;
	litem[idx] = strtol(param_value.c_str(), &endp, 0);
	if(*endp) {
	    note_printf(pbreply, "`%s' invalid for parameter `%s'",
			param_value.c_str(), param_name.c_str());
	    return -1;
	}
	return 0;
    }
    case 'i': {
	int *iitem = *(int**) vitem;
	iitem[idx] = strtol(param_value.c_str(), &endp, 0);
	if(*endp) {
	    note_printf(pbreply,
			"`%s' invalid for parameter `%s'",
			param_value.c_str(), param_name.c_str());
	    return -1;
	}
	return 0;
    }
    case 's': {
	char **sitem = *(char***) vitem;
	sitem[idx] = strdup(param_value.c_str());
	return 0;
    }
    default:
	note_printf(pbreply,
		    "%s: Invalid type character `%c'\n",
		    param_name.c_str(), item_type_char);
	return -1;
    }
    return 0;
}

void remove_quotes(string &s)
{
    s.erase(remove_copy(s.begin(), s.end(), s.begin(), '"'), s.end());
}

static int do_module_args(void *module,
			  pbstringarray_t args,
			  const string &symprefix,
			  pb::Container &pbreply)
{
    for(int i = 0; i < args.size(); i++) {
        string s(args.Get(i));
	remove_quotes(s);
        size_t idx = s.find('=');
        if(idx == string::npos) {
	    note_printf(pbreply, "Invalid parameter `%s'",
			s.c_str());
            return -1;
        }
        string param_name(s, 0, idx);
        string param_value(s, idx+1);
        void *item = DLSYM<void*>(module,
				  symprefix +
				  "address_" +
				  param_name);
        if (!item) {
	    note_printf(pbreply,
			"Unknown parameter `%s'",
			s.c_str());
            return -1;
        }
	dlerror();
        char **item_type = DLSYM<char**>(module,
					 symprefix +
					 "type_" +
					 param_name);
        if (!item_type || !*item_type) {
	    const char *err = dlerror();
	    if (err)
		note_printf(pbreply, "BUG: %s:", err);
	    note_printf(pbreply,
			"Unknown parameter `%s' (type information missing)",
			s.c_str());
            return -1;
        }
        string item_type_string = *item_type;

        if (item_type_string.size() > 1) {
            int a, b;
            char item_type_char;
            int r = sscanf(item_type_string.c_str(), "%d-%d%c",
			   &a, &b, &item_type_char);
            if(r != 3) {
		note_printf(pbreply,
			    "Unknown parameter `%s'"
			    " (corrupt array type information): %s",
			    s.c_str(), item_type_string.c_str());
                return -1;
            }
            size_t idx = 0;
            int i = 0;
            while(idx != string::npos) {
                if(i == b) {
		    note_printf(pbreply,
				"%s: can only take %d arguments",
				s.c_str(), b);
                    return -1;
                }
                size_t idx1 = param_value.find(",", idx);
                string substr(param_value, idx, idx1 - idx);
                int result = do_one_item(item_type_char, s, substr, item, i, pbreply);
                if(result != 0) return result;
                i++;
                idx = idx1 == string::npos ? idx1 : idx1 + 1;
            }
        } else {
            char item_type_char = item_type_string[0];
            int result = do_one_item(item_type_char, s, param_value, item, 0, pbreply);
            if (result != 0) return result;
        }
    }
    return 0;
}

// kthreads:
// only instance args are exported in sysfs, module params are not
// see RTAPI_IP_MODEin src/rtapi/rtapi.h
// therefore, if we see a param on kthreads newinst, we just
// overwrite the previous value via sysfs
static int do_kmodinst_args(const string &comp,
			  pbstringarray_t args,
			  pb::Container &pbreply)
{
    for (int i = 0; i < args.size(); i++) {
        string s(args.Get(i));
	remove_quotes(s);
        size_t idx = s.find('=');
        if(idx == string::npos) {
	    note_printf(pbreply, "Invalid parameter `%s'",
			s.c_str());
            return -1;
        }
        string param_name(s, 0, idx);
        string param_value(s, idx+1);

	// ls /sys/module/brd/parameters/
	// max_part  rd_nr  rd_size

	string path = "/sys/module/" + comp + "/parameters/" + param_name;
	struct stat sb;
	if (stat(path.c_str(), &sb) < 0) {
	    // if param_name is an instance param, it's exported in sysfs
	    note_printf(pbreply, "newinst '%s': no such instance parameter '%s'",
			comp.c_str(),
			param_name.c_str());
	    return -ENOENT;
	}
	int retval = procfs_cmd(path.c_str(), param_value.c_str());
	if (retval < 0) {
	    note_printf(pbreply, "newinst %s: setting param %s to %s failed:  %d - %s",
			comp.c_str(),
			param_name.c_str(),
			param_value.c_str(),
			retval,
			strerror(-retval));
	    return retval;
	}
    }
    return 0;
}

static const char **pbargv(const pbstringarray_t &args)
{
    const char **argv, **s;
    s = argv = (const char **) calloc(sizeof(char *), args.size() + 1);
    for (int i = 0; i < args.size(); i++) {
	*s++ = args.Get(i).c_str();
    }
    *s = NULL;
    return argv;
}

static inline bool kernel_threads(flavor_ptr f) {
    assert(f);
    return (f->flags & FLAVOR_KERNEL_BUILD) != 0;
}


static void usrfunct_error(const int retval,
			   const string &func,
			   pbstringarray_t args,
			   pb::Container &pbreply)
{
    if (retval >= 0) return;
    string s = pbconcat(args);
    note_printf(pbreply, "hal_call_usrfunct(%s,%s) failed: %d - %s",
		func.c_str(), s.c_str(), retval, strerror(-retval));
}

// split arg array into key=value, others
static void separate_kv(pbstringarray_t &kvpairs,
		    pbstringarray_t &leftovers,
		    const pbstringarray_t &args)
{
    for(int i = 0; i < args.size(); i++) {
        string s(args.Get(i));
	remove_quotes(s);
        if (s.find('=') == string::npos)
	    leftovers.Add()->assign(s);
	else
	    kvpairs.Add()->assign(s);
    }
}

static int do_newinst_cmd(int instance,
			  string comp,
			  string instname,
			  pbstringarray_t args,
			  pb::Container &pbreply)
{
    int retval = -1;


    if (kernel_threads(flavor)) {
	string s = pbconcat(args);
	retval = do_kmodinst_args(comp,args,pbreply);
	if (retval) return retval;
	return procfs_cmd(PROCFS_RTAPICMD,"call newinst %s %s %s",
			  comp.c_str(),
			  instname.c_str(),
			  s.c_str());
    } else {
	if (call_usrfunct == NULL) {
	    pbreply.set_retcode(1);
	    pbreply.add_note("this HAL library version does not support user functions - version problem?");
	    return -1;
	}
	void *w = modules[comp];
	if (w == NULL) {
	    // if newinst via halcmd, it should have been automatically loaded already
	    note_printf(pbreply,
			"newinst: component '%s' not loaded",
			comp.c_str());
	    return -1;
	}
	dlerror();
	string s = pbconcat(args);

	pbstringarray_t kvpairs, leftovers;

	separate_kv(kvpairs, leftovers, args);

	// set the instance parameters
	retval = do_module_args(w, kvpairs, RTAPI_IP_SYMPREFIX, pbreply);
	if (retval < 0) {
	    note_printf(pbreply,
			"passing args for '%s' failed: '%s'",
			instname.c_str(), s.c_str());
	    return retval;
	}
	rtapi_print_msg(RTAPI_MSG_DBG,
			"%s: instargs='%s'\n",__FUNCTION__,
			s.c_str());

	// massage the argv for the newinst user function,
	// and call it
	pbstringarray_t a;
	a.Add()->assign(comp);
	a.Add()->assign(instname);
	a.MergeFrom(leftovers);
	const char **argv = pbargv(a); // pass non-kv pairs only
	int ureturn = 0;
	retval = call_usrfunct("newinst", a.size(), argv, &ureturn );
	if (argv) free(argv);
	if (retval == 0) retval = ureturn;
	usrfunct_error(retval, "newinst", args, pbreply);
    }
    return retval;
}

static int do_delinst_cmd(int instance,
			  string instname,
			  pb::Container &pbreply)
{
    int retval = -1;
    string s;


    if (kernel_threads(flavor)) {
	return procfs_cmd(PROCFS_RTAPICMD,"call delinst %s", instname.c_str());
    } else {
	if (call_usrfunct == NULL) {
	    pbreply.set_retcode(1);
	    pbreply.add_note("this HAL library version does not support user functions - version problem?");
	    return -1;
	}
	pbstringarray_t a;
	a.Add()->assign(instname);
	const char **argv = pbargv(a);
	int ureturn = 0;
	retval = call_usrfunct("delinst", a.size(), argv, &ureturn);
	if (argv) free(argv);
	if (retval == 0) retval = ureturn;
	usrfunct_error(retval, "delinst", a, pbreply);
    }
    return retval;
 }

static int do_callfunc_cmd(int instance,
			   string func,
			   pbstringarray_t args,
			   pb::Container &pbreply)
{
    int retval = -1;

    if (kernel_threads(flavor)) {
	string s = pbconcat(args);
	return procfs_cmd(PROCFS_RTAPICMD,"call %s %s", func.c_str(), s.c_str());
    } else {
	if (call_usrfunct == NULL) {
	    pbreply.set_retcode(1);
	    pbreply.add_note("this HAL library version does not support user functions - version problem?");
	    return -1;
	}
	const char **argv = pbargv(args);
	int ureturn = 0;
	retval = call_usrfunct(func.c_str(),
			       args.size(),
			       argv,
			       &ureturn);
	if (argv) free(argv);
	if (retval == 0) retval = ureturn;
	usrfunct_error(retval, func, args, pbreply);
    }
    return retval;
}



static int do_load_cmd(int instance,
		       string name,
		       pbstringarray_t args,
		       pb::Container &pbreply)
{
    void *w = modules[name];
    char module_name[PATH_MAX];
    void *module;
    int retval;

    if (w == NULL) {
	if (kernel_threads(flavor)) {
	    string cmdargs = pbconcat(args);
	    retval = run_module_helper("insert %s %s", name.c_str(), cmdargs.c_str());
	    if (retval) {
		note_printf(reply, "couldnt insmod %s - see dmesg\n", name.c_str());
	    } else {
		modules[name] = (void *) -1;  // so 'if (modules[name])' works
		loading_order.push_back(name);
	    }
	    return retval;
	} else {
	    strncpy(module_name, (name + flavor->mod_ext).c_str(),
		    PATH_MAX);
	    module = modules[name] = dlopen(module_name, RTLD_GLOBAL |RTLD_NOW);
	    if (!module) {
		rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlopen: %s\n",
				name.c_str(), dlerror());
		const char *rpath = rtapi_get_rpath();
		note_printf(reply, "%s: dlopen: %s",
			    __FUNCTION__,dlerror());
		note_printf(reply, "rpath=%s",	rpath == NULL ? "" : rpath);
		if (rpath)
		    free((void *)rpath);
		return -1;
	    }
	    // retrieve the address of rtapi_switch_struct
	    // so rtapi functions can be called and members
	    // accessed
	    if (rtapi_switch == NULL) {
		rtapi_get_handle_t rtapi_get_handle;
		dlerror();
		rtapi_get_handle = (rtapi_get_handle_t) dlsym(module,
							      "rtapi_get_handle");
		if (rtapi_get_handle != NULL) {
		    rtapi_switch = rtapi_get_handle();
		    assert(rtapi_switch != NULL);
		}
	    }
	    /// XXX handle arguments
	    int (*start)(void) = DLSYM<int(*)(void)>(module, "rtapi_app_main");
	    if(!start) {
		note_printf(reply, "%s: dlsym: %s\n",
			    name.c_str(), dlerror());
		return -1;
	    }
	    int result;

	    result = do_module_args(module, args, RTAPI_MP_SYMPREFIX, pbreply);
	    if(result < 0) { dlclose(module); return -1; }

	    // need to call rtapi_app_main with as root
	    // RT thread creation and hardening requires this
	    if ((result = start()) < 0) {
		note_printf(reply, "rtapi_app_main(%s): %d %s\n",
			    name.c_str(), result, strerror(-result));
		modules.erase(modules.find(name));
		return result;
	    }
	    loading_order.push_back(name);

	    rtapi_print_msg(RTAPI_MSG_DBG, "%s: loaded from %s\n",
			    name.c_str(), module_name);
	    return 0;
	}
    } else {
	note_printf(reply, "%s: already loaded\n", name.c_str());
	return -1;
    }
}

 static int do_unload_cmd(int instance, string name, pb::Container &reply)
{
    void *w = modules[name];
    int retval = 0;

    if (w == NULL) {
	note_printf(reply, "unload: '%s' not loaded\n",
		    name.c_str());
	return -1;
    } else {
	if (kernel_threads(flavor)) {
	    retval = run_module_helper("remove %s", name.c_str());
	    if (retval) {
		note_printf(reply,  "couldnt rmmod %s - see dmesg\n",
				name.c_str());
		return retval;
	    } else {
		modules.erase(modules.find(name));
		remove_module(name);
	    }
	} else {
	    int (*stop)(void) = DLSYM<int(*)(void)>(w, "rtapi_app_exit");
	    if (stop)
		stop();
	    modules.erase(modules.find(name));
	    remove_module(name);
	    dlclose(w);
	}
    }
    rtapi_print_msg(RTAPI_MSG_DBG, " '%s' unloaded\n", name.c_str());
    return retval;
}

// shut down the stack in reverse loading order
static void exit_actions(int instance)
{
    pb::Container reply;
    size_t index = loading_order.size() - 1;
    for(std::vector<std::string>::reverse_iterator rit = loading_order.rbegin();
	rit != loading_order.rend(); ++rit, --index) {
	do_unload_cmd(instance, *rit, reply);
    }
}

static int init_actions(int instance)
{
    int retval;
    char moddir[PATH_MAX];

    get_rtapi_config(moddir,"MODULES",PATH_MAX);

    if (kernel_threads(flavor)) {
	// kthreads cant possibly run without shmdrv, so bail
	// also, cannot load it here because rtapi_msgd already needs this
	// so it'd be too late here
	if (!is_module_loaded("shmdrv")) {
	    rtapi_print_msg(RTAPI_MSG_ERR, "shmdrv not loaded");
	    return -1;
	}
	// leftovers or running session?
	if (is_module_loaded("rtapi")) {
	    rtapi_print_msg(RTAPI_MSG_ERR, "rtapi already loaded");
	    return -1;
	}
	if (is_module_loaded("hal_lib")) {
	    rtapi_print_msg(RTAPI_MSG_ERR, "hal_lib already loaded");
	    return -1;
	}
	char *m = strtok(moddir, "\t ");
	while (m != NULL) {
	    char cmdline[PATH_MAX];
	    if (!strcmp(m, "rtapi")) {
		snprintf(cmdline, sizeof(cmdline),
			 "insert %s rtapi_instance=%d", m, instance_id);
	    } else {
		snprintf(cmdline, sizeof(cmdline), "insert %s", m);
	    }

	    rtapi_print_msg(RTAPI_MSG_DBG, "running '%s'", cmdline);
	    retval = run_module_helper(cmdline);
	    if (retval) {
		rtapi_print_msg(RTAPI_MSG_ERR,
				"linuxcnc_module_helper '%s' failed - see dmesg\n",
				cmdline);
		return retval;
	    } else
		rtapi_print_msg(RTAPI_MSG_DBG, "'%s' loaded\n", m);
	    m = strtok(NULL,  "\t ");
	}
    }
    pb::Container reply;
    retval =  do_load_cmd(instance, "rtapi", pbstringarray_t(), reply);
    if (retval)
	return retval;
    if ((retval = do_load_cmd(instance, "hal_lib", pbstringarray_t(), reply)))
	return retval;

    if (!kernel_threads(flavor)) {
	// resolve the "hal_call_usrfunct" for later - callfunc, newinst & delinst need it
	void *hallib = modules["hal_lib"];
	dlerror();
	call_usrfunct = (hal_call_usrfunct_t) dlsym(hallib, "hal_call_usrfunct");

	if (call_usrfunct == NULL) {
	    rtapi_print_msg(RTAPI_MSG_ERR,
			    "cant resolve 'hal_call_usrfunct' in hal_lib - version problem?");
	    char *s = dlerror();
	    if (s)
		rtapi_print_msg(RTAPI_MSG_ERR, "dlsym(hal_call_usrfunct): '%s'", s);
	    return -1;
	}
    }
    return 0;
}


static int attach_global_segment()
{
    int retval = 0;
    int globalkey = OS_KEY(GLOBAL_KEY, instance_id);
    int size = 0;
    int tries = 10; // 5 sec deadline for msgd/globaldata to come up

    shm_common_init();
    do {
	retval = shm_common_new(globalkey, &size,
				instance_id, (void **) &global_data, 0);
	if (retval < 0) {
	    tries--;
	    if (tries == 0) {
		syslog_async(LOG_ERR,
		       "rtapi_app:%d: ERROR: cannot attach global segment key=0x%x %s\n",
		       instance_id, globalkey, strerror(-retval));
		return retval;
	    }
	    struct timespec ts = {0, 500 * 1000 * 1000}; //ms
	    nanosleep(&ts, NULL);
	}
    } while (retval < 0);

    if (size != sizeof(global_data_t)) {
	syslog_async(LOG_ERR,
	       "rtapi_app:%d global segment size mismatch: expect %zu got %d\n",
	       instance_id, sizeof(global_data_t), size);
	return -EINVAL;
    }

    tries = 10;
    while  (global_data->magic !=  GLOBAL_READY) {
	tries--;
	if (tries == 0) {
	    syslog_async(LOG_ERR,
		   "rtapi_app:%d: ERROR: global segment magic not changing to ready: magic=0x%x\n",
		   instance_id, global_data->magic);
	    return -EINVAL;
	}
	struct timespec ts = {0, 500 * 1000 * 1000}; //ms
	nanosleep(&ts, NULL);
    }
    return retval;
}


// handle commands from zmq socket
static int rtapi_request(zloop_t *loop, zmq_pollitem_t *poller, void *arg)
{
    zmsg_t *r = zmsg_recv(poller->socket);
    char *origin = zmsg_popstr (r);
    zframe_t *request_frame  = zmsg_pop (r);
    static bool force_exit = false;

    pb::Container pbreq, pbreply;

    if (!pbreq.ParseFromArray(zframe_data(request_frame),
			      zframe_size(request_frame))) {
	rtapi_print_msg(RTAPI_MSG_ERR, "cant decode request from %s (size %zu)",
			origin ? origin : "NULL",
			zframe_size(request_frame));
	zmsg_destroy(&r);
	return 0;
    }
    if (debug) {
	string buffer;
	if (TextFormat::PrintToString(pbreq, &buffer)) {
	    fprintf(stderr, "request: %s\n",buffer.c_str());
	}
    }

    pbreply.set_type(pb::MT_RTAPI_APP_REPLY);

    switch (pbreq.type()) {
    case pb::MT_RTAPI_APP_PING:
	char buffer[LINELEN];
	snprintf(buffer, sizeof(buffer),
		 "pid=%d flavor=%s gcc=%s git=%s",
		 getpid(),flavor->name,  __VERSION__, GIT_VERSION);
	pbreply.add_note(buffer);
	pbreply.set_retcode(0);
	break;

    case pb::MT_RTAPI_APP_EXIT:
	assert(pbreq.has_rtapicmd());
	exit_actions(pbreq.rtapicmd().instance());
	force_exit = true;
	pbreply.set_retcode(0);
	break;

    case pb::MT_RTAPI_APP_CALLFUNC:

	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_func());
	assert(pbreq.rtapicmd().has_instance());
	pbreply.set_retcode(do_callfunc_cmd(pbreq.rtapicmd().instance(),
					      pbreq.rtapicmd().func(),
					      pbreq.rtapicmd().argv(),
					      pbreply));
	break;

    case pb::MT_RTAPI_APP_NEWINST:
	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_comp());
	assert(pbreq.rtapicmd().has_instname());
	assert(pbreq.rtapicmd().has_instance());
	pbreply.set_retcode(do_newinst_cmd(pbreq.rtapicmd().instance(),
					   pbreq.rtapicmd().comp(),
					   pbreq.rtapicmd().instname(),
					   pbreq.rtapicmd().argv(),
					   pbreply));
	break;

    case pb::MT_RTAPI_APP_DELINST:

	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_instname());
	assert(pbreq.rtapicmd().has_instance());
	pbreply.set_retcode(do_delinst_cmd(pbreq.rtapicmd().instance(),
					   pbreq.rtapicmd().instname(),
					   pbreply));
	break;


    case pb::MT_RTAPI_APP_LOADRT:
	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_modname());
	assert(pbreq.rtapicmd().has_instance());
	pbreply.set_retcode(do_load_cmd(pbreq.rtapicmd().instance(),
					pbreq.rtapicmd().modname(),
					pbreq.rtapicmd().argv(),
					pbreply));
	break;

    case pb::MT_RTAPI_APP_UNLOADRT:
	assert(pbreq.rtapicmd().has_modname());
	assert(pbreq.rtapicmd().has_instance());

	pbreply.set_retcode(do_unload_cmd(pbreq.rtapicmd().instance(),
					  pbreq.rtapicmd().modname(),
					  pbreply));
	break;

    case pb::MT_RTAPI_APP_LOG:
	assert(pbreq.has_rtapicmd());
	if (pbreq.rtapicmd().has_rt_msglevel()) {
	    global_data->rt_msg_level = pbreq.rtapicmd().rt_msglevel();
	}
	if (pbreq.rtapicmd().has_user_msglevel()) {
	    global_data->user_msg_level = pbreq.rtapicmd().user_msglevel();
	}
	pbreply.set_retcode(0);
	break;

    case pb::MT_RTAPI_APP_NEWTHREAD:
	assert(pbreq.has_rtapicmd());
	assert(pbreq.rtapicmd().has_threadname());
	assert(pbreq.rtapicmd().has_threadperiod());
	assert(pbreq.rtapicmd().has_cpu());
	assert(pbreq.rtapicmd().has_use_fp());
	assert(pbreq.rtapicmd().has_instance());

	if (kernel_threads(flavor)) {
	    int retval =  procfs_cmd(PROCFS_RTAPICMD,"newthread %s %d %d %d",
					   pbreq.rtapicmd().threadname().c_str(),
					   pbreq.rtapicmd().threadperiod(),
					   pbreq.rtapicmd().use_fp(),
					   pbreq.rtapicmd().cpu());
	    pbreply.set_retcode(retval < 0 ? retval:0);

	} else {
	    void *w = modules["hal_lib"];
{'fraction_non_alphanumeric': 0.14144133365238892, 'fraction_numerical': 0.007776980138789184, 'mean_word_length': 3.343032822378107, 'pattern_counts': {'":': 0, '<': 116, '<?xml version=': 0, '>': 120, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1966010', 'n_tokens_mistral': 19665, 'n_tokens_neox': 18345, 'n_words': 4397}