From 83b47e6337f7613322b0ad81ea2472c4630d04e3 Mon Sep 17 00:00:00 2001 From: Bertho Stultiens Date: Fri, 7 Aug 2026 12:04:09 +0200 Subject: [PATCH] hal: Rewrite most of halrmt to become functional and use the query API. --- docs/src/man/man1/halrmt.1.adoc | 441 ++-- src/hal/utils/Submakefile | 6 +- src/hal/utils/halrmt.c | 3593 ------------------------------- src/hal/utils/halrmt.cc | 3320 ++++++++++++++++++++++++++++ 4 files changed, 3585 insertions(+), 3775 deletions(-) delete mode 100644 src/hal/utils/halrmt.c create mode 100644 src/hal/utils/halrmt.cc diff --git a/docs/src/man/man1/halrmt.1.adoc b/docs/src/man/man1/halrmt.1.adoc index 9086723abe6..65dd59824bc 100644 --- a/docs/src/man/man1/halrmt.1.adoc +++ b/docs/src/man/man1/halrmt.1.adoc @@ -2,221 +2,303 @@ == NAME -halrmt - remote-control interface for LinuxCNC +halrmt - remote-control interface for LinuxCNC/HAL == SYNOPSIS -*halrmt* [*--port* __] [*--name* __] [*--connectpw* __] [*--enablepw* __] [*--sessions* __] [*-ini* __] +*halrmt* [*--port* __] [*--name* __] [*--connectpw* __] [*--enablepw* __] [*--sessions* __] [*--ini* __] == OPTIONS ---port _port_:: Waits for socket connections (telnet) on specified -socket, without port specification it uses default port 5006. -(Note: linuxcncrsh uses 5007 as default.) ---name _server_name_:: Sets the server name to specified name for Hello. ---connectpw _password_:: Sets the connection password to 'password'. Default: EMC. ---enablepw _password_:: Sets the enable password to _password_. Default EMCTOO. ---sessions :: Sets the maximum number of simultaneous connextions to max sessions. Default is no limit (-1). --ini __:: Uses the specified _INI file_ instead of the default emc.ini. +-h,--help:: + Print a brief usage help message. -== DESCRIPTION - -The application *halrmt* supports six commands that are meant to be sent -to an instance of HAL that is running on another machine. Of these, the commands _set_ and _get_ contain HAL specific sub-commands that are based -on the commands supported by _halcmd_. - -Commands and most parameters are not case sensitive. -The exceptions are passwords, file paths and text strings. -The supported commands are as follows: - -Hello :: -If a valid password was entered the server will respond with "HELLO ACK -__ __" where server name and server version are -looked up from the implementation. If an invalid password or any other -syntax error occurs then the server responds with "HELLO NAK". - -Get:: The get command includes one of the HAL sub-commands, -described below and zero or more additional parameters. -Set:: The set command inclides one of the HAL sub-commands, -described below and one or more additional parameters. -Quit:: The quit command disconnects the associated socket connection. -Shutdown:: The shutdown command tells LinuxCNC to shut down -before quitting the connection. This command may only be issued if the -Hello has been successfully negotiated and the connection has control of -the CNC (see enable sub-command below). This command has no parameters. -Help:: The help command will return help information in text format over the -telnet connection. If no parameters are specified, it will itemize the -available commands. If a command is specified, it will provide usage -information for the specified command. Help will respond regardless of -whether a "Hello" has been successfully negotiated. - -*HAL sub-commands:* - -echo on | off:: With get will return the current echo -state, with set, sets the echo state. When echo is on, all commands will -be echoed upon receipt. This state is local to each connection. - -verbose on | off:: With get will return the current verbose state, with set, sets -the verbose state. When in verbose mode is on, all set commands return -positive acknowledgement in the form SET ACK. In addition, -text error messages will be issued when in verbose mode. This state is -local to each connection. - -enable | off:: With get will return On or -Off to indicate whether the current connection is enabled to perform -control functions. With set and a valid password, the current connection -is enabled for control functions. "OFF" may not be used as a password -and disables control functions for this connection. - -config [TBD] comm_mode ascii | binary:: With get, will return the current -communications mode. With set, will set the communications mode to the -specified mode. The binary protocol is TBD. - -comm_prot :: With get, returns the current protocol version used by the server, -with set, sets the server to use the specified protocol version, provided it is -lower than or equal to the highest version number supported by the server implementation. - -Comps []:: -Get only, returns all components beginning with the specified substring. -If no substring is specified then it returns all components. - -Pins []:: -Get only, returns all information about all pins beginning with the -specified substring. If no substring is specified then it returns all -pins. - -PinVals []:: -Get only, returns only value information about all pins beginning with -the specified substring. If no substring is specified then it returns -all pins. - -Signals []:: -Get only, returns all information about all signals beginning with the -specified substring. If no substring is specified then it returns all -signals. - -SigVals []:: -Get only, returns only value information about all signals beginning -with the specified substring. If no substring is specified then it -returns all pins. - -Params []:: -Get only, returns all information about all parameters beginning with -the specified substring. If no substring is specified then it returns -all parameters. - -ParamVals []:: -Get only, returns only value information about all parameters beginning -with the specified substring. If no substring is specified then it -returns all pins parameters. - -Functs []:: -Get only, returns all information about all functions beginning with the -specified substring. If no substring is specified then it returns all -functions. - -Threads:: -Get only, returns all information about all functions. +-n,--name _server_name_:: + Sets the server name to specified name for in reply to `HELLO` command. + Default: EMCNETSVR. -Comp :: -Get only, returns the component matching the specified name. +-p,--port _port_:: + Listen on TCP port _port_ for connection. + Both IPv4 and IPv6 connections are accepted. + Default port is 5006. + (Note: linuxcncrsh uses 5007 as default) -Pin :: -Get only, returns all information about the pin matching the specified name. +-w,--connectpw _password_:: + Sets the `HELLO` command password to _password_. + Default: EMC. -PinVal :: -Get only, returns the value of the pin matching the specified name. +-e,--enablepw _password_:: + Sets the `SET ENABLE` command password to _password_. + Default: EMCTOO. -Sig :: -Get only, returns all information about the pin matching the specified name. +-s,--sessions _max_sessions_:: + Sets the maximum number of simultaneous connextions to _max_sessions_. + Default is no limit (0). -SigVal :: -Get only, returns just the value of the signal matching the specified name. +-i,--ini _INI_file_:: + Uses the specified _INI_file_. + The default is to get the _INI_file_ name from the environment variable `INI_FILE_NAME`. -Param :: -Get only, returns all information about the parameter matching the specified name. +-q,--quiet:: + Don't print informational messages to the console. -ParamVal :: -Get only, returns just the value of the parameter matching the specified name. - -Funct :: -Get only, returns all information about the parameter matching the -specified name. - -Thread :: -Get only, returns all information about the thread matching the -specified name. - -LoadRt :: -Set only, loads the real time executable specified by name. - -Unload :: -Set only, unloads the executable specified by name. - -LoadUsr :: -Set only, loads the user executable specified by name. - -Linkps :: -Set only, links the specified pin to the specified signal. - -Linksp :: -Set only, links the specified signal to the specified pin. - -Linkpp :: -Set only, links the pin specified by pin 1 with the pin specified by pin 2. - -Net :: -Set only, nets the specified net list. - -Unlinkp :: -Set only, unlinks the specified pins. - -Lock +== DESCRIPTION -Unlock +The application *halrmt* supports six primary commands to manipulate the HAL of a remote LinuxCNC instance. +Of these, the commands `SET` and `GET` contain HAL specific sub-commands. +The sub-commands are based on the commands supported by *halcmd*(1). -NewSig :: -Set only, creates the signal specified by name and of type specified by -type. +=== COMMANDS -DelSig :: -Set only, deletes the signal specified by name. +Commands, sub-commands and most parameters are not case sensitive. +The exceptions are passwords, file paths and text strings. -SetP :: -Set only, sets the parameter specified by name to the value specified by -value. +The supported commands are as follows: -SetS :: -Set only, sets the signal specified by name to the value specified by -value. +hello []:: + If a valid _password_ was entered the server will respond with + + `HELLO ACK __ __` + + where server name and server version are looked up from the implementation. + If an invalid _password_ or any other syntax error occurs then the server responds with `HELLO NAK`. + +get [args...]:: + The get command includes one of the HAL sub-commands, + described below and zero or more additional parameters. + +set [args...]:: + The set command includes one of the HAL sub-commands, + described below and one or more additional parameters. + +quit:: + The quit command disconnects the associated socket connection. + +shutdown:: + The shutdown command disconnects all clients and tells *halrmt* to shut down. + This command may only be issued if the `HELLO` has been successfully negotiated. + The connection must be enabled. + +help [sub-command]:: + The help command will return information in text over the network connection. + If no parameters are specified, it will itemize the available commands. + If a command is specified, it will provide usage information for the specified command. + Command `HELP` will respond regardless of whether a `HELLO` has been successfully negotiated. + +=== SUBCOMMANDS + +A 'pattern' is a glob pattern as the shell implements it. +Backslash characters in a 'pattern' are not special (don't escape) and are case insensitive. + +set addf [arg [arg...]]:: + Adds the function specified by _name_, to the thread specified by _thread_. + Optional parameters may be specified by _arg_. + +get comp :: + Returns the component matching the specified _name_. + +get comps [pattern]:: + Returns information about all components matching _pattern_. + If no _pattern_ is specified then all components are returned. + +set delf :: + Deletes the function specified by _name_. + +set delsig |all:: + Deletes the signal specified by _signalname_. + Specifying 'all' will remove all signals in the system. + +get echo:: +set echo {on|off}:: + With get will return the current echo state. + With set, sets the echo state to the supplied argument. + When echo is on, all commands will be echoed upon receipt. + The _echo_ state is local to each connection. + + The echoed line is INI-variable expanded if `SET EXPAND` is set to 'On'. + This allows you to see the actual command being executed. + +get enable:: +set enable |off:: + With get will return 'On' or 'Off' to indicate whether the current connection is enabled to perform control functions. + With set and a valid password, the current connection is enabled for control functions. + The argument 'OFF' may not be used as a password and disables control functions for this connection. + +get expand:: +set expand {on|off}:: + Get or set INI-file variable expansion. + Setting the INI-file expansion to _on_ will pre-process every input line and substitute variables. + An INI-file variable is specified by `[SECTION]VAR` or `[SECTION](VAR)`. + Not using parentheses requires you to have trailing white-space or be at the end of the line. + INI-file expansion defaults to off and requires a valid INI-file. + Enabling expansion with no or an invalid INI-file will expand to an empty string. + Expansion is applied to the input line _before_ it is tokenized. + That means that all commands and sub-commands are subject to expansion. + Everything typed as input is first expanded before processed. + The _expand_ state is local to each connection. + +get funct :: + Returns all information about the parameter matching the specified _name_. + +get functs [pattern]:: + Returns information about functions matching _pattern_. + If no _pattern_ is specified then all functions are returned. + +get ini
:: + Return the variable \[__section__]_var_ from the current INI-file. + +get inifile:: +set inifile :: + Gets the current active INI-file's name or sets the active INI-file to _filename_. + A test is performed to validate the new INI-file when set. + Setting the INI-file is local to the connection. + +set linkps []:: + Add _pinname_ to signal with _signalname_. + Will act as `SET UNLINKP _pinname_` if no _signalname_ is provided. + +set linksp :: + Add _pinname_ to signal with _signalname_. + +set loadrt [arg [arg...]]:: + Load the real-time component _compname_ with optional arguments. + The real-time environment must have been started or an error will be returned. + +set loadusr [flags] [arg [arg...]]:: + Loads the user component _program_ and passes any optional arguments. + The _flags_ are on or more of: + * *-W* - to wait for the component to become ready. + The component is assumed to have the same name as the first argument of the command. + Use *-n* to override the component name to look for. + * *-n* _name_ - to wait for the component, which will have the given _name_. + * *-w* - to wait for the program to exit + * *-i* - to ignore the program return value (with *-w*) + +get lock:: +set lock {all|load|config|params|run|tune|none}:: + Gets the current HAL lock value or sets the HAL lock of the argument's value. + The _tune_ value is a combination of _load_ and _config_. + The values _all_, _none_ and _tune_ override any other values in the lock that may be present. + Doing `SET LOCK none` is equivalent to `SET UNLOCK all`. + +get net :: +set net [pinname [pinname...]]:: + Get shows all the pins connected to _signalname_. + Set creates a signal _signalname_ and adds all the _pinname_ pins to the signal. + +set newsig :: + Creates the signal specified by _signalname_ and of type specified by _type_. + +get param :: + Returns all information about the parameter matching the specified _paramname_. + +get params [pattern]:: + Returns information about parameters matching _pattern_. + If no _pattern_ is specified then all parameters are returned. + +get paramval :: + Returns just the value of the parameter matching the specified _paramname_. + +get paramvals [pattern]:: + Returns only value information about all parameters matching _pattern_. + If no _pattern_ is specified then all parameters are returned. + +get pin :: + Returns all information about the pin matching the specified _pinname_. + +get pins [pattern]:: + Returns all information about all pins matching _pattern_. + If no _pattern_ is specified then all pins are returned. + +get pinval :: + Returns the value of the pin matching the specified _pinname_. + +get pinvals [pattern]:: + Returns only value information about all pins matching _pattern_. + If no _pattern_ is specified then all pins are returned. + +set save {all|comp|sig|link|linka|net|neta|param|thread} []:: + Will return the saved content on the connection if no filename is provided. + Saves the specified set of data to file _filename_ instead if provided. + +set setp :: + Sets the pin or parameter specified by _pinname_/_paramname_ to the value specified by _value_. + +set sets :: + Sets the signal specified by _signalname_ to the value specified by _value_. + +get signal :: + Returns all information about the pin matching the specified _signalname_. + +get signals [pattern]:: + Returns all information about all signals matching _pattern_. + If no _pattern_ is specified then all signals are returned. + +get sigval :: + Returns just the value of the signal matching the specified _signalname_. -AddF []:: -Set only, adds the function specified by name, to the thread specified -by thread, with the optional parameters specified by parameters. +get sigvals [pattern]:: + Returns only value information about all signals matching _pattern_. + If no _pattern_ is specified then all signals are returned. + +set start:: + Start the real-time threads. + The real-time environment must have been started or an error will be returned. + +set stop:: + Stop the real-time threads. + The real-time environment must have been started or an error will be returned. + +get time:: + Returns the time, in seconds, from the start of the epoch. + The starting time depends on the platform. -DelF :: -Set only, deletes the function specified by name. +get timestamp:: +set timestamp {on|off} [{on|off}]:: + Enable or disable printing of a timestamp on messages sent back from the server to the user. + The optional format argument selects human readable date/time format and seconds since the epoch. + The format prints seconds when off and human readable when on. + +get thread :: + Returns all information about the thread matching the specified _name_. + +get threads [pattern]:: + Returns all information about all threads matching _pattern_. + If no _pattern_ is specified then all threads are returned. + +set unlinkp :: + Unlinks the specified _pinname_ from any signal it may be attached to. -Save +set unload :: + Unloads the real-time component _compname_. + The real-time environment must have been started or an error will be returned. -Start +set unlock {all|load|config|params|run|tune|none}:: + Removes the HAL lock of the argument's value. + The _tune_ value is a combination of _load_ and _config_. + Removing _none_ has no effect but may be useful in scripting. -Stop +get verbose:: +set verbose on | off:: + With get will return the current verbose state. + With set, sets the verbose state to the supplied argument. + When verbose mode is on, all `SET` commands return positive acknowledgement in the form `SET ACK`. + In addition, text error messages will be issued when in verbose mode. + The _verbose_ state is local to each connection. == SEE ALSO -linuxcnc(1) +*linuxcnc*(1), +*halcmd*(1). Much more information about LinuxCNC and HAL is available in the LinuxCNC and HAL User Manuals, found at /usr/share/doc/LinuxCNC/. == BUGS -It is not known if this interface currently works. +Probably, but it has been mostly rewritten after the last incarnation incurred som bit-rot and was deemed non-functional. == AUTHOR This man page written by Andy Pugh, as part of the LinuxCNC project. +Manual page updated by B.Stultiens after the rewrite. == REPORTING BUGS @@ -224,7 +306,8 @@ Report bugs at https://github.com/LinuxCNC/linuxcnc/issues. == COPYRIGHT -Copyright © 2020 Andy Pugh. +Copyright © 2020 Andy Pugh. + +Copyright © 2026 B.Stultiens. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR diff --git a/src/hal/utils/Submakefile b/src/hal/utils/Submakefile index 69d17f766e1..3c980d256d1 100644 --- a/src/hal/utils/Submakefile +++ b/src/hal/utils/Submakefile @@ -27,12 +27,12 @@ TARGETS += ../tcl/hal.so $(Q)$(CXX) $(LDFLAGS) -o $@ $^ $(READLINE_LIBS) -lfmt TARGETS += ../bin/halcmd -HALRMTSRCS := hal/utils/halrmt.c +HALRMTSRCS := hal/utils/halrmt.cc hal/utils/setps_util.c USERSRCS += $(HALRMTSRCS) -../bin/halrmt: $(call TOOBJS, $(HALRMTSRCS)) ../lib/liblinuxcnchal.so.0 +../bin/halrmt: $(call TOOBJS, $(HALRMTSRCS)) ../lib/liblinuxcnchal.so.0 ../lib/liblinuxcncini.so.1 $(ECHO) Linking $(notdir $@) - $(Q)$(CC) $(LDFLAGS) -o $@ $^ -lpthread + $(Q)$(CXX) $(LDFLAGS) -o $@ $^ -lfmt TARGETS += ../bin/halrmt ifneq ($(GTK_VERSION),) diff --git a/src/hal/utils/halrmt.c b/src/hal/utils/halrmt.c deleted file mode 100644 index 6d9ad460dd8..00000000000 --- a/src/hal/utils/halrmt.c +++ /dev/null @@ -1,3593 +0,0 @@ -/******************************************************************** -* Description: halrmt.cc -* Simple telnet interface to LinuxCNC HAL commands (halcmd) -* -* Derived from work by jmkasunich -* -* Other contributors: -* Alex Joni -* -* Author: Eric H. Johnson -* License: GPL Version 2 -* System: Linux -* -* Copyright (c) 2006-2008 All rights reserved. -* -* Last change: -********************************************************************/ - -/******************************************************************* - Using halrmt: - - halrmt {-- --port --name --connectpw - --enablepw --sessions -ini } - - With -- --port Waits for socket connections (Telnet) on specified socket, without port - uses default port 5006. (note: linuxcncrsh uses 5007 as default) - With -- --name Sets the server name to specified name for Hello. - With -- --connectpw Sets the connection password to 'password'. Default EMC - With -- --enablepw Sets the enable password to 'password'. Default EMCTOO - With -- --sessions Sets the maximum number of simultaneous connextions - to max sessions. Default is no limit (-1). - With -- -ini , uses specified INI file instead of default emc.ini. - - There are six commands supported, Where the commands set and get contain HAL - specific sub-commands based on the commands supported by halcmd. Commands and - most parameters are not case sensitive. The exceptions are passwords, - file paths and text strings. - - The supported commands are as follows: - - ==> HELLO <== - - Hello - If a valid password was entered the server will respond with - - HELLO ACK - - Where server name and server version are looked up from the implementation. - if an invalid password or any other syntax error occurs then the server - responds with: - - HELLO NAK - - ==> Get <== - - The get command includes one of the hal sub-commands, described below and - zero or more additional parameters. - - ==> Set <== - - The set command inclides one of the hal sub-commands, described below and - one or more additional parameters. - - ==> Quit <== - - The quit command disconnects the associated socket connection. - - ==> Shutdown <== - - The shutdown command tells EMC to shutdown before quitting the connection. This - command may only be issued if the Hello has been successfully negotiated and the - connection has control of the CNC (see enable sub-command below). This command - has no parameters. - - ==> Help <== - - The help command will return help information in text format over the telnet - connection. If no parameters are specified, it will itemize the available commands. - If a command is specified, it will provide usage information for the specified - command. Help will respond regardless of whether a "Hello" has been - successfully negotiated. - - - HAL sub-commands: - - echo on | off - With get will return the current echo state, with set, sets the echo - state. When echo is on, all commands will be echoed upon receipt. This - state is local to each connection. - - verbose on | off - With get will return the current verbose state, with set, sets the - verbose state. When in verbose mode is on, all set commands return - positive acknowledgement in the form SET ACK. In addition, - text error messages will be issued when in verbose mode. This state - is local to each connection. - - enable | off - With get will return On or Off to indicate whether the current connection - is enabled to perform control functions. With set and a valid password, - the current connection is enabled for control functions. "OFF" may not - be used as a password and disables control functions for this connection. - - config [TBD] - - comm_mode ascii | binary - With get, will return the current communications mode. With set, will - set the communications mode to the specified mode. The binary protocol - is TBD. - - comm_prot - With get, returns the current protocol version used by the server, - with set, sets the server to use the specified protocol version, - provided it is lower than or equal to the highest version number - supported by the server implementation. - - Comps [] - - Get only, returns all components beginning with the specified substring. - If no substring is specified then it returns all components. - - Pins [] - - Get only, returns all information about all pins beginning with the - specified substring. If no substring is specified then it returns all pins. - - PinVals [] - - Get only, returns only value information about all pins beginning with the - specified substring. If no substring is specified then it returns all pins. - - Signals [] - - Get only, returns all information about all signals beginning with the - specified substring. If no substring is specified then it returns all signals. - - SigVals [] - - Get only, returns only value information about all signals beginning with the - specified substring. If no substring is specified then it returns all pins. - - Params [] - - Get only, returns all information about all parameters beginning with the - specified substring. If no substring is specified then it returns all - parameters. - - ParamVals [] - - Get only, returns only value information about all parameters beginning with the - specified substring. If no substring is specified then it returns all pins - parameters. - - Functs [] - - Get only, returns all information about all functions beginning with the - specified substring. If no substring is specified then it returns all - functions. - - Threads - - Get only, returns all information about all functions. - - Comp - - Get only, returns the component matching the specified name. - - Pin - - Get only, returns all information about the pin matching the specified - name. - - PinVal - - Get only, returns the value of the pin matching the specified name. - - Sig - - Get only, returns all information about the pin matching the specified - name. - - SigVal - - Get only, returns just the value of the signal matching the specified - name. - - Param - - Get only, returns all information about the parameter matching the - specified name. - - ParamVal - - Get only, returns just the value of the parameter matching the specified - name. - - Funct - - Get only, returns all information about the parameter matching the - specified name. - - Thread - - Get only, returns all information about the thread matching the - specified name. - - LoadRt - - Set only, loads the real time executable specified by name. - - Unload - - Set only, unloads the executable specified by name. - - LoadUsr - - Set only, loads the user executable specified by name. - - Linkps - - Set only, links the specified pin to the specified signal. - - Linksp - - Set only, links the specified signal to the specified pin. - - Linkpp - - Set only, links the pin specified by pin 1 with the pin specified by pin 2. - - Net - - Set only, nets the specified net list. - - Unlinkp - - Set only, unlinks the specified pins - - Lock - - Unlock - - NewSig - - Set only, creates the signal specified by name and of type specified by type. - - DelSig - - Set only, deletes the signal specified by name. - - SetP - - Set only, sets the parameter specified by name to the value specified by value. - - SetS - - Set only, sets the signal specified by name to the value specified by value. - - AddF [] - - Set only, adds the function specified by name, to the thread specified by - thread, with the optional parameters specified by parameters. - - DelF - - Set only, deletes the function specified by name. - - Save - - Start - - Stop - -****************************************************************************/ - -#include "config.h" - -#define _REENTRANT - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include // RTAPI realtime OS API -#include -#include // rtapi_strlcpy -#include // HAL public API decls -#include "../hal_priv.h" // private HAL decls - -/*********************************************************************** -* LOCAL FUNCTION DECLARATIONS * -************************************************************************/ - -/* These functions are used internally by this file. The code is at - the end of the file. */ - -#define MAX_TOK 20 -#define MAX_CMD_LEN 1024 -#define MAX_EXPECTED_SIGS 999 - -static int release_HAL_mutex(void); -static int do_help_cmd(char *command); -static int unloadrt_comp(char *mod_name); -static const char *data_type(int type); -static const char *pin_data_dir(int dir); -static const char *param_data_dir(int dir); -static const char *data_arrow1(int dir); -static const char *data_arrow2(int dir); -static char *data_value(int type, void *valptr); -static char *data_value2(int type, void *valptr); -static void save_comps(FILE *dst); -static void save_signals(FILE *dst); -static void save_links(FILE *dst, int arrows); -static void save_nets(FILE *dst, int arrows); -static void save_params(FILE *dst); -static void save_threads(FILE *dst); -static void print_help_general(int showR); - -/*********************************************************************** -* GLOBAL VARIABLES * -************************************************************************/ - -int comp_id = -1; /* -1 means hal_init() not called yet */ -int hal_flag = 0; /* used to indicate that halrmt might have the - hal mutex, so the sig handler can't just - exit, instead it must set 'done' */ -int done = 0; /* used to break out of processing loop */ -int linenumber=0; /* used to print linenumber on errors */ -int scriptmode = 0; /* used to make output "script friendly" (suppress headers) */ -int prompt_mode = 0; /* when getting input from stdin, print a prompt */ -char comp_name[HAL_NAME_LEN+1]; /* name for this instance of halrmt */ - -char pwd[16] = "EMC\0"; // Connect password -char enablePWD[16] = "EMCTOO\0"; // Enable password -char serverName[24] = "EMCNETSVR\0"; // Server name written in hello response -int sessions = 0; // Number of open sessions -int maxSessions = -1; // Maximum number of sessions to allow - -typedef struct { - int cliSock; - char hostName[80]; - char version[8]; - int linked; - int echo; - int verbose; - int enabled; - int commMode; - int commProt; - char inBuf[256]; - char outBuf[4096]; - char progName[256];} connectionRecType; - - -int port = 5006; -char errorStr[256]; - -int server_sockfd, client_sockfd; -socklen_t server_len, client_len; -struct sockaddr_in server_address; -struct sockaddr_in client_address; -int useSockets = 1; -int tokenIdx; -const char *delims = " \n\r\0"; -int connCount = -1; -int enabledConn = -1; - -typedef enum { - cmdHello, cmdSet, cmdGet, cmdQuit, cmdShutdown, cmdHelp, cmdUnknown} commandTokenType; - -typedef enum { - hcEcho, hcVerbose, hcEnable, hcConfig, hcCommMode, hcCommProt, - hcComps, hcPins, hcPinVals, hcSigs, hcSigVals, hcParams, hcParamVals, hcFuncts, hcThreads, - hcComp, hcPin, hcPinVal, hcSig, hcSigVal, hcParam, hcParamVal, hcFunct, hcThread, - hcLoadRt, hcUnload, hcLoadUsr, hcLinkps, hcLinksp, hcLinkpp, hcNet, hcUnlinkp, - hcLock, hcUnlock, hcNewSig, hcDelSig, hcSetP, hcSetS, hcAddF, hcDelF, - hcSave, hcStart, hcStop, hcUnknown - } halCommandType; - -typedef enum { - rtNoError, rtHandledNoError, rtStandardError, rtCustomError, rtCustomHandledError - } cmdResponseType; - -struct option longopts[] = { - {"port", 1, NULL, 'p'}, - {"name", 1, NULL, 'n'}, - {"sessions", 1, NULL, 's'}, - {"connectpw", 1, NULL, 'w'}, - {"enablepw", 1, NULL, 'e'}, - {NULL,0,NULL,0} -}; - -const char *commands[] = {"HELLO", "SET", "GET", "QUIT", "SHUTDOWN", "HELP", ""}; -const char *halCommands[] = { - "ECHO", "VERBOSE", "ENABLE", "CONFIG", "COMM_MODE", "COMM_PROT", - "COMPS", "PINS", "PINVALS", "SIGNALS", "SIGVALS", "PARAMS", "PARAMVALS", "FUNCTS", "THREADS", - "COMP", "PIN", "PINVAL", "SIGNAL", "SIGVAL", "PARAM", "PARAMVAL", "FUNCT", "THREAD", - "LOADRT", "UNLOAD", "LOADUSR", "LINKPS", "LINKSP", "LINKPP", "NET", "UNLINKP", - "LOCK", "UNLOCK", "NEWSIG", "DELSIG", "SETP", "SETS", "ADDF", "DELF", - "SAVE", "START", "STOP", ""}; - -#ifndef NO_INI - FILE *inifile = NULL; -#endif - - -/* signal handler */ -static void quit(int sig) -{ - (void)sig; - if ( hal_flag ) { - /* this process might have the hal mutex, so just set the - 'done' flag and return, exit after mutex work finishes */ - done = 1; - } else { - /* don't have to worry about the mutex, but if we just - return, we might return into the fgets() and wait - all day instead of exiting. So we exit from here. */ - if ( comp_id > 0 ) { - hal_exit(comp_id); - } - _exit(1); - } -} - -static void strupr(char *s) -{ - int i; - - for (i = 0; i < (int)strlen(s); i++) - if (s[i] > 96 && s[i] <= 'z') - s[i] -= 32; -} - -static int initSockets() -{ - int optval = 1; - - server_sockfd = socket(AF_INET, SOCK_STREAM, 0); - setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); - server_address.sin_family = AF_INET; - server_address.sin_addr.s_addr = htonl(INADDR_ANY); - server_address.sin_port = htons(port); - server_len = sizeof(server_address); - bind(server_sockfd, (struct sockaddr *)&server_address, server_len); - listen(server_sockfd, 5); - signal(SIGCHLD, SIG_IGN); - return 0; -} - -static int sockWrite(connectionRecType *context) -{ - rtapi_strxcat(context->outBuf, "\r\n"); - return write(context->cliSock, context->outBuf, strlen(context->outBuf)); -} - -static void sockWriteError(const char *nakStr, connectionRecType *context) -{ - if (context->verbose == 1) - snprintf(context->outBuf, sizeof(context->outBuf), "%s %s", nakStr, errorStr); - else - snprintf(context->outBuf, sizeof(context->outBuf), "%s", nakStr); - if(sockWrite(context) < 0) perror("sockWrite"); -} - -pid_t hal_systemv_nowait(char *const argv[], connectionRecType *context) { - pid_t pid; - int n; - const char *nakStr = "SET LOADRT NAK"; - - /* now we need to fork, and then exec .... */ - /* disconnect from the HAL shmem area before forking */ - hal_exit(comp_id); - comp_id = 0; - /* now the fork() */ - pid = fork(); - if ( pid < 0 ) { - /* fork failed */ -// halcmd_error("fork() failed\n"); - /* reconnect to the HAL shmem area */ - comp_id = hal_init(comp_name); - if (comp_id < 0) { - fprintf(stderr, "halcmd: hal_init() failed after fork: %d\n", - comp_id ); - exit(-1); - } - hal_ready(comp_id); - return -1; - } - if ( pid == 0 ) { - /* child process */ - /* print debugging info if "very verbose" (-V) */ - for(n=0; argv[n] != NULL; n++) { - rtapi_print_msg(RTAPI_MSG_DBG, "%s ", argv[n] ); - } - if (n == 0) { - snprintf(errorStr, sizeof(errorStr), "hal_systemv_nowait: empty argv array passed in\n"); - sockWriteError(nakStr, context); - exit(1); - } - rtapi_print_msg(RTAPI_MSG_DBG, "\n" ); - /* call execv() to invoke command */ - execvp(argv[0], argv); - /* should never get here */ -// halcmd_error("execv(%s) failed\n", argv[0] ); - snprintf(errorStr, sizeof(errorStr), "execv(%s) failed", argv[0]); - sockWriteError(nakStr, context); - exit(1); - } - /* parent process */ - /* reconnect to the HAL shmem area */ - comp_id = hal_init(comp_name); - - return pid; -} - -int hal_systemv(char *const argv[], connectionRecType *context) { - pid_t pid; - int status; - int retval; - const char *nakStr = "SET LOADRT NAK"; - - /* do the fork */ - pid = hal_systemv_nowait(argv, context); - /* this is the parent process, wait for child to end */ - retval = waitpid ( pid, &status, 0 ); - retval = 0; - if (comp_id < 0) { - fprintf(stderr, "halcmd: hal_init() failed after systemv: %d\n", comp_id ); - exit(-1); - } - hal_ready(comp_id); - /* check result of waitpid() */ - if ( retval < 0 ) { -// halcmd_error("waitpid(%d) failed: %s\n", pid, strerror(errno) ); - snprintf(errorStr, sizeof(errorStr), "waitpid(%d) failed: %s", pid, strerror(errno)); - sockWriteError(nakStr, context); - return -1; - } - if ( WIFEXITED(status) == 0 ) { -// halcmd_error("child did not exit normally\n"); - snprintf(errorStr, sizeof(errorStr), "child did not exit normally"); - sockWriteError(nakStr, context); - return -1; - } - retval = WEXITSTATUS(status); - if ( retval != 0 ) { -// halcmd_error("exit value: %d\n", retval ); - snprintf(errorStr, sizeof(errorStr), "exit value: %d", retval); - sockWriteError(nakStr, context); - return -1; - } - return 0; -} - - -/*********************************************************************** -* LOCAL FUNCTION DEFINITIONS * -************************************************************************/ - -/* release_HAL_mutex() unconditionally releases the hal_mutex - very useful after a program segfaults while holding the mutex -*/ -static int release_HAL_mutex(void) -{ - return hal_mutex_force_release() == 0 ? 0 : -EINVAL; -} - -static int doLock(char *command, connectionRecType *context) -{ - int retval = 0; - const char *nakStr = "SET LOCK NAK"; - - /* if command is blank, want to lock everything */ - if (*command == '\0') - retval = hal_set_lock(HAL_LOCK_ALL); - else if (strcmp(command, "none") == 0) - retval = hal_set_lock(HAL_LOCK_NONE); - else if (strcmp(command, "tune") == 0) - retval = hal_set_lock(HAL_LOCK_TUNE); - else if (strcmp(command, "all") == 0) - retval = hal_set_lock(HAL_LOCK_ALL); - - if (retval != 0) { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: Locking failed", linenumber); - sockWriteError(nakStr, context); - } - return retval; -} - -static int doUnlock(char *command, connectionRecType *context) -{ - int retval=0; - const char *nakStr = "SET UNLOCK NAK"; - - /* if command is blank, want to unlock everything */ - if (*command == '\0') - retval = hal_set_lock(HAL_LOCK_NONE); - else if (strcmp(command, "none") == 0) - retval = hal_set_lock(HAL_LOCK_NONE); - else if (strcmp(command, "tune") == 0) - retval = hal_set_lock(hal_get_lock() & ~HAL_LOCK_TUNE); - else if (strcmp(command, "all") == 0) - retval = hal_set_lock(HAL_LOCK_NONE); - - if (retval != 0) { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: Unlocking failed", linenumber); - sockWriteError(nakStr, context); - } - return retval; -} - -static int doLinkpp(char *first_pin_name, char *second_pin_name, connectionRecType *context) -{ - int retval; - hal_pin_t *first_pin, *second_pin; - const char *nakStr = "SET LINKPP NAK"; - - halpr_mutex_acquire(); - /* check if the pins are there */ - first_pin = halpr_find_pin_by_name(first_pin_name); - second_pin = halpr_find_pin_by_name(second_pin_name); - if (first_pin == NULL) { - /* first pin not found*/ - halpr_mutex_release(); - snprintf(errorStr, sizeof(errorStr), "HAL:%d: ERROR: pin '%s' not found\n", linenumber, first_pin_name); - sockWriteError(nakStr, context); - return -EINVAL; - } - else - if (second_pin == NULL) { - halpr_mutex_release(); - snprintf(errorStr, sizeof(errorStr), "HAL:%d: ERROR: pin '%s' not found", linenumber, second_pin_name); - sockWriteError(nakStr, context); - return -EINVAL; - } - - /* give the mutex, as the other functions use their own mutex */ - halpr_mutex_release(); - - /* check that both pins have the same type, - don't want to create a sig, which after that won't be useful */ - if (first_pin->type != second_pin->type) { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: ERROR: pins '%s' and '%s' not of the same type", - linenumber, first_pin_name, second_pin_name); - sockWriteError(nakStr, context); - return -EINVAL; - } - - /* now create the signal */ - retval = hal_signal_new(first_pin_name, first_pin->type); - - if (retval == 0) { - /* if it worked, link the pins to it */ - retval = hal_link(first_pin_name, first_pin_name); - - if ( retval == 0 ) - /* if that worked, link the second pin to the new signal */ - retval = hal_link(second_pin_name, first_pin_name); - } - if (retval < 0) { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: linkpp failed", linenumber); - sockWriteError(nakStr, context); - } - return retval; -} - -int doLinkPS(char *pin, char *sig, connectionRecType *context) -{ - int retval; - const char *nakStr = "SET NET NAK"; - - retval = hal_link(pin, sig); - if (retval == 0) { - /* print success message */ -// halcmd_info("Pin '%s' linked to signal '%s'\n", pin, sig); - snprintf(errorStr, sizeof(errorStr), "Pin '%s' linked to signal '%s'", pin, sig); - sockWriteError(nakStr, context); - } else { -// halcmd_error("link failed\n"); - snprintf(errorStr, sizeof(errorStr), "link failed"); - sockWriteError(nakStr, context); - } - return retval; -} - - -static int preflightNet(char *signal, hal_sig_t *sig, char *pins[], connectionRecType *context) -{ - int i, type=-1, writers=0, bidirs=0, pincnt=0; - const char *nakStr = "SET NET NAK"; - - /* if signal already exists, use its info */ - if (sig) { - type = sig->type; - writers = sig->writers; - bidirs = sig->bidirs; - } - - for(i=0; pins[i] && *pins[i]; i++) { - hal_pin_t *pin = NULL; - pin = halpr_find_pin_by_name(pins[i]); - if(!pin) { -// halcmd_error("pin '%s' does not exist\n", pins[i]); - snprintf(errorStr, sizeof(errorStr), "pin '%s' does not exist", pins[i]); - sockWriteError(nakStr, context); - return -ENOENT; - } - if(SHMPTR(pin->signal) == sig) { - /* Already on this signal */ - pincnt++; - continue; - } else if(pin->signal != 0) { -// halcmd_error("pin '%s' was already linked\n", pin->name); - snprintf(errorStr, sizeof(errorStr), "pin '%s' was already linked", pin->name); - sockWriteError(nakStr, context); - return -EINVAL; - } - if (type == -1) { - /* no pre-existing type, use this pin's type */ - type = pin->type; - } - if(type != pin->type) { -// halcmd_error("Type mismatch on pin '%s'\n", pin->name); - snprintf(errorStr, sizeof(errorStr), "Type mismatch on pin '%s'", pin->name); - sockWriteError(nakStr, context); - return -EINVAL; - } - if(pin->dir == HAL_OUT) { - if(writers || bidirs) { -// halcmd_error("Signal '%s' can not add OUT pin '%s'\n", signal, pin->name); - snprintf(errorStr, sizeof(errorStr), "Signal '%s' can not add OUT pin '%s'", signal, pin->name); - sockWriteError(nakStr, context); - return -EINVAL; - } - writers++; - } - if(pin->dir == HAL_IO) { - if(writers) { -// halcmd_error("Signal '%s' can not add I/O pin '%s'\n", signal, pin->name); - snprintf(errorStr, sizeof(errorStr), "Signal '%s' can not add I/O pin '%s'", signal, pin->name); - sockWriteError(nakStr, context); - return -EINVAL; - } - bidirs++; - } - pincnt++; - } - if(pincnt) - return 0; -// halcmd_error("'net' requires at least one pin, none given\n"); - snprintf(errorStr, sizeof(errorStr), "'net' requires at least one pin, none given"); - sockWriteError(nakStr, context); - return -EINVAL; -} - - -int doNet(char *signal, char *pins[], connectionRecType *context) -{ - hal_sig_t *sig; - int i, retval; - const char *nakStr = "SET NET NAK"; - - halpr_mutex_acquire(); - /* see if signal already exists */ - sig = halpr_find_sig_by_name(signal); - - /* verify that everything matches up (pin types, etc) */ - retval = preflightNet(signal, sig, pins, context); - if(retval < 0) { - halpr_mutex_release(); - return retval; - } - - { - hal_pin_t *pin = halpr_find_pin_by_name(signal); - if(pin) { -// halcmd_error("Signal name '%s' must not be the same as a pin.\n", signal); - snprintf(errorStr, sizeof(errorStr), "Signal name '%s' must not be the same as a pin.", signal); - sockWriteError(nakStr, context); - halpr_mutex_release(); - return -ENOENT; - } - } - if(!sig) { - /* Create the signal with the type of the first pin */ - hal_pin_t *pin = halpr_find_pin_by_name(pins[0]); - halpr_mutex_release(); - if(!pin) { - return -ENOENT; - } - retval = hal_signal_new(signal, pin->type); - } else { - /* signal already exists */ - halpr_mutex_release(); - } - /* add pins to signal */ - for(i=0; retval == 0 && pins[i] && *pins[i]; i++) { - retval = doLinkPS(pins[i], signal, context); - } - - return retval; -} - - -static int doLink(char *pin, char *sig, connectionRecType *context) -{ - int retval; - const char *nakStr = "SET LINKPS NAK"; - - /* if sig is blank, want to unlink pin */ - if (*sig == '\0') { - /* unlink the pin */ - retval = hal_unlink(pin); - } else { - /* make the link */ - retval = hal_link(pin, sig); - } - if (retval != 0) { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: link failed", linenumber); - sockWriteError(nakStr, context); - } - return retval; -} - -static int doNewsig(char *name, char *type, connectionRecType *context) -{ - int retval; - const char *nakStr = "SET NEWSIG NAK"; - - if (strcasecmp(type, "bit") == 0) - retval = hal_signal_new(name, HAL_BIT); - else - if (strcasecmp(type, "float") == 0) - retval = hal_signal_new(name, HAL_FLOAT); - else - if (strcasecmp(type, "u16") == 0) - retval = hal_signal_new(name, HAL_U32); - else - if (strcasecmp(type, "s32") == 0) - retval = hal_signal_new(name, HAL_S32); - else { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: Unknown signal type '%s'", - linenumber, type); - sockWriteError(nakStr, context); - retval = -EINVAL; - } - if (retval < 0) { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: newsig failed", linenumber); - sockWriteError(nakStr, context); - } - return retval; -} - -static int set_common(hal_type_t type, void *d_ptr, char *value, connectionRecType *context) { - (void)context; - // This function assumes that the mutex is held - int retval = 0; - double fval; - long lval; - unsigned long ulval; - char *cp = value; - - switch (type) { - case HAL_BIT: - if ((strcmp("1", value) == 0) || (strcasecmp("TRUE", value) == 0)) { - *(hal_bit_t *) (d_ptr) = 1; - } else if ((strcmp("0", value) == 0) - || (strcasecmp("FALSE", value)) == 0) { - *(hal_bit_t *) (d_ptr) = 0; - } else { - retval = -EINVAL; - } - break; - case HAL_FLOAT: - fval = strtod ( value, &cp ); - if ((*cp != '\0') && (!isspace(*cp))) { - /* invalid character(s) in string */ - retval = -EINVAL; - } else { - *((hal_float_t *) (d_ptr)) = fval; - } - break; - case HAL_S32: - lval = strtol(value, &cp, 0); - if ((*cp != '\0') && (!isspace(*cp))) { - /* invalid chars in string */ - retval = -EINVAL; - } else { - *((hal_s32_t *) (d_ptr)) = lval; - } - break; - case HAL_U32: - ulval = strtoul(value, &cp, 0); - if ((*cp != '\0') && (!isspace(*cp))) { - /* invalid chars in string */ - retval = -EINVAL; - } else { - *((hal_u32_t *) (d_ptr)) = ulval; - } - break; - default: - /* Shouldn't get here, but just in case... */ - retval = -EINVAL; - } - return retval; -} - -static int doSetp(char *name, char *value, connectionRecType *context) -{ - const char *nakStr = "SET SETP NAK"; - int retval; - hal_param_t *param; - hal_pin_t *pin; - hal_type_t type; - void *d_ptr; - - /* get mutex before accessing shared data */ - halpr_mutex_acquire(); - /* search param list for name */ - param = halpr_find_param_by_name(name); - if (param == NULL) { - pin = halpr_find_pin_by_name(name); - if(pin == NULL) { - halpr_mutex_release(); - snprintf(errorStr, sizeof(errorStr), - "HAL:%d: ERROR: parameter or pin '%s' not found\n", linenumber, name); - sockWriteError(nakStr, context); - return -EINVAL; - } else { - /* found it */ - type = pin->type; - if(pin->dir == HAL_OUT) { - halpr_mutex_release(); - snprintf(errorStr, sizeof(errorStr), - "HAL:%d: ERROR: pin '%s' is not writable\n", linenumber, name); - sockWriteError(nakStr, context); - return -EINVAL; - } - if(pin->signal != 0) { - halpr_mutex_release(); - snprintf(errorStr, sizeof(errorStr), - "HAL:%d: ERROR: pin '%s' is connected to a signal\n", linenumber, name); - sockWriteError(nakStr, context); - return -EINVAL; - } - // d_ptr = (void*)SHMPTR(pin->dummysig); - d_ptr = (void*)&pin->dummysig; - } - } else { - /* found it */ - type = param->type; - /* is it read only? */ - if (param->dir == HAL_RO) { - halpr_mutex_release(); - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: param '%s' is not writable\n", linenumber, name); - return -EINVAL; - } - d_ptr = SHMPTR(param->data_ptr); - } - - retval = set_common(type, d_ptr, value, context); - - halpr_mutex_release(); - if (retval != 0) { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: setp failed\n", linenumber); - sockWriteError(nakStr, context); - } - return retval; -} - -static int doSets(char *name, char *value, connectionRecType *context) -{ - const char *nakStr = "SET SETS NAK"; - int retval; - hal_sig_t *sig; - hal_type_t type; - void *d_ptr; - - /* get mutex before accessing shared data */ - halpr_mutex_acquire(); - /* search signal list for name */ - sig = halpr_find_sig_by_name(name); - if (sig == NULL) { - halpr_mutex_release(); - snprintf(errorStr, sizeof(errorStr), - "HAL:%d: ERROR: signal '%s' not found\n", linenumber, name); - sockWriteError(nakStr, context); - return -EINVAL; - } - /* found it - does it have a writer? */ - if (sig->writers > 0) { - halpr_mutex_release(); - snprintf(errorStr, sizeof(errorStr), - "HAL:%d: ERROR: signal '%s' already has writer(s)\n", linenumber, name); - sockWriteError(nakStr, context); - return -EINVAL; - } - /* no writer, so we can safely set it */ - type = sig->type; - d_ptr = SHMPTR(sig->data_ptr); - retval = set_common(type, d_ptr, value, context); - halpr_mutex_release(); - if (retval != 0) { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: sets failed\n", linenumber); - sockWriteError(nakStr, context); - } - return retval; -} - -static int doAddf(char *name, char *thread, char *parm, connectionRecType *context) -{ - int retval; - const char *nakStr = "SET ADDF NAK"; - - if (parm[0] == '\0') - /* no - add function at end of thread */ - retval = hal_add_funct_to_thread(name, thread, -1); - else - retval = hal_add_funct_to_thread(name, thread, atoi(parm)); - if (retval != 0) { - /* print fail message */ - snprintf(errorStr, sizeof(errorStr), "HAL:%d: Unable to add function '%s' to thread '%s'", linenumber, name, thread); - sockWriteError(nakStr, context); - } - return retval; -} - -static int doDelf(char *name, char *thread, connectionRecType *context) -{ - int retval; - const char *nakStr = "SET DELF NAK"; - - retval = hal_del_funct_from_thread(name, thread); - if (retval != 0) { - /* print success message */ - snprintf(errorStr, sizeof(errorStr), "Failed to remove function '%s' from thread '%s'", name, thread); - sockWriteError(nakStr, context); - } - return retval; -} - -static int doStart(connectionRecType *context) -{ - int retval; - const char *nakStr = "SET START NAK"; - - retval = hal_start_threads(); - if (retval != 0) { - /* print success message */ - snprintf(errorStr, sizeof(errorStr), "Failed to start realtime threads"); - sockWriteError(nakStr, context); - } - return retval; -} - -static int doStop(connectionRecType *context) -{ - int retval; - const char *nakStr = "SET STOP NAK"; - - retval = hal_stop_threads(); - if (retval != 0) { - /* print success message */ - snprintf(errorStr, sizeof(errorStr), "Unable to stop realtime threads"); - sockWriteError(nakStr, context); - } - return retval; -} - -static int doLoadRt(char *mod_name, char *args[], connectionRecType *context) -{ - char arg_string[MAX_CMD_LEN+1]; - int n=0, retval=0; - hal_comp_t *comp; - char *cp1; - const char *nakStr = "SET LOADRT NAK"; - -#if defined(RTAPI_USPACE) -#if 0 - argv[m++] = "-Wn"; - argv[m++] = mod_name; - argv[m++] = EMC2_BIN_DIR "/rtapi_app"; - argv[m++] = "load"; - argv[m++] = mod_name; - /* loop thru remaining arguments */ - while ( args[n] && args[n][0] != '\0' ) { - argv[m++] = args[n++]; - } - argv[m++] = NULL; -// retval = do_loadusr_cmd(argv); -#endif -#else - int m=0; - char *argv[MAX_TOK+3]; - static char *rtmod_dir = EMC2_RTLIB_DIR; - struct stat stat_buf; - char mod_path[MAX_CMD_LEN+1]; - - if (hal_get_lock()&HAL_LOCK_LOAD) { - snprintf(errorStr, sizeof(errorStr), "HAL is locked, loading of modules is not permitted"); - sockWriteError(nakStr, context); - return -EPERM; - } - if ( (strlen(rtmod_dir)+strlen(mod_name)+5) > MAX_CMD_LEN ) { - snprintf(errorStr, sizeof(errorStr), "Module path too long"); - sockWriteError(nakStr, context); - return -1; - } - - /* make full module name '/.o' */ - rtapi_strxcpy (mod_path, rtmod_dir); - rtapi_strxcat (mod_path, "/"); - rtapi_strxcat (mod_path, mod_name); - rtapi_strxcat (mod_path, MODULE_EXT); - /* is there a file with that name? */ - if ( stat(mod_path, &stat_buf) != 0 ) { - /* can't find it */ - snprintf(errorStr, sizeof(errorStr), "Can't find module '%s' in %s", mod_name, rtmod_dir); - sockWriteError(nakStr, context); - return -1; - } - - // TODO - FIXME - remove test after 2.2.x when blocks isn't functional anymore - if (strncmp(mod_name, "blocks", 6) == 0) { - //using RTAPI_MSG_ERR as that is the default warning level for halcmd - snprintf(errorStr, sizeof(errorStr), "blocks is deprecated, use the subcomponents generated by 'comp' instead"); - sockWriteError(nakStr, context); - } - - argv[0] = EMC2_BIN_DIR "/linuxcnc_module_helper"; - argv[1] = "insert"; - argv[2] = mod_path; - /* loop thru remaining arguments */ - n = 0; - m = 3; - while ( args[n] && args[n][0] != '\0' ) { - argv[m++] = args[n++]; - } - /* add a NULL to terminate the argv array */ - argv[m] = NULL; -/* snprintf(context->outBuf, sizeof(context->outBuf), "Setup argv %s %s %s", argv[0], argv[1], argv[2]); - sockWrite(context); - return 0; */ - - - retval = hal_systemv(argv, context); -#endif - - if ( retval != 0 ) { - snprintf(errorStr, sizeof(errorStr), "insmod failed, returned %d", retval); - sockWriteError(nakStr, context); - return -1; - } - /* make the args that were passed to the module into a single string */ - n = 0; - arg_string[0] = '\0'; - while ( args[n] && args[n][0] != '\0' ) { - strncat(arg_string, args[n++], MAX_CMD_LEN); - strncat(arg_string, " ", MAX_CMD_LEN); - } - /* allocate HAL shmem for the string */ - cp1 = hal_malloc(strlen(arg_string)+1); - if ( cp1 == NULL ) { - snprintf(errorStr, sizeof(errorStr), "failed to allocate memory for module args"); - sockWriteError(nakStr, context); - return -1; - } - /* copy string to shmem */ - strcpy (cp1, arg_string); - /* get mutex before accessing shared data */ - halpr_mutex_acquire(); - /* search component list for the newly loaded component */ - comp = halpr_find_comp_by_name(mod_name); - if (comp == NULL) { - halpr_mutex_release(); - snprintf(errorStr, sizeof(errorStr), "module '%s' not loaded", mod_name); - sockWriteError(nakStr, context); - return -EINVAL; - } - /* link args to comp struct */ - comp->insmod_args = SHMOFF(cp1); - halpr_mutex_release(); - /* print success message */ -// halcmd_info("Realtime module '%s' loaded\n", mod_name); - return 0; -} - -static int doDelsig(char *mod_name, connectionRecType *context) -{ - int next, retval, retval1, n; - hal_sig_t *sig; - char sigs[MAX_EXPECTED_SIGS][HAL_NAME_LEN+1]; - const char *nakStr = "SET DELSIG NAK"; - - /* check for "all" */ - if ( strcmp(mod_name, "all" ) != 0 ) { - retval = hal_signal_delete(mod_name); - return retval; - } - else { - /* build a list of signal(s) to delete */ - n = 0; - halpr_mutex_acquire(); - - next = hal_data->sig_list_ptr; - while (next != 0) { - sig = SHMPTR(next); - /* we want to unload this signal, remember it's name */ - if (n < ( MAX_EXPECTED_SIGS - 1)) { - snprintf(sigs[n], sizeof(sigs[n]), "%s", sig->name); - n++; - } - next = sig->next_ptr; - } - halpr_mutex_release(); - sigs[n][0] = '\0'; - - if (sigs[0][0] == '\0') { - /* desired signals not found */ - snprintf(errorStr, sizeof(errorStr), "HAL:%d: ERROR: no signals found to be deleted", linenumber); - sockWriteError(nakStr, context); - return -1; - } - /* we now have a list of components, unload them */ - n = 0; - retval1 = 0; - while (sigs[n][0] != '\0') { - retval = hal_signal_delete(sigs[n]); - /* check for fatal error */ - if (retval < -1) - return retval; - /* check for other error */ - if (retval != 0) - retval1 = retval; - n++; - } - } - return retval1; -} - -static int doUnload(char *mod_name, connectionRecType *context) -{ - int next, retval, retval1, n, all; - hal_comp_t *comp; - char comps[64][HAL_NAME_LEN+1]; - const char *nakStr = "SET UNLOAD NAK"; - - /* check for "all" */ - if ( strcmp(mod_name, "all" ) == 0 ) { - all = 1; - } else { - all = 0; - } - /* build a list of component(s) to unload */ - n = 0; - halpr_mutex_acquire(); - next = hal_data->comp_list_ptr; - while (next != 0) { - comp = SHMPTR(next); - if ( comp->type == COMPONENT_TYPE_REALTIME ) { - /* found a realtime component */ - if ( all || ( strcmp(mod_name, comp->name) == 0 )) { - /* we want to unload this component, remember its name */ - if ( n < 63 ) { - snprintf(comps[n], sizeof(comps[n]), "%s", comp->name); - comps[n][HAL_NAME_LEN] = '\0'; - n++; - } - } - } - next = comp->next_ptr; - } - halpr_mutex_release(); - /* mark end of list */ - comps[n][0] = '\0'; - if ( !all && ( comps[0][0] == '\0' )) { - /* desired component not found */ -// halcmd_error("component '%s' is not loaded\n", mod_name); - snprintf(errorStr, sizeof(errorStr), "component '%s' is not loaded", mod_name); - sockWriteError(nakStr, context); - return -1; - } - /* we now have a list of components, unload them */ - n = 0; - retval1 = 0; - while ( comps[n][0] != '\0' ) { - retval = unloadrt_comp(comps[n++]); - /* check for fatal error */ - if ( retval < -1 ) { - return retval; - } - /* check for other error */ - if ( retval != 0 ) { - retval1 = retval; - } - } -/* if (retval1 < 0) { - snprintf(errorStr, sizeof(errorStr), "unloadrt failed"); - sockWriteError(nakStr, context); - } */ - return retval1; -} - -static int unloadrt_comp(char *mod_name) -{ - int retval, status; - char *argv[4]; - pid_t pid; - - /* now we need to fork, and then exec rmmod.... */ - /* disconnect from the HAL shmem area before forking */ - hal_exit(comp_id); - comp_id = 0; - /* now the fork() */ - pid = fork(); - if ( pid < 0 ) { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: unloadrt fork() failed\n", linenumber); - /* reconnect to the HAL shmem area */ - comp_id = hal_init(comp_name); - if (comp_id < 0) { - fprintf(stderr, "halrmt: hal_init() failed after fork: %d\n", - comp_id); - exit(-1); - } - hal_ready(comp_id); - return -1; - } - if ( pid == 0 ) { - /* this is the child process - prepare to exec() rmmod */ - argv[0] = EMC2_BIN_DIR "/linuxcnc_module_helper"; - argv[1] = "remove"; - argv[2] = mod_name; - /* add a NULL to terminate the argv array */ - argv[3] = NULL; - /* print debugging info if "very verbose" (-V) */ - rtapi_print_msg(RTAPI_MSG_DBG, "%s %s %s\n", argv[0], argv[1], argv[2] ); - /* call execv() to invoke rmmod */ - execv(argv[0], argv); - /* should never get here */ - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: execv(%s) failed\n", linenumber, argv[0] ); - exit(1); - } - /* this is the parent process, wait for child to end */ - retval = waitpid ( pid, &status, 0 ); - /* reconnect to the HAL shmem area */ - comp_id = hal_init(comp_name); - if (comp_id < 0) { - fprintf(stderr, "halrmt: hal_init() failed after unloadrt: %d\n", - comp_id ); - exit(-1); - } - hal_ready(comp_id); - /* check result of waitpid() */ - if ( retval < 0 ) { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: waitpid(%d) failed\n", linenumber, pid); - return -1; - } - if ( WIFEXITED(status) == 0 ) { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: child did not exit normally\n", linenumber); - return -1; - } - retval = WEXITSTATUS(status); - if ( retval != 0 ) { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: rmmod failed, returned %d\n", linenumber, retval); - return -1; - } - /* print success message */ - rtapi_print_msg(RTAPI_MSG_INFO, "Realtime module '%s' unloaded\n", - mod_name); - return 0; -} - -static int doLoadUsr(char *args[]) -{ - int wait_flag, wait_comp_flag, name_flag, ignore_flag; - char *prog_name, *new_comp_name; - char prog_path[MAX_CMD_LEN+1]; - char *cp1, *cp2, *envpath; - struct stat stat_buf; - char *argv[MAX_TOK+1]; - int n, m, retval, status; - pid_t pid; - - if (hal_get_lock()&HAL_LOCK_LOAD) { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: HAL is locked, loading of programs is not permitted\n", linenumber); - return -EPERM; - } - /* check for options (-w, -i, and/or -r) */ - wait_flag = 0; - wait_comp_flag = 0; - name_flag = 0; - ignore_flag = 0; - prog_name = NULL; - while ( **args == '-' ) { - /* this argument contains option(s) */ - cp1 = *args; - cp1++; - while ( *cp1 != '\0' ) { - if ( *cp1 == 'w' ) { - wait_flag = 1; - } else if ( *cp1 == 'W' ) { - wait_comp_flag = 1; - } else if ( *cp1 == 'i' ) { - ignore_flag = 1; - } else if ( *cp1 == 'n' ) { - name_flag = 1; - } else { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: unknown loadusr option '-%c'\n", linenumber, *cp1); - return -EINVAL; - } - cp1++; - } - /* move to next arg */ - args++; - } - /* get program and component name */ - if(name_flag) { - new_comp_name = *args++; - prog_name = *args++; - } else { - new_comp_name = prog_name = *args++; - } - /* need to find path to a program matching "prog_name" */ - prog_path[0] = '\0'; - if ( prog_path[0] == '\0' ) { - /* try the name by itself */ - rtapi_strlcpy(prog_path, prog_name, MAX_CMD_LEN); - rtapi_print_msg(RTAPI_MSG_DBG, "Trying '%s'\n", prog_path); - if ( stat(prog_path, &stat_buf) != 0 ) { - /* no luck, clear prog_path to indicate failure */ - prog_path[0] = '\0'; - } - } - if ( prog_path[0] == '\0' ) { - /* no luck yet, try the emc2/bin directory where - the halrmt executable is located */ - n = readlink("/proc/self/exe", prog_path, MAX_CMD_LEN-10); - if ( n > 0 ) { - prog_path[n] = '\0'; - /* have path to executabie, find last '/' */ - cp2 = ""; - cp1 = prog_path; - while ( *cp1 != '\0' ) { - if ( *cp1 == '/' ) { - cp2 = cp1; - } - cp1++; - } - if ( *cp2 == '/' ) { - /* chop "halrmt" from end of path */ - *(++cp2) = '\0'; - /* append the program name */ - strncat(prog_path, prog_name, MAX_CMD_LEN-strlen(prog_path)); - /* and try it */ - rtapi_print_msg(RTAPI_MSG_DBG, "Trying '%s'\n", prog_path); - if ( stat(prog_path, &stat_buf) != 0 ) { - /* no luck, clear prog_path to indicate failure */ - prog_path[0] = '\0'; - } - } - } - } - if ( prog_path[0] == '\0' ) { - /* no luck yet, try the user's PATH */ - envpath = getenv("PATH"); - if ( envpath != NULL ) { - while ( *envpath != '\0' ) { - /* copy a single directory from the PATH env variable */ - n = 0; - while ( (*envpath != ':') && (*envpath != '\0') && (n < MAX_CMD_LEN)) { - prog_path[n++] = *envpath++; - } - /* append '/' and program name */ - if ( n < MAX_CMD_LEN ) { - prog_path[n++] = '/'; - } - cp1 = prog_name; - while ((*cp1 != '\0') && ( n < MAX_CMD_LEN)) { - prog_path[n++] = *cp1++; - } - prog_path[n] = '\0'; - rtapi_print_msg(RTAPI_MSG_DBG, "Trying '%s'\n", prog_path); - if ( stat(prog_path, &stat_buf) != 0 ) { - /* no luck, clear prog_path to indicate failure */ - prog_path[0] = '\0'; - /* and get ready to try the next directory */ - if ( *envpath == ':' ) { - envpath++; - } - } else { - /* success, break out of loop */ - break; - } - } - } - } - if ( prog_path[0] == '\0' ) { - /* still can't find a program to run */ - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: Can't find program '%s'\n", linenumber, prog_name); - return -1; - } - - /* now we need to fork, and then exec the program.... */ - /* disconnect from the HAL shmem area before forking */ - hal_exit(comp_id); - comp_id = 0; - /* now the fork() */ - pid = fork(); - if ( pid < 0 ) { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: loadusr fork() failed\n", linenumber); - /* reconnect to the HAL shmem area */ - comp_id = hal_init(comp_name); - if (comp_id < 0) { - fprintf(stderr, "halrmt: hal_init() failed after fork: %d\n", - comp_id); - exit(-1); - } - hal_ready(comp_id); - return -1; - } - if ( pid == 0 ) { - /* this is the child process - prepare to exec() the program */ - argv[0] = prog_name; - /* loop thru remaining arguments */ - n = 0; - m = 1; - while ( args[n][0] != '\0' ) { - argv[m++] = args[n++]; - } - /* add a NULL to terminate the argv array */ - argv[m] = NULL; - /* print debugging info if "very verbose" (-V) */ - rtapi_print_msg(RTAPI_MSG_DBG, "%s ", argv[0] ); - n = 1; - while ( argv[n] != NULL ) { - rtapi_print_msg(RTAPI_MSG_DBG, "%s ", argv[n++] ); - } - rtapi_print_msg(RTAPI_MSG_DBG, "\n" ); - /* call execv() to invoke the program */ - execv(prog_path, argv); - /* should never get here */ - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: execv(%s) failed: %s\n", linenumber, prog_name, - strerror(errno)); - exit(1); - } - /* this is the parent process, reconnect to the HAL shmem area */ - comp_id = hal_init(comp_name); - if (comp_id < 0) { - fprintf(stderr, "halrmt: hal_init() failed after loadusr: %d\n", - comp_id); - exit(-1); - } - hal_ready(comp_id); - if ( wait_comp_flag ) { - int ready = 0, count=0; - int next; - while(!ready) { - struct timespec ts = {0, 10 * 1000 * 1000}; // 10ms - nanosleep(&ts, NULL); - retval = waitpid( pid, &status, WNOHANG ); - if(retval != 0) goto wait_common; - - halpr_mutex_acquire(); - next = hal_data->comp_list_ptr; - while(next) { - hal_comp_t *comp = SHMPTR(next); - next = comp->next_ptr; - if(strcmp(comp->name, new_comp_name) == 0 && comp->ready) { - ready = 1; - break; - } - } - halpr_mutex_release(); - - count++; - if(count == 100) { - fprintf(stderr, "Waiting for component '%s' to become ready.", - new_comp_name); - fflush(stderr); - } else if(count > 100 && count % 10 == 0) { - fprintf(stderr, "."); - fflush(stderr); - } - } - if (count >= 100) { - fprintf(stderr, "\n"); - } - rtapi_print_msg(RTAPI_MSG_INFO, "Component '%s' ready\n", new_comp_name); - } - if ( wait_flag ) { - /* wait for child process to complete */ - retval = waitpid ( pid, &status, 0 ); - /* check result of waitpid() */ -wait_common: - if ( retval < 0 ) { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: waitpid(%d) failed\n", linenumber, pid); - return -1; - } - if ( WIFEXITED(status) == 0 ) { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: program '%s' did not exit normally\n", linenumber, prog_name ); - return -1; - } - if ( ignore_flag == 0 ) { - retval = WEXITSTATUS(status); - if ( retval != 0 ) { - rtapi_print_msg(RTAPI_MSG_ERR, - "HAL:%d: ERROR: program '%s' failed, returned %d\n", linenumber, prog_name, retval ); - return -1; - } - } - /* print success message */ - rtapi_print_msg(RTAPI_MSG_INFO, "Program '%s' finished\n", prog_name); - } else { - /* print success message */ - rtapi_print_msg(RTAPI_MSG_INFO, "Program '%s' started\n", prog_name); - } - return 0; -} - -static void getCompInfo(char *pattern, connectionRecType *context) -{ - int next, len; - hal_comp_t *comp; - - halpr_mutex_acquire(); - len = strlen(pattern); - next = hal_data->comp_list_ptr; - while (next != 0) { - comp = SHMPTR(next); - if (strncmp(pattern, comp->name, len) == 0) { - snprintf(context->outBuf, sizeof(context->outBuf), "COMP %s %02d %s", comp->name, comp->comp_id, (comp->type != COMPONENT_TYPE_USER) ? "RT " : "User"); - sockWrite(context); - } - next = comp->next_ptr; - } - halpr_mutex_release(); -} - - -static void getPinInfo(char *pattern, int valuesOnly, connectionRecType *context) -{ - int next, len; - hal_pin_t *pin; - hal_comp_t *comp; - hal_sig_t *sig; - void *dptr; - - halpr_mutex_acquire(); - len = strlen(pattern); - next = hal_data->pin_list_ptr; - while (next != 0) { - pin = SHMPTR(next); - if (strncmp(pattern, pin->name, len) == 0) { - comp = SHMPTR(pin->owner_ptr); - if (pin->signal != 0) { - sig = SHMPTR(pin->signal); - dptr = SHMPTR(sig->data_ptr); - } - else { - sig = NULL; - dptr = &(pin->dummysig); - } - if (valuesOnly == 0) - snprintf(context->outBuf, sizeof(context->outBuf), "PIN %s %s %02d %s %s", - pin->name, - data_value2((int) pin->type, dptr), - comp->comp_id, - data_type((int) pin->type), - pin_data_dir((int) pin->dir)); - else - snprintf(context->outBuf, sizeof(context->outBuf), "PINVAL %s %s", - pin->name, - data_value2((int) pin->type, dptr)); - sockWrite(context); - } - next = pin->next_ptr; - } - halpr_mutex_release(); -} - - -static void getSigInfo(char *pattern, int valuesOnly, connectionRecType *context) -{ - int next, len; - hal_sig_t *sig; - void *dptr; - - halpr_mutex_acquire(); - len = strlen(pattern); - next = hal_data->sig_list_ptr; - while (next != 0) { - sig = SHMPTR(next); - if (strncmp(pattern, sig->name, len) == 0) { - dptr = SHMPTR(sig->data_ptr); - if (valuesOnly == 0) - snprintf(context->outBuf, sizeof(context->outBuf), "SIGNAL %s %s %s", - sig->name, - data_value((int) sig->type, dptr), - data_type((int) sig->type)); - else - snprintf(context->outBuf, sizeof(context->outBuf), "SIGNALVAL %s %s", sig->name, data_value((int) sig->type, dptr)); - sockWrite(context); - } - next = sig->next_ptr; - } - halpr_mutex_release(); -} - -static void getParamInfo(char *pattern, int valuesOnly, connectionRecType *context) -{ - int next, len; - hal_param_t *param; - hal_comp_t *comp; - - halpr_mutex_acquire(); - len = strlen(pattern); - next = hal_data->param_list_ptr; - while (next != 0) { - param = SHMPTR(next); - if ( strncmp(pattern, param->name, len) == 0 ) { - comp = SHMPTR(param->owner_ptr); - if (valuesOnly == 0) - snprintf(context->outBuf, sizeof(context->outBuf), "PARAM %s %s %02d %s %s", - param->name, - data_value((int) param->type, SHMPTR(param->data_ptr)), - comp->comp_id, - data_type((int) param->type), - param_data_dir((int) param->dir)); - else - snprintf(context->outBuf, sizeof(context->outBuf), "PARAMVAL %s %s", - param->name, - data_value((int) param->type, SHMPTR(param->data_ptr))); - sockWrite(context); - } - next = param->next_ptr; - } - halpr_mutex_release(); -} - -static void getFunctInfo(char *pattern, connectionRecType *context) -{ - int next, len; - hal_funct_t *fptr; - hal_comp_t *comp; - - halpr_mutex_acquire(); - len = strlen(pattern); - next = hal_data->funct_list_ptr; - while (next != 0) { - fptr = SHMPTR(next); - if (strncmp(pattern, fptr->name, len) == 0) { - comp = SHMPTR(fptr->owner_ptr); - snprintf(context->outBuf, sizeof(context->outBuf), "FUNCT %s %02d %08lX %08lX %s %3d", - fptr->name, - comp->comp_id, - (unsigned long)fptr->funct, - (unsigned long)fptr->arg, - (fptr->uses_fp ? "YES" : "NO"), - fptr->users); - sockWrite(context); - } - next = fptr->next_ptr; - } - halpr_mutex_release(); -} - -static void getThreadInfo(char *pattern, connectionRecType *context) -{ - int next_thread, len, n; - hal_thread_t *tptr; - hal_list_t *list_root, *list_entry; - hal_funct_entry_t *fentry; - hal_funct_t *funct; - - halpr_mutex_acquire(); - len = strlen(pattern); - next_thread = hal_data->thread_list_ptr; - while (next_thread != 0) { - tptr = SHMPTR(next_thread); - if (strncmp(pattern, tptr->name, len) == 0) { - char name[HAL_NAME_LEN+1]; - hal_pin_t* pin; - hal_sig_t* sig; - void* dptr; - unsigned int runtime_pin_value; - - size_t ret = snprintf(name, sizeof(name), "%s.time",tptr->name); - if (ret >= sizeof(name)) { - rtapi_print_msg(RTAPI_MSG_ERR, - "unexpected: pin name too long for %s thread",tptr->name); - runtime_pin_value = 0; - } else { - pin = halpr_find_pin_by_name(name); - if (pin) { - if (pin->signal != 0) { - sig = SHMPTR(pin->signal); - dptr = SHMPTR(sig->data_ptr); - } else { - sig = NULL; - dptr = &(pin->dummysig); - } - runtime_pin_value = (int)*(int*)dptr; - } else { - runtime_pin_value = 0; - rtapi_print_msg(RTAPI_MSG_ERR, - "unexpected: cannot find time pin for %s thread",tptr->name); - } - } - - snprintf(context->outBuf, sizeof(context->outBuf), "THREAD %s %11u %s %u %u", - tptr->name, - (unsigned int)tptr->period, - (tptr->uses_fp ? "YES" : "NO "), - runtime_pin_value, - (unsigned int)hal_get_si32(tptr->maxtime)); - sockWrite(context); - list_root = &(tptr->funct_list); - list_entry = list_next(list_root); - n = 1; - while (list_entry != list_root) { - /* print the function info */ - fentry = (hal_funct_entry_t *) list_entry; - funct = SHMPTR(fentry->funct_ptr); - /* scriptmode only uses one line per thread, which contains: - thread period, FP flag, name, then all functs separated by spaces */ - snprintf(context->outBuf, sizeof(context->outBuf), "SUBTHREAD %s %2d", funct->name, n); - sockWrite(context); - n++; - list_entry = list_next(list_entry); - } - } - next_thread = tptr->next_ptr; - } - halpr_mutex_release(); -} - - -/* Switch function for pin/sig/param type for the print_*_list functions */ -static const char *data_type(int type) -{ - const char *type_str; - - switch (type) { - case HAL_BIT: - type_str = "bit "; - break; - case HAL_FLOAT: - type_str = "float"; - break; - case HAL_S32: - type_str = "s32 "; - break; - case HAL_U32: - type_str = "u32 "; - break; - default: - /* Shouldn't get here, but just in case... */ - type_str = "undef"; - } - return type_str; -} - -/* Switch function for pin direction for the print_*_list functions */ -static const char *pin_data_dir(int dir) -{ - const char *pin_dir; - - switch (dir) { - case HAL_IN: - pin_dir = "IN "; - break; - case HAL_OUT: - pin_dir = "OUT"; - break; - case HAL_IO: - pin_dir = "I/O"; - break; - default: - /* Shouldn't get here, but just in case... */ - pin_dir = "???"; - } - return pin_dir; -} - -/* Switch function for param direction for the print_*_list functions */ -static const char *param_data_dir(int dir) -{ - const char *param_dir; - - switch (dir) { - case HAL_RO: - param_dir = "RO"; - break; - case HAL_RW: - param_dir = "RW"; - break; - default: - /* Shouldn't get here, but just in case... */ - param_dir = "??"; - } - return param_dir; -} - -/* Switch function for arrow direction for the print_*_list functions */ -static const char *data_arrow1(int dir) -{ - const char *arrow; - - switch (dir) { - case HAL_IN: - arrow = "<=="; - break; - case HAL_OUT: - arrow = "==>"; - break; - case HAL_IO: - arrow = "<=>"; - break; - default: - /* Shouldn't get here, but just in case... */ - arrow = "???"; - } - return arrow; -} - -/* Switch function for arrow direction for the print_*_list functions */ -static const char *data_arrow2(int dir) -{ - const char *arrow; - - switch (dir) { - case HAL_IN: - arrow = "==>"; - break; - case HAL_OUT: - arrow = "<=="; - break; - case HAL_IO: - arrow = "<=>"; - break; - default: - /* Shouldn't get here, but just in case... */ - arrow = "???"; - } - return arrow; -} - -/* Switch function to return var value for the print_*_list functions */ -/* the value is printed in a fixed width field */ -static char *data_value(int type, void *valptr) -{ - char *value_str; - static char buf[15]; - - switch (type) { - case HAL_BIT: - if (*((char *) valptr) == 0) - value_str = " FALSE"; - else - value_str = " TRUE"; - break; - case HAL_FLOAT: - snprintf(buf, 14, "%12.7g", (double)*((hal_float_t *) valptr)); - value_str = buf; - break; - case HAL_S32: - snprintf(buf, 14, " %10ld", (long)*((hal_u32_t *) valptr)); - value_str = buf; - break; - case HAL_U32: - snprintf(buf, 14, " %08lX", (unsigned long)*((hal_u32_t *) valptr)); - value_str = buf; - break; - default: - /* Shouldn't get here, but just in case... */ - value_str = " undef "; - } - return value_str; -} - -/* Switch function to return var value in string form */ -/* the value is printed as a packed string (no whitespace */ -static char *data_value2(int type, void *valptr) -{ - char *value_str; - static char buf[15]; - - switch (type) { - case HAL_BIT: - if (*((char *) valptr) == 0) - value_str = "FALSE"; - else - value_str = "TRUE"; - break; - case HAL_FLOAT: - snprintf(buf, 14, "%.7g", (double)*((hal_float_t *) valptr)); - value_str = buf; - break; - case HAL_S32: - snprintf(buf, 14, "%ld", (long)*((hal_s32_t *) valptr)); - value_str = buf; - break; - case HAL_U32: - snprintf(buf, 14, "%lu", (unsigned long)*((hal_u32_t *) valptr)); - value_str = buf; - break; - default: - /* Shouldn't get here, but just in case... */ - value_str = "unknown_type"; - } - return value_str; -} - -static int doSave(char *type, char *filename, connectionRecType *context) -{ - FILE *dst; - const char *nakStr = "SET SAVE NAK"; - - if (rtapi_get_msg_level() == RTAPI_MSG_NONE) { - /* must be -Q, don't print anything */ - return 0; - } - if (*filename == '\0' ) - dst = stdout; - else { - dst = fopen(filename, "w" ); - if (dst == NULL) { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: Can't open 'save' destination '%s'", linenumber, filename); - sockWriteError(nakStr, context); - return -1; - } - } - if (*type == '\0') - type = "all"; - if (strcmp(type, "all" ) == 0) { - /* save everything */ - save_comps(dst); - save_signals(dst); - save_links(dst, 0); - save_params(dst); - save_threads(dst); - } - else - if (strcmp(type, "comp") == 0) - save_comps(dst); - else - if (strcmp(type, "sig") == 0) - save_signals(dst); - else - if (strcmp(type, "link") == 0) - save_links(dst, 0); - else - if (strcmp(type, "linka") == 0) - save_links(dst, 1); - else - if (strcmp(type, "net") == 0) - save_nets(dst, 0); - else - if (strcmp(type, "neta") == 0) - save_nets(dst, 1); - else - if (strcmp(type, "param") == 0) - save_params(dst); - else - if (strcmp(type, "thread") == 0) - save_threads(dst); - else { - snprintf(errorStr, sizeof(errorStr), "HAL:%d: Unknown 'save' type '%s'", linenumber, type); - sockWriteError(nakStr, context); - fclose(dst); - return -1; - } - if (*filename != '\0' ) - fclose(dst); - return 0; -} - -static void save_comps(FILE *dst) -{ - int next; - hal_comp_t *comp; - - fprintf(dst, "# components\n"); - halpr_mutex_acquire(); - next = hal_data->comp_list_ptr; - while (next != 0) { - comp = SHMPTR(next); - if ( comp->type == COMPONENT_TYPE_REALTIME ) { - /* only print realtime components */ - if ( comp->insmod_args == 0 ) { - fprintf(dst, "#loadrt %s (not loaded by loadrt, no args saved)\n", comp->name); - } else { - fprintf(dst, "loadrt %s %s\n", comp->name, - (char *)SHMPTR(comp->insmod_args)); - } - } - next = comp->next_ptr; - } - halpr_mutex_release(); -} - -static void save_signals(FILE *dst) -{ - int next; - hal_sig_t *sig; - - fprintf(dst, "# signals\n"); - halpr_mutex_acquire(); - next = hal_data->sig_list_ptr; - while (next != 0) { - sig = SHMPTR(next); - fprintf(dst, "newsig %s %s\n", sig->name, data_type((int) sig->type)); - next = sig->next_ptr; - } - halpr_mutex_release(); -} - -static void save_links(FILE *dst, int arrow) -{ - int next; - hal_pin_t *pin; - hal_sig_t *sig; - const char *arrow_str; - - fprintf(dst, "# links\n"); - halpr_mutex_acquire(); - next = hal_data->pin_list_ptr; - while (next != 0) { - pin = SHMPTR(next); - if (pin->signal != 0) { - sig = SHMPTR(pin->signal); - if (arrow != 0) { - arrow_str = data_arrow1((int) pin->dir); - } else { - arrow_str = "\0"; - } - fprintf(dst, "linkps %s %s %s\n", pin->name, arrow_str, sig->name); - } - next = pin->next_ptr; - } - halpr_mutex_release(); -} - -static void save_nets(FILE *dst, int arrow) -{ - int next; - hal_pin_t *pin; - hal_sig_t *sig; - const char *arrow_str; - - fprintf(dst, "# nets\n"); - halpr_mutex_acquire(); - next = hal_data->sig_list_ptr; - while (next != 0) { - sig = SHMPTR(next); - fprintf(dst, "newsig %s %s\n", sig->name, data_type((int) sig->type)); - pin = halpr_find_pin_by_sig(sig, NULL); - while (pin != NULL) { - if (arrow != 0) { - arrow_str = data_arrow2((int) pin->dir); - } else { - arrow_str = "\0"; - } - fprintf(dst, "linksp %s %s %s\n", sig->name, arrow_str, pin->name); - pin = halpr_find_pin_by_sig(sig, pin); - } - next = sig->next_ptr; - } - halpr_mutex_release(); -} - -static void save_params(FILE *dst) -{ - int next; - hal_param_t *param; - - fprintf(dst, "# parameter values\n"); - halpr_mutex_acquire(); - next = hal_data->param_list_ptr; - while (next != 0) { - param = SHMPTR(next); - if (param->dir != HAL_RO) { - /* param is writable, save it's value */ - fprintf(dst, "setp %s %s\n", param->name, - data_value((int) param->type, SHMPTR(param->data_ptr))); - } - next = param->next_ptr; - } - halpr_mutex_release(); -} - -static void save_threads(FILE *dst) -{ - int next_thread; - hal_thread_t *tptr; - hal_list_t *list_root, *list_entry; - hal_funct_entry_t *fentry; - hal_funct_t *funct; - - fprintf(dst, "# realtime thread/function links\n"); - halpr_mutex_acquire(); - next_thread = hal_data->thread_list_ptr; - while (next_thread != 0) { - tptr = SHMPTR(next_thread); - list_root = &(tptr->funct_list); - list_entry = list_next(list_root); - while (list_entry != list_root) { - /* print the function info */ - fentry = (hal_funct_entry_t *) list_entry; - funct = SHMPTR(fentry->funct_ptr); - fprintf(dst, "addf %s %s\n", funct->name, tptr->name); - list_entry = list_next(list_entry); - } - next_thread = tptr->next_ptr; - } - halpr_mutex_release(); -} - -static int do_help_cmd(char *command) -{ - - if (strcmp(command, "help") == 0) { - printf("If you need help to use 'help', then I can't help you.\n"); - } else if (strcmp(command, "loadrt") == 0) { - printf("loadrt modname [modarg(s)]\n"); - printf(" Loads realtime HAL module 'modname', passing 'modargs'\n"); - printf(" to the module.\n"); - } else if (strcmp(command, "unloadrt") == 0) { - printf("unloadrt modname\n"); - printf(" Unloads realtime HAL module 'modname'. If 'modname'\n"); - printf(" is 'all', unloads all realtime modules.\n"); - } else if (strcmp(command, "loadusr") == 0) { - printf("loadusr [options] progname [progarg(s)]\n"); - printf(" Starts user space program 'progname', passing\n"); - printf(" 'progargs' to it. Options are:\n"); - printf(" -w wait for program to finish\n"); - printf(" -i ignore program return value (use with -w)\n"); - } else if ((strcmp(command, "linksp") == 0) || (strcmp(command,"linkps") == 0)) { - printf("linkps pinname [arrow] signame\n"); - printf("linksp signame [arrow] pinname\n"); - printf(" Links pin 'pinname' to signal 'signame'. Both forms do\n"); - printf(" the same thing. Use whichever makes sense. The optional\n"); - printf(" 'arrow' can be '==>', '<==', or '<=>' and is ignored. It\n"); - printf(" can be used in files to show the direction of data flow,\n"); - printf(" but don't use arrows on the command line.\n"); - } else if (strcmp(command, "linkpp") == 0) { - printf("linkpp firstpin secondpin\n"); - printf(" Creates a signal with the name of the first pin,\n"); printf(" then links both pins to the signal. \n"); - }else if (strcmp(command, "unlinkp") == 0) { - printf("unlinkp pinname\n"); - printf(" Unlinks pin 'pinname' if it is linked to any signal.\n"); - } else if (strcmp(command, "lock") == 0) { - printf("lock [all|tune|none]\n"); - printf(" Locks HAL to some degree.\n"); - printf(" none - no locking done.\n"); - printf(" tune - some tuning is possible (setp & such).\n"); - printf(" all - HAL completely locked.\n"); - } else if (strcmp(command, "unlock") == 0) { - printf("unlock [all|tune]\n"); - printf(" Unlocks HAL to some degree.\n"); - printf(" tune - some tuning is possible (setp & such).\n"); - printf(" all - HAL completely unlocked.\n"); - } else if (strcmp(command, "newsig") == 0) { - printf("newsig signame type\n"); - printf(" Creates a new signal called 'signame'. Type is 'bit',\n"); - printf(" 'float', 'u8', 's8', 'u16', 's16', 'u32', or 's32'.\n"); - } else if (strcmp(command, "delsig") == 0) { - printf("delsig signame\n"); - printf(" Deletes signal 'signame'. If 'signame is 'all',\n"); - printf(" deletes all signals\n"); - } else if (strcmp(command, "setp") == 0) { - printf("setp paramname value\n"); - printf("paramname = value\n"); - printf(" Sets parameter 'paramname' to 'value' (if writable).\n"); - printf(" 'setp' and '=' work the same, don't use '=' on the\n"); - printf(" command line. 'value' may be a constant such as 1.234\n"); - printf(" or TRUE, or a reference to an environment variable,\n"); -#ifdef NO_INI - printf(" using the syntax '$name'./n"); -#else - printf(" using the syntax '$name'. If option -i was given,\n"); - printf(" 'value' may also be a reference to an INI file entry\n"); - printf(" using the syntax '[section]name'.\n"); -#endif - } else if (strcmp(command, "sets") == 0) { - printf("sets signame value\n"); - printf(" Sets signal 'signame' to 'value' (if sig has no writers).\n"); - } else if (strcmp(command, "getp") == 0) { - printf("getp paramname\n"); - printf(" Gets the value of parameter 'paramname'.\n"); - } else if (strcmp(command, "gets") == 0) { - printf("gets signame\n"); - printf(" Gets the value of signal 'signame'.\n"); - } else if (strcmp(command, "addf") == 0) { - printf("addf functname threadname [position]\n"); - printf(" Adds function 'functname' to thread 'threadname'. If\n"); - printf(" 'position' is specified, adds the function to that spot\n"); - printf(" in the thread, otherwise adds it to the end. Negative\n"); - printf(" 'position' means position with respect to the end of the\n"); - printf(" thread. For example '1' is start of thread, '-1' is the\n"); - printf(" end of the thread, '-3' is third from the end.\n"); - } else if (strcmp(command, "delf") == 0) { - printf("delf functname threadname\n"); - printf(" Removes function 'functname' from thread 'threadname'.\n"); - } else if (strcmp(command, "show") == 0) { - printf("show [type] [pattern]\n"); - printf(" Prints info about HAL items of the specified type.\n"); - printf(" 'type' is 'comp', 'pin', 'sig', 'param', 'funct',\n"); - printf(" 'thread', or 'all'. If 'type' is omitted, it assumes\n"); - printf(" 'all' with no pattern. If 'pattern' is specified\n"); - printf(" it prints only those items whose names match the\n"); - printf(" pattern (no fancy regular expressions, just a simple\n"); - printf(" match: 'foo' matches 'foo', 'foobar' and 'foot' but\n"); - printf(" not 'fo' or 'frobz' or 'ffoo').\n"); - } else if (strcmp(command, "list") == 0) { - printf("list type [pattern]\n"); - printf(" Prints the names of HAL items of the specified type.\n"); - printf(" 'type' is 'comp', 'pin', 'sig', 'param', 'funct', or\n"); - printf(" 'thread'. If 'pattern' is specified it prints only\n"); - printf(" those names that match the pattern (no fancy regular\n"); - printf(" expressions, just a simple match: 'foo' matches 'foo',\n"); - printf(" 'foobar' and 'foot' but not 'fo' or 'frobz' or 'ffoo').\n"); - printf(" Names are printed on a single line, space separated.\n"); - } else if (strcmp(command, "status") == 0) { - printf("status [type]\n"); - printf(" Prints status info about HAL.\n"); - printf(" 'type' is 'lock', 'mem', or 'all'. \n"); - printf(" If 'type' is omitted, it assumes\n"); - printf(" 'all'.\n"); - } else if (strcmp(command, "save") == 0) { - printf("save [type] [filename]\n"); - printf(" Prints HAL state to 'filename' (or stdout), as a series\n"); - printf(" of HAL commands. State can later be restored by using\n"); - printf(" \"halrmt -f filename\".\n"); - printf(" Type can be 'comp', 'sig', 'link[a]', 'net[a]', 'param',\n"); - printf(" or 'thread'. ('linka' and 'neta' show arrows for pin\n"); - printf(" direction.) If 'type' is omitted or 'all', does the\n"); - printf(" equivalent of 'comp', 'sig', 'link', 'param', and 'thread'.\n"); - } else if (strcmp(command, "start") == 0) { - printf("start\n"); - printf(" Starts all realtime threads.\n"); - } else if (strcmp(command, "stop") == 0) { - printf("stop\n"); - printf(" Stops all realtime threads.\n"); - } else if (strcmp(command, "quit") == 0) { - printf("quit\n"); - printf(" Stop processing input and terminate halrmt (when\n"); - printf(" reading from a file or stdin).\n"); - } else if (strcmp(command, "exit") == 0) { - printf("exit\n"); - printf(" Stop processing input and terminate halrmt (when\n"); - printf(" reading from a file or stdin).\n"); - } else { - printf("No help for unknown command '%s'\n", command); - } - return 0; -} - -static void print_help_general(int showR) -{ - printf("\nUsage: halrmt [options] [cmd [args]]\n\n"); - printf("options:\n\n"); - printf(" -f [filename] Read commands from 'filename', not command\n"); - printf(" line. If no filename, read from stdin.\n"); -#ifndef NO_INI - printf(" -i filename Open INI file 'filename', allow commands\n"); - printf(" to get their values from INI file.\n"); -#endif - printf(" -q Quiet - print errors only (default).\n"); - printf(" -Q Very quiet - print nothing.\n"); - if (showR != 0) { - printf(" -R Release mutex (for crash recovery only).\n"); - } - printf(" -s Script friendly - don't print headers on output.\n"); - printf(" -v Verbose - print result of every command.\n"); - printf(" -V Very verbose - print lots of junk.\n"); - printf(" -h Help - print this help screen and exit.\n\n"); - printf("commands:\n\n"); - printf(" loadrt, unloadrt, loadusr, lock, unlock, linkps, linksp, linkpp,\n"); - printf(" unlinkp, newsig, delsig, setp, getp, sets, gets, addf, delf, show,\n"); - printf(" list, save, status, start, stop, quit, exit\n"); - printf(" help Lists all commands with short descriptions\n"); - printf(" help command Prints detailed help for 'command'\n\n"); -} - -static halCommandType lookupHalCommand(char *s) -{ - halCommandType i = hcEcho; - int temp; - - while (i < hcUnknown) { - if (strcmp(halCommands[i], s) == 0) return i; -// (int)i += 1; - temp = i; - temp++; - i = (halCommandType) temp; - } - return i; -} - - static int commandHello(connectionRecType *context) -{ - char *pch; -// const char *password = "EMC"; - - pch = strtok(NULL, delims); - if (pch == NULL) return -1; - if (strcmp(pch, pwd) != 0) return -1; - pch = strtok(NULL, delims); - if (pch == NULL) return -1; - rtapi_strxcpy(context->hostName, pch); - pch = strtok(NULL, delims); - if (pch == NULL) return -1; - context->linked = 1; - rtapi_strxcpy(context->version, pch); - printf("Connected to %s\n\r", context->hostName); - return 0; -} - -static cmdResponseType getEcho(char *s, connectionRecType *context) -{ - (void)s; - const char *pEchoStr = "ECHO %s"; - - if (context->echo == 1) snprintf(context->outBuf, sizeof(context->outBuf), pEchoStr, "ON"); - else snprintf(context->outBuf, sizeof(context->outBuf), pEchoStr, "OFF"); - return rtNoError; -} - -static cmdResponseType getVerbose(char *s, connectionRecType *context) -{ - (void)s; - const char *pVerboseStr = "VERBOSE %s"; - - if (context->verbose == 1) snprintf(context->outBuf, sizeof(context->outBuf), pVerboseStr, "ON"); - else snprintf(context->outBuf, sizeof(context->outBuf), pVerboseStr, "OFF"); - return rtNoError; -} - -static cmdResponseType getEnable(char *s, connectionRecType *context) -{ - (void)s; - const char *pEnableStr = "ENABLE %s"; - - if (context->cliSock == enabledConn) snprintf(context->outBuf, sizeof(context->outBuf), pEnableStr, "ON"); - else snprintf(context->outBuf, sizeof(context->outBuf), pEnableStr, "OFF"); - return rtNoError; -} - -static cmdResponseType getConfig(char *s, connectionRecType *context) -{ - (void)s; - const char *pConfigStr = "CONFIG"; - - rtapi_strxcpy(context->outBuf, pConfigStr); - return rtNoError; -} - -static cmdResponseType getCommMode(char *s, connectionRecType *context) -{ - (void)s; - const char *pCommModeStr = "COMM_MODE %s"; - - switch (context->commMode) { - case 0: snprintf(context->outBuf, sizeof(context->outBuf), pCommModeStr, "ASCII"); break; - case 1: snprintf(context->outBuf, sizeof(context->outBuf), pCommModeStr, "BINARY"); break; - } - return rtNoError; -} - -static cmdResponseType getCommProt(char *s, connectionRecType *context) -{ - (void)s; - const char *pCommProtStr = "COMM_PROT %s"; - - snprintf(context->outBuf, sizeof(context->outBuf), pCommProtStr, context->version); - return rtNoError; -} - -static cmdResponseType getComps(char *s, connectionRecType *context) -{ - if (s == NULL) - getCompInfo("", context); - else - getCompInfo(s, context); - return rtHandledNoError; -} - -static cmdResponseType getPins(char *s, connectionRecType *context) -{ - if (s == NULL) - getPinInfo("", 0, context); - else - getPinInfo(s, 0, context); - return rtHandledNoError; -} - -static cmdResponseType getPinVals(char *s, connectionRecType *context) - -{ - if (s == NULL) - getPinInfo("", 1, context); - else - getPinInfo(s, 1, context); - return rtHandledNoError; -} - -static cmdResponseType getSignals(char *s, connectionRecType *context) -{ - if (s == NULL) - getSigInfo("", 0, context); - else - getSigInfo(s, 0, context); - return rtHandledNoError; -} - -static cmdResponseType getSignalVals(char *s, connectionRecType *context) -{ - if (s == NULL) - getSigInfo("", 1, context); - else - getSigInfo(s, 1, context); - return rtHandledNoError; -} - -static cmdResponseType getParams(char *s, connectionRecType *context) -{ - if (s == NULL) - getParamInfo("", 0, context); - else - getParamInfo(s, 0, context); - return rtHandledNoError; -} - -static cmdResponseType getParamVals(char *s, connectionRecType *context) -{ - if (s == NULL) - getParamInfo("", 1, context); - else - getParamInfo(s, 1, context); - return rtHandledNoError; -} - -static cmdResponseType getFuncts(char *s, connectionRecType *context) -{ - if (s == NULL) - getFunctInfo("", context); - else - getFunctInfo(s, context); - return rtHandledNoError; -} - -static cmdResponseType getThreads(char *s, connectionRecType *context) -{ - if (s == NULL) - getThreadInfo("", context); - else - getThreadInfo(s, context); - return rtHandledNoError; -} - -static cmdResponseType getComp(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - getCompInfo(s, context); - return rtHandledNoError; -} - -static cmdResponseType getPin(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - getPinInfo(s, 0, context); - return rtHandledNoError; -} - -static cmdResponseType getPinVal(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - getPinInfo(s, 1, context); - return rtHandledNoError; -} - -static cmdResponseType getSignal(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - getSigInfo(s, 0, context); - return rtHandledNoError; -} - -static cmdResponseType getSignalVal(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - getSigInfo(s, 1, context); - return rtHandledNoError; -} - -static cmdResponseType getParam(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - getParamInfo(s, 0, context); - return rtHandledNoError; -} - -static cmdResponseType getParamVal(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - getParamInfo(s, 1, context); - return rtHandledNoError; -} - -static cmdResponseType getFunct(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - getFunctInfo(s, context); - return rtHandledNoError; -} - -static cmdResponseType getThread(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - getThreadInfo(s, context); - return rtHandledNoError; -} - -int commandGet(connectionRecType *context) -{ - static char *setNakStr = "GET NAK\r\n"; - static char *setCmdNakStr = "GET %s NAK\r\n"; - halCommandType cmd; - char *pch; - cmdResponseType ret = rtNoError; - - pch = strtok(NULL, delims); - if (pch == NULL) { - return write(context->cliSock, setNakStr, strlen(setNakStr)); - } - strupr(pch); - cmd = lookupHalCommand(pch); - switch (cmd) { - case hcEcho: ret = getEcho(pch, context); break; - case hcVerbose: ret = getVerbose(pch, context); break; - case hcEnable: ret = getEnable(pch, context); break; - case hcConfig: ret = getConfig(pch, context); break; - case hcCommMode: ret = getCommMode(pch, context); break; - case hcCommProt: ret = getCommProt(pch, context); break; - case hcComps: ret = getComps(strtok(NULL, delims), context); break; - case hcPins: ret = getPins(strtok(NULL, delims), context); break; - case hcPinVals: ret = getPinVals(pch, context); break; - case hcSigs: ret = getSignals(strtok(NULL, delims), context); break; - case hcSigVals: ret = getSignalVals(strtok(NULL, delims), context); break; - case hcParams: ret = getParams(strtok(NULL, delims), context); break; - case hcParamVals: ret = getParamVals(strtok(NULL, delims), context); break; - case hcFuncts: ret = getFuncts(strtok(NULL, delims), context); break; - case hcThreads: ret = getThreads(strtok(NULL, delims), context); break; - case hcComp: ret = getComp(strtok(NULL, delims), context); break; - case hcPin: ret = getPin(strtok(NULL, delims), context); break; - case hcPinVal: ret = getPinVal(strtok(NULL, delims), context); break; - case hcSig: ret = getSignal(strtok(NULL, delims), context); break; - case hcSigVal: ret = getSignalVal(strtok(NULL, delims), context); break; - case hcParam: ret = getParam(strtok(NULL, delims), context); break; - case hcParamVal: ret = getParamVal(strtok(NULL, delims), context); break; - case hcFunct: ret = getFunct(strtok(NULL, delims), context); break; - case hcThread: ret = getThread(strtok(NULL, delims), context); break; - case hcLoadRt: ; - case hcUnload: ; - case hcLoadUsr: ; - case hcLinkps: ; - case hcLinksp: ; - case hcLinkpp: ; - case hcNet: ; - case hcUnlinkp: ; - case hcLock: ; - case hcUnlock: ; - case hcNewSig: ; - case hcDelSig: ; - case hcSetP: ; - case hcSetS: ; - case hcAddF: ; - case hcDelF: ; - case hcSave: ; - case hcStart: ; - case hcStop: ; - case hcUnknown: ret = rtStandardError; - } - switch (ret) { - case rtNoError: // Standard ok response, just write value in buffer - sockWrite(context); - break; - case rtHandledNoError: // Custom ok response already handled, take no action - break; - case rtStandardError: // Standard error response - snprintf(context->outBuf, sizeof(context->outBuf), setCmdNakStr, pch); - sockWrite(context); - break; - case rtCustomError: // Custom error response entered in buffer - sockWrite(context); - break; - case rtCustomHandledError: ;// Custom error response handled, take no action - } - return 0; -} - -static int checkOnOff(char *s) -{ - static char *onStr = "ON"; - static char *offStr = "OFF"; - - if (s == NULL) return -1; - strupr(s); - if (strcmp(s, onStr) == 0) return 0; - if (strcmp(s, offStr) == 0) return 1; - return -1; -} - -static int checkBinaryASCII(char *s) -{ - static char *binaryStr = "BINARY"; - static char *ASCIIStr = "ASCII"; - - if (s == NULL) return -1; - strupr(s); - if (strcmp(s, ASCIIStr) == 0) return 0; - if (strcmp(s, binaryStr) == 0) return 1; - return -1; -} - -static cmdResponseType setEcho(char *s, connectionRecType *context) -{ - - switch (checkOnOff(s)) { - case -1: return rtStandardError; - case 0: context->echo = 1; break; - case 1: context->echo = 0; - } - return rtNoError; -} - -static cmdResponseType setVerbose(char *s, connectionRecType *context) -{ - - switch (checkOnOff(s)) { - case -1: return rtStandardError; - case 0: context->verbose = 1; break; - case 1: context->verbose = 0; - } - return rtNoError; -} - -static cmdResponseType setEnable(char *s, connectionRecType *context) -{ - if (strcmp(s, enablePWD) == 0) { - enabledConn = context->cliSock; - context->enabled = 1; - return rtNoError; - } - else - if (checkOnOff(s) == 1) { - context->enabled = 0; - enabledConn = -1; - return rtNoError; - } - else return rtStandardError; -} - -static cmdResponseType setConfig(char *s, connectionRecType *context) -{ - (void)s; - (void)context; - return rtNoError; -} - -static cmdResponseType setCommMode(char *s, connectionRecType *context) -{ - int ret; - - ret = checkBinaryASCII(s); - if (ret == -1) return rtStandardError; - context->commMode = ret; - return rtNoError; -} - -static cmdResponseType setCommProt(char *s, connectionRecType *context) -{ - (void)s; - char *pVersion; - - pVersion = strtok(NULL, delims); - if (pVersion == NULL) return rtStandardError; - rtapi_strxcpy(context->version, pVersion); - return rtNoError; -} - -static cmdResponseType setLoadRt(char *s, connectionRecType *context) -{ - char *pch; - char *args[MAX_TOK+3]; - int i; - - if (*s == '\0') return rtCustomHandledError; - i = 0; - pch = strtok(NULL, delims); - while (pch != NULL) { - args[i] = pch; - pch = strtok(NULL, delims); - i++; - } - args[i] = NULL; - if (doLoadRt(s, args, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setUnload(char *s, connectionRecType *context) -{ - if (doUnload(s, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setLoadUsr(char *s, connectionRecType *context) -{ - (void)context; - char *argv[MAX_TOK+1] = {NULL}; - - argv[0] = s; - argv[1] = "\0"; - if (doLoadUsr(argv) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setLinkps(char *p, char *s, connectionRecType *context) -{ - if ((p == NULL) || (s == NULL)) return rtStandardError; - if (doLink(p, s, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setLinksp(char *p, char *s, connectionRecType *context) -{ - if ((p == NULL) || (s == NULL)) return rtStandardError; - if (doLink(s, p, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setLinkpp(char *p1, char *p2, connectionRecType *context) -{ - if ((p1 == NULL) || (p2 == NULL)) return rtStandardError; - if (doLinkpp(p1, p2, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setNet(char *p1, connectionRecType *context) -{ - char *argv[MAX_TOK]; - int i; - char *pch; - - if (p1 == NULL) return rtStandardError; - i = 0; - - do { - pch = strtok(NULL, delims); - if (pch == NULL) break; - argv[i] = pch; - i++; - if (i >= MAX_TOK) return rtStandardError; // too many pins to link to the net - } while(1); - - if (i == 0) return rtStandardError; // no pins to link to the net - else - if (doNet(p1, argv, context) == 0) - return rtNoError; - else return rtCustomHandledError; -} - -static cmdResponseType setUnlink(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - if (doLink(s, "\0", context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setLock(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - if (doLock(s, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setUnlock(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - if (doUnlock(s, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setNewsig(char *s, char *t, connectionRecType *context) -{ - if ((s == NULL) || (t == NULL)) return rtStandardError; - if (doNewsig(s, t, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setDelsig(char *s, connectionRecType *context) -{ - if (s == NULL) return rtStandardError; - if (doDelsig(s, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setSetp(char *s, char *p, connectionRecType *context) -{ - if ((s == NULL) || (p == NULL)) return rtStandardError; - if (doSetp(s, p, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setSets(char *s, char *p, connectionRecType *context) -{ - if ((s == NULL) || (p == NULL)) return rtStandardError; - if (doSets(s, p, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setAddf(char *s, char *t, char *p, connectionRecType *context) -{ - if ((s == NULL) || (t == NULL)) return rtStandardError; - if (doAddf(s, t, p, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setDelf(char *s, char *t, connectionRecType *context) -{ - if ((s == NULL) || (t == NULL)) return rtStandardError; - if (doDelf(s, t, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setSave(char *type, char *fileName, connectionRecType *context) -{ - if (doSave(type, fileName, context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setStart(connectionRecType *context) -{ - if (doStart(context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -static cmdResponseType setStop(connectionRecType *context) -{ - if (doStop(context) == 0) - return rtNoError; - else - return rtCustomHandledError; -} - -#define MAX_TOKENS 5 - -int commandSet(connectionRecType *context) -{ - static char *setNakStr = "SET NAK\n\r"; - static char *setCmdNakStr = "SET %s NAK\n\r"; - static char *ackStr = "SET %s ACK\n\r"; - halCommandType cmd; - char *tokens[MAX_TOKENS]; - int i; - char *pch; - char *pcmd; - int retval = 0; - cmdResponseType ret = rtNoError; - - pcmd = strtok(NULL, delims); - if (pcmd == NULL) { - return write(context->cliSock, setNakStr, strlen(setNakStr)); - } - strupr(pcmd); - cmd = lookupHalCommand(pcmd); - if ((cmd >= hcCommProt) && (context->cliSock != enabledConn)) { - snprintf(context->outBuf, sizeof(context->outBuf), setCmdNakStr, pcmd); - return write(context->cliSock, context->outBuf, strlen(context->outBuf)); - } - pch = strtok(NULL, delims); - i = 0; - while (pch != NULL) { - tokens[i] = pch; - i++; - pch = strtok(NULL, delims); - } - switch (cmd) { - case hcEcho: ret = setEcho(tokens[0], context); break; - case hcVerbose: ret = setVerbose(tokens[0], context); break; - case hcEnable: ret = setEnable(tokens[0], context); break; - case hcConfig: ret = setConfig(tokens[0], context); break; - case hcCommMode: ret = setCommMode(tokens[0], context); break; - case hcCommProt: ret = setCommProt(tokens[0], context); break; - case hcComps: break; - case hcPins: break; - case hcPinVals: break; - case hcSigs: break; - case hcSigVals: break; - case hcParams: break; - case hcParamVals: break; - case hcFuncts: break; - case hcThreads: break; - case hcComp: break; - case hcPin: break; - case hcPinVal: break; - case hcSig: break; - case hcSigVal: break; - case hcParam: break; - case hcParamVal: break; - case hcFunct: break; - case hcThread: break; - case hcLoadRt: ret = setLoadRt(tokens[0], context); break; - case hcUnload: ret = setUnload(tokens[0], context); break; - case hcLoadUsr: ret = setLoadUsr(tokens[0], context); break; - case hcLinkps: setLinkps(tokens[0], tokens[1], context); break; - case hcLinksp: setLinksp(tokens[0], tokens[1], context); break; - case hcLinkpp: setLinkpp(tokens[0], tokens[1], context); break; - case hcNet: setNet(tokens[0], context); break; - case hcUnlinkp: setUnlink(tokens[0], context); break; - case hcLock: setLock(tokens[0], context); break; - case hcUnlock: setUnlock(tokens[0], context); break;; - case hcNewSig: setNewsig(tokens[0], tokens[1], context); break; - case hcDelSig: setDelsig(tokens[0], context); break; - case hcSetP: setSetp(tokens[0], tokens[1], context); break; - case hcSetS: setSets(tokens[0], tokens[1], context); break; - case hcAddF: setAddf(tokens[0], tokens[1], tokens[2], context); break; - case hcDelF: setDelf(tokens[0], tokens[1], context); break; - case hcSave: setSave(tokens[0], tokens[1], context); break; - case hcStart: setStart(context); break; - case hcStop: setStop(context); break; - case hcUnknown: ret = rtStandardError; - } - switch (ret) { - case rtNoError: - if (context->verbose) { - snprintf(context->outBuf, sizeof(context->outBuf), ackStr, pcmd); - retval = write(context->cliSock, context->outBuf, strlen(context->outBuf)); - } - break; - case rtHandledNoError: // Custom ok response already handled, take no action - break; - case rtStandardError: - snprintf(context->outBuf, sizeof(context->outBuf), setCmdNakStr, pcmd); - retval = write(context->cliSock, context->outBuf, strlen(context->outBuf)); - break; - case rtCustomError: // Custom error response entered in buffer - retval = write(context->cliSock, context->outBuf, strlen(context->outBuf)); - break; - case rtCustomHandledError: ;// Custom error response handled, take no action - } - return retval; -} - -int commandQuit(connectionRecType *context) -{ - printf("Closing connection with %s\n", context->hostName); - return -1; -} - -int commandShutdown(connectionRecType *context) -{ - if (context->cliSock == enabledConn) { - printf("Shutting down\n"); - hal_flag = 1; - hal_exit(comp_id); - exit(0); - return -1; - } - else - return 0; -} - -static int helpGeneral(connectionRecType *context) -{ - snprintf(context->outBuf, sizeof(context->outBuf), "Available commands:\n\r"); - rtapi_strxcat(context->outBuf, " Hello \n\r"); - rtapi_strxcat(context->outBuf, " Get \n\r"); - rtapi_strxcat(context->outBuf, " Set \n\r"); - rtapi_strxcat(context->outBuf, " Quit\n\r"); - rtapi_strxcat(context->outBuf, " Shutdown\n\r"); - rtapi_strxcat(context->outBuf, " Help \n\r"); - sockWrite(context); - return 0; -} - -static int helpHello(connectionRecType *context) -{ - snprintf(context->outBuf, sizeof(context->outBuf), "Usage:\n\r"); - rtapi_strxcat(context->outBuf, " Hello \n\rWhere:\n\r"); - rtapi_strxcat(context->outBuf, " Password is the connection password to allow communications with the CNC server.\n\r"); - rtapi_strxcat(context->outBuf, " Client Name is the name of client trying to connect, typically the network name of the client.\n\r"); - rtapi_strxcat(context->outBuf, " Protocol Version is the version of the protocol with which the client wishes to use.\n\r\n\r"); - rtapi_strxcat(context->outBuf, " With valid password, server responds with:\n\r"); - rtapi_strxcat(context->outBuf, " Hello Ack \n\rWhere:\n\r"); - rtapi_strxcat(context->outBuf, " Ack is acknowledging the connection has been made.\n\r"); - rtapi_strxcat(context->outBuf, " Server Name is the name of the EMC Server to which the client has connected.\n\r"); - rtapi_strxcat(context->outBuf, " Protocol Version is the client requested version or latest version support by server if"); - rtapi_strxcat(context->outBuf, " the client requests a version later than that supported by the server.\n\r\n\r"); - rtapi_strxcat(context->outBuf, " With invalid password, the server responds with:\n\r"); - rtapi_strxcat(context->outBuf, " Hello Nak\n\r"); - sockWrite(context); - return 0; -} - -static int helpGet(connectionRecType *context) -{ - snprintf(context->outBuf, sizeof(context->outBuf), "Usage:\n\rGet \n\r"); - rtapi_strxcat(context->outBuf, " Get commands require that a hello has been successfully negotiated.\n\r"); - rtapi_strxcat(context->outBuf, " Emc command may be one of:\n\r"); - rtapi_strxcat(context->outBuf, " Comm_mode\n\r"); - rtapi_strxcat(context->outBuf, " Comm_prot\n\r"); - rtapi_strxcat(context->outBuf, " Comp outBuf, " Comps\n\r"); - rtapi_strxcat(context->outBuf, " Echo\n\r"); - rtapi_strxcat(context->outBuf, " Enable\n\r"); - rtapi_strxcat(context->outBuf, " Funct \n\r"); - rtapi_strxcat(context->outBuf, " Functs\n\r"); - rtapi_strxcat(context->outBuf, " Param \n\r"); - rtapi_strxcat(context->outBuf, " Params\n\r"); - rtapi_strxcat(context->outBuf, " ParamVal \n\r"); - rtapi_strxcat(context->outBuf, " ParamVals\n\r"); - rtapi_strxcat(context->outBuf, " Pin \n\r"); - rtapi_strxcat(context->outBuf, " PinVal \n\r"); - rtapi_strxcat(context->outBuf, " Pins\n\r"); - rtapi_strxcat(context->outBuf, " PinVals\n\r"); - rtapi_strxcat(context->outBuf, " Signal \n\r"); - rtapi_strxcat(context->outBuf, " Signals\n\r"); - rtapi_strxcat(context->outBuf, " SigVal \n\r"); - rtapi_strxcat(context->outBuf, " SigVals\n\r"); - rtapi_strxcat(context->outBuf, " Thread \n\r"); - rtapi_strxcat(context->outBuf, " Threads\n\r"); - rtapi_strxcat(context->outBuf, " Verbose\n\r"); -// rtapi_strxcat(outBuf, "CONFIG\n\r"); - sockWrite(context); - return 0; -} - -static int helpSet(connectionRecType *context) -{ - snprintf(context->outBuf, sizeof(context->outBuf), "Usage:\n\r Set \n\r"); - rtapi_strxcat(context->outBuf, " Set commands require that a hello has been successfully negotiated,\n\r"); - rtapi_strxcat(context->outBuf, " in most instances requires that control be enabled by the connection.\n\r"); - rtapi_strxcat(context->outBuf, " The set commands not requiring control enabled are:\n\r"); - rtapi_strxcat(context->outBuf, " Comm_mode \n\r"); - rtapi_strxcat(context->outBuf, " Comm_prot \n\r"); - rtapi_strxcat(context->outBuf, " Echo \n\r"); - rtapi_strxcat(context->outBuf, " Enable \n\r"); - rtapi_strxcat(context->outBuf, " Verbose \n\r\n\r"); - rtapi_strxcat(context->outBuf, " The set commands requiring control enabled are:\n\r"); - rtapi_strxcat(context->outBuf, " Addf []\n\r"); - rtapi_strxcat(context->outBuf, " Delf \n\r"); - rtapi_strxcat(context->outBuf, " DelSig \n\r"); - rtapi_strxcat(context->outBuf, " Linkpp \n\r"); - rtapi_strxcat(context->outBuf, " Linkps \n\r"); - rtapi_strxcat(context->outBuf, " Linksp \n\r"); - rtapi_strxcat(context->outBuf, " Loadrt \n\r"); - rtapi_strxcat(context->outBuf, " Loadusr [ .. ]\n\r"); - rtapi_strxcat(context->outBuf, " Lock \n\r"); - rtapi_strxcat(context->outBuf, " Net [ .. \n\r"); - rtapi_strxcat(context->outBuf, " NewSig \n\r"); - rtapi_strxcat(context->outBuf, " Save [ []]\n\r"); - rtapi_strxcat(context->outBuf, " Setp \n\r"); - rtapi_strxcat(context->outBuf, " Sets \n\r"); - rtapi_strxcat(context->outBuf, " Start\n\r"); - rtapi_strxcat(context->outBuf, " Stop\n\r"); - rtapi_strxcat(context->outBuf, " Unlink \n\r"); - rtapi_strxcat(context->outBuf, " Unload \n\r"); - rtapi_strxcat(context->outBuf, " Unlock \n\r"); - - sockWrite(context); - return 0; -} - -static int helpQuit(connectionRecType *context) -{ - snprintf(context->outBuf, sizeof(context->outBuf), "Usage:\n\r"); - rtapi_strxcat(context->outBuf, " The quit command has the server initiate a disconnect from the client,\n\r"); - rtapi_strxcat(context->outBuf, " the command has no parameters and no requirements to have negotiated\n\r"); - rtapi_strxcat(context->outBuf, " a hello, or be in control."); - sockWrite(context); - return 0; -} - -static int helpShutdown(connectionRecType *context) -{ - snprintf(context->outBuf, sizeof(context->outBuf), "Usage:\n\r"); - rtapi_strxcat(context->outBuf, " The shutdown command terminates the connection with all clients,\n\r"); - rtapi_strxcat(context->outBuf, " and initiates a shutdown of EMC. The command has no parameters, and\n\r"); - rtapi_strxcat(context->outBuf, " can only be issued by the connection having control.\n\r"); - sockWrite(context); - return 0; -} - -static int helpHelp(connectionRecType *context) -{ - snprintf(context->outBuf, sizeof(context->outBuf), "If you need help on help, it is time to look into another line of work.\n\r"); - sockWrite(context); - return 0; -} - -int commandHelp(connectionRecType *context) -{ - char *pch; - - pch = strtok(NULL, delims); - if (pch == NULL) return (helpGeneral(context)); - strupr(pch); - printf("Command = %s", pch); - if (strcmp(pch, "HELLO") == 0) return (helpHello(context)); - if (strcmp(pch, "GET") == 0) return (helpGet(context)); - if (strcmp(pch, "SET") == 0) return (helpSet(context)); - if (strcmp(pch, "QUIT") == 0) return (helpQuit(context)); - if (strcmp(pch, "SHUTDOWN") == 0) return (helpShutdown(context)); - if (strcmp(pch, "HELP") == 0) return (helpHelp(context)); - snprintf(context->outBuf, sizeof(context->outBuf), "%s is not a valid command.", pch); - sockWrite(context); - return 0; -} - -commandTokenType lookupToken(char *s) -{ - commandTokenType i = cmdHello; - int temp; - - while (i < cmdUnknown) { - if (strcmp(commands[i], s) == 0) return i; -// (int)i += 1; - temp = i; - temp++; - i = (commandTokenType) temp; - } - return i; -} - -int parseCommand(connectionRecType *context) -{ - int ret = 0; - char *pch; - char s[64]; - static char *helloNakStr = "HELLO NAK\r\n"; - static char *helloAckStr = "HELLO ACK %s 1.1\r\n"; - static char *setNakStr = "SET NAK\r\n"; - - pch = strtok(context->inBuf, delims); - snprintf(s, sizeof(s), helloAckStr, serverName); - if (pch != NULL) { - strupr(pch); - switch (lookupToken(pch)) { - case cmdHello: - if (commandHello(context) == -1) - ret = write(context->cliSock, helloNakStr, strlen(helloNakStr)); - else - ret = write(context->cliSock, s, strlen(s)); - break; - case cmdGet: - ret = commandGet(context); - break; - case cmdSet: - if (context->linked == 0) - ret = write(context->cliSock, setNakStr, strlen(setNakStr)); - else ret = commandSet(context); - break; - case cmdQuit: - ret = commandQuit(context); - break; - case cmdShutdown: - ret = commandShutdown(context); - break; - case cmdHelp: - ret = commandHelp(context); - break; - case cmdUnknown: ret = -2; - } - } - return ret; -} - -void *readClient(void *arg) -{ - (void)arg; - char str[1600]; - char buf[1600]; - unsigned int i, j; - int len; - int ret; - connectionRecType *context; - - -// res = 1; - context = (connectionRecType *) malloc(sizeof(connectionRecType)); - if(NULL == context) { - perror("readClient():malloc"); - abort(); // There is no "clean" way. Ensure we make some noise. - } - context->cliSock = client_sockfd; - context->linked = 0; - context->echo = 1; - context->verbose = 0; - rtapi_strxcpy(context->version, "1.0"); - rtapi_strxcpy(context->hostName, "Default"); - connCount++; - context->commMode = 0; - context->commProt = 0; - context->inBuf[0] = 0; - buf[0] = 0; - - while (1) { - len = read(context->cliSock, &str, 1600); - if (len <= 0) goto finished; - str[len] = 0; - rtapi_strxcat(buf, str); - if (!memchr(str, 0x0d, strlen(str))) continue; - if ((context->echo == 1) && (context->linked == 1)) { - ret = write(context->cliSock, &buf, strlen(buf)); - if (ret < 0) { - goto finished; - } - } - i = 0; - j = 0; - while (i <= strlen(buf)) { - if ((buf[i] != '\n') && (buf[i] != '\r')) { - context->inBuf[j] = buf[i]; - j++; - } - else if (j > 0) - { - context->inBuf[j] = 0; - if (parseCommand(context) == -1) goto finished; - j = 0; - } - i++; - } - buf[0] = 0; - } - -finished: - close(context->cliSock); - free(context); - pthread_exit((void *)0); -} - -/*********************************************************************** -* MAIN PROGRAM * -************************************************************************/ - - /* main() is responsible for parsing command line options, and then - parsing either a single command from the command line or a series - of commands from a file or standard input. It breaks the command[s] - into tokens, and passes them to parse_cmd() which does the actual - work for each command. -*/ - -int sockMain() -{ - pthread_t thrd; - int res; - - while (1) { - - client_len = sizeof(client_address); - client_sockfd = accept(server_sockfd, - (struct sockaddr *)&client_address, &client_len); - if (client_sockfd < 0) exit(0); - res = pthread_create(&thrd, NULL, readClient, (void *)NULL); - if (res != 0) - close(client_sockfd); - } - return 0; -} - -int main(int argc, char **argv) -{ - int n, fd; - int errorcount; - int opt; - char *cp1, *filename = NULL; - FILE *srcfile = NULL; - - /* set default level of output - 'quiet' */ - rtapi_set_msg_level(RTAPI_MSG_ERR); - /* set default for other options */ - // process halrmt command line args - while((opt = getopt_long(argc, argv, "e:n:p:s:w:", longopts, NULL)) != -1) { - switch(opt) { - case 'e': snprintf(enablePWD, sizeof(enablePWD), "%s", optarg); break; - case 'n': snprintf(serverName, sizeof(serverName), "%s", optarg); break; - case 'p': sscanf(optarg, "%d", &port); break; - case 's': sscanf(optarg, "%d", &maxSessions); break; - case 'w': snprintf(pwd, sizeof(pwd), "%s", optarg); break; - } - } - - /* start parsing halcmd options */ - n = optind; - while ((n < argc) && (argv[n][0] == '-')) { - cp1 = argv[n++]; - /* loop to parse grouped options */ - while (*(++cp1) != '\0') { - switch (*cp1) { - case 'R': - /* force an unlock of the HAL mutex - to be used after a segfault in a hal program */ - if (release_HAL_mutex() < 0) { - printf("halrmt: Release Mutex failed!\n"); - return 1; - } - return 0; - break; - case 'h': - /* -h = help */ - if (argc > n) { /* there are more arguments, n has been incremented already */ - do_help_cmd(argv[n]); - } else - print_help_general(1); - return 0; - break; - case 'q': - /* -q = quiet (default) */ - rtapi_set_msg_level(RTAPI_MSG_ERR); - break; - case 'Q': - /* -Q = very quiet */ - rtapi_set_msg_level(RTAPI_MSG_NONE); - break; - case 's': - /* script friendly mode */ - scriptmode = 1; - break; - case 'v': - /* -v = verbose */ - rtapi_set_msg_level(RTAPI_MSG_INFO); - break; - case 'V': - /* -V = very verbose */ - rtapi_set_msg_level(RTAPI_MSG_ALL); - break; - case 'f': - /* -f = read from file (or stdin) */ - if (srcfile == NULL) { - /* it's the first -f (ignore repeats) */ - if ((n < argc) && (argv[n][0] != '-')) { - /* there is a following arg, and it's not an option */ - filename = argv[n++]; - srcfile = fopen(filename, "r"); - if (srcfile == NULL) { - fprintf(stderr, - "Could not open command file '%s'\n", - filename); - exit(-1); - } - /* make sure file is closed on exec() */ - fd = fileno(srcfile); - fcntl(fd, F_SETFD, FD_CLOEXEC); - } else { - /* no filename followed -f option, use stdin */ - srcfile = stdin; - prompt_mode = 1; - } - } - break; -#ifndef NO_INI - case 'i': - /* -i = allow reading 'setp' values from an INI file */ - if (inifile == NULL) { - /* it's the first -i (ignore repeats) */ - if ((n < argc) && (argv[n][0] != '-')) { - /* there is a following arg, and it's not an option */ - filename = argv[n++]; - inifile = fopen(filename, "r"); - if (inifile == NULL) { - fprintf(stderr, - "Could not open INI file '%s'\n", - filename); - exit(-1); - } - /* make sure file is closed on exec() */ - fd = fileno(inifile); - fcntl(fd, F_SETFD, FD_CLOEXEC); - } else { - /* no filename followed -i option, error */ - fprintf(stderr, - "No missing INI filename for -i option\n"); - exit(-1); - } - } - break; -#endif /* NO_INI */ - default: - /* unknown option */ - printf("Unknown option '-%c'\n", *cp1); - break; - } - } - } - signal(SIGINT, quit); - signal(SIGTERM, quit); - signal(SIGPIPE, SIG_IGN); - /* at this point all options are parsed, connect to HAL */ - /* create a unique module name, to allow for multiple halrmt's */ - snprintf(comp_name, HAL_NAME_LEN, "halrmt%d", getpid()); - /* tell the signal handler that we might have the mutex */ - hal_flag = 1; - /* connect to the HAL */ - comp_id = hal_init(comp_name); - /* done with mutex */ - hal_flag = 0; - /* check result */ - if (comp_id < 0) { - fprintf(stderr, "halrmt: hal_init() failed: %d\n", comp_id ); - fprintf(stderr, "NOTE: 'rtapi' kernel module must be loaded\n" ); - return 1; - } - hal_ready(comp_id); - errorcount = 0; - initSockets(); - /* HAL init is OK, let's process the command(s) */ - /* tell the signal handler we might have the mutex */ - sockMain(); - hal_flag = 1; - hal_exit(comp_id); - if ( errorcount > 0 ) { - return 1; - } else { - return 0; - } -} - diff --git a/src/hal/utils/halrmt.cc b/src/hal/utils/halrmt.cc new file mode 100644 index 00000000000..1efda42bd09 --- /dev/null +++ b/src/hal/utils/halrmt.cc @@ -0,0 +1,3320 @@ +/******************************************************************** +* Description: halrmt.cc +* Simple telnet interface to LinuxCNC HAL commands (halcmd) +* +* Derived from work by jmkasunich +* +* Other contributors: +* Alex Joni +* +* Author: Eric H. Johnson +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2006-2008 All rights reserved. +* Copyright (c) 2026 B.Stultiens +* +* Last change: +* 2026 - Mostly rewritten +********************************************************************/ + +//****************************************************************** +// Using halrmt: see man halrmt(1) +//****************************************************************** + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "setps_util.h" + +using namespace linuxcnc; + +#define NELEM(x) (sizeof(x) / sizeof(*(x))) + +// To prevent excessive hostnames only +#define MAX_HOSTNAME_SIZE 80 + +static std::string helloPwd = "EMC"; // Connect password +static std::string enablePwd = "EMCTOO"; // Enable password +static std::string serverName = "EMCNETSVR"; // Server name written in hello response +static int port = 5006; +static std::string inifilename; +static volatile int quitloop = 0; // Signal to main loop to exit +static int quiet = 0; + +static unsigned maxSessions = 0; // Maximum number of sessions to allow + +static double mytimeofday(); + +typedef enum { + PS_IDLE = 0, + PS_WAITCOMP, + PS_WAITPROC, +} process_enum_t; + +typedef struct __connectionrectype_t connectionRecType; // Forward decl. + +typedef struct __process_state_t { + process_enum_t state; // Waiting sequence for loadusr -W/-w + volatile pid_t pid; // Also set by the signal handler + volatile int retval; // Set by the signal handler (< 0 if signal terminated process) + std::string acknak; // The command in the answer after it finishes + int (*cascade)(connectionRecType &); // Async wait function to cascade into (NULL if done) + std::string insmod; // For loadrt's insmod string set in HAL + std::string name_proc; // Process name (executable) + std::string name_comp; // Component name (explicitly set with loadusr -n compname) + std::set comp_names_pre; + bool wait_comp; // Set on loadusr -W + bool wait_proc; // Set on loadusr -w + bool ignore_rv; // Set on loadusr -i + bool ready; // The component was detected ready + volatile bool exited; // Process exited, also set by the signal handler + bool pacify; // Set when the "Waiting for..." text needs termination + double timestamp; // Start of the new process + int nextmsg; // Progressive second count for pacifier message + + void clear() { + state = PS_IDLE; + pid = 0; + retval = 0; + acknak.clear(); + cascade = NULL; + insmod.clear(); + name_proc.clear(); + name_comp.clear(); + comp_names_pre.clear(); + wait_comp = false; + wait_proc = false; + ignore_rv = false; + ready = false; + exited = false; + pacify = false; + timestamp = mytimeofday(); + nextmsg = 3; + }; +} process_state_t; + +typedef struct __connectionrectype_t { + int sock; // Client socket + std::vector toks; // Tokenized command + std::string inbuf; // Input buffer, contains all data from read() + std::string outbuf; // Output buffer, contains all data to write() + std::string hostname; // Name of the client + std::string version; // Client's version + std::string inifilename; // Local ini filename for the connection + process_state_t process; // Async state for doLoadUsr + bool halfclosed; + bool linked; + bool enabled; + bool echo; // Echo input + bool verbose; // Show GET/SET xxx ACK on every command + bool timestamp; + bool timefmt; + bool expand; // Enable INI-file expansion + bool header; // Show a header with PIN{VAL}S, PARAM{VAL}S, SIGNAL{VAL}S, FUNCTS, THREADS +} connectionRecType; + +static std::vector clients; // Client connections + + +typedef enum { cmdUnknown, cmdHello, cmdSet, cmdGet, cmdQuit, cmdShutdown, cmdHelp } cmdType; + +typedef enum { + hcUnknown, + hcEcho, hcVerbose, hcEnable, hcExpand, hcHeader, + hcTime, hcTimestamp, + hcIni, hcIniFile, + hcSave, + hcPin, hcPinVal, hcPins, hcPinVals, + hcParam, hcParamVal, hcParams, hcParamVals, + hcSig, hcSigVal, hcSigs, hcSigVals, + hcComp, hcComps, + hcFunct, hcFuncts, + hcThread, hcThreads, + hcLoadRt, hcUnload, hcLoadUsr, + hcLinkPS, hcLinkSP, hcNet, hcUnlinkP, + hcLock, hcUnlock, + hcNewSig, hcDelSig, + hcSetP, hcSetS, + hcAddF, hcDelF, + hcStart, hcStop +} subCmdType; + +typedef enum { + rtOk, + rtError, + rtDelayed +} cmdResponseType; + +typedef struct { + cmdType command; + unsigned nargs; // Minimum number of arguments + const char *name; +} commandListType; + +// Must be sorted on 'name' +static const commandListType commandList[] = { + {cmdGet, 1, "GET" }, + {cmdHello, 2, "HELLO" }, + {cmdHelp, 0, "HELP" }, + {cmdQuit, 0, "QUIT" }, + {cmdSet, 1, "SET" }, + {cmdShutdown, 0, "SHUTDOWN"}, +}; + +enum accessLevelType { + alNone, // No restrictions + alHello, // Must have issued successful HELLO + alEnable // Must have issued successful SET ENABLE +}; + +typedef struct { + subCmdType command; + const char *name; + unsigned nget; // Minimum getter arg count (token count = nget + 2) + unsigned nset; // minimum setter arg count (token count = nget + 2) + cmdResponseType (*getter)(connectionRecType &); + cmdResponseType (*setter)(connectionRecType &); + accessLevelType acclvl; // Access level + const char *gethelp; // Argument list for GET + const char *sethelp; // Argument list for SET +} getsetListType; + +static void info(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +static void info(const char *fmt, ...) +{ + if(quiet > 0) + return; + va_list va; + va_start(va, fmt); + fprintf(stdout, "halrmt: "); + vfprintf(stdout, fmt, va); + fprintf(stdout, "\n"); + fflush(stdout); + va_end(va); +} + +static void error(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +static void error(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + fprintf(stderr, "halrmt: "); + vfprintf(stderr, fmt, va); + fprintf(stderr, "\n"); + fflush(stderr); + va_end(va); +} + +static void xperror(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +static void xperror(const char *fmt, ...) +{ + int en = errno; + va_list va; + va_start(va, fmt); + fprintf(stderr, "halrmt: "); + vfprintf(stderr, fmt, va); + fprintf(stderr, ": %s\n", strerror(en)); + fflush(stderr); + va_end(va); +} + +static void mysleep(double secs) +{ + struct timespec ts, rem; + ts.tv_sec = (time_t)secs; + ts.tv_nsec = 1000000000l * (secs - ts.tv_sec); +retry: + int err = nanosleep(&ts, &rem); + if(err < 0) { + if(EINTR == errno) { + ts = rem; + goto retry; + } + xperror("nanosleep"); + } +} + +static double mytimeofday(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (double)tv.tv_sec + tv.tv_usec * 1e-6; +} + +static void flushall(void) +{ + // Try to write pending data to each socket + for(auto &c : clients) { + if(!c.outbuf.size()) + continue; + + ssize_t res = write(c.sock, c.outbuf.c_str(), c.outbuf.size()); + if (res < 0) { + if (errno != EINTR && errno != EAGAIN) { + xperror("flushall: client write()"); + // Make this connection to close/abort + c.halfclosed = true; + c.inbuf.clear(); + c.outbuf.clear(); + } + } else { + c.outbuf.erase(0, res); // Remove written data from buffer + } + } +} + +static void reply(connectionRecType &ctx, const std::string &s) +{ + if (ctx.timestamp) { + struct timeval tv; + gettimeofday(&tv, NULL); + if (ctx.timefmt) { + ctx.outbuf.append(fmt::format("{}.{:06d}: ", tv.tv_sec, tv.tv_usec)); + } else { + char tstr[32]; // Enough to hold 'yyyy-mm-dd hh:mm:ss.000000' + struct tm lt; + localtime_r(&tv.tv_sec, <); + strftime(tstr, sizeof(tstr), "%F %T", <); + ctx.outbuf.append(fmt::format("{}.{:06d}: ", tstr, tv.tv_usec)); + } + } + ctx.outbuf.append(s); +} + +static void replynl(connectionRecType &ctx, const std::string &s) +{ + reply(ctx, s + "\r\n"); +} + +static void errornl(connectionRecType &ctx, const std::string &s) +{ + reply(ctx, "error: " + s + "\r\n"); +} + +static void warnnl(connectionRecType &ctx, const std::string &s) +{ + reply(ctx, "warning: " + s + "\r\n"); +} + +static bool to_int(const std::string &s, int &i) +{ + try { + size_t pos; + i = std::stoi(s, &pos); + return pos == s.size(); + } catch (std::exception const &) { + return false; + } +} + + +static int compareNoCase(const std::string &a, const std::string &b) +{ + for (auto i = a.begin(), j = b.begin(); i != a.end() && j != b.end(); ++i, ++j) { + int d = std::toupper((unsigned char)*i) - std::toupper((unsigned char)*j); + if (d) + return d; // Trivial, difference in character sequence + } + // Until here, all characters are the same, but we may have broken out of + // the compare loop due to a difference in string length and both strings + // share the same prefix. + // Non-trivial compare, sizes differ on same string prefix: + // a=ABC + // b=ABC\0D + // We cannot use the difference between character ordinal because we have + // to account for embedded NUL characters. Therefore, we must use the sizes + // to determine rank. Using the difference of sizes also matches equal + // length, i.e. when the strings are equal. + return (int)((ssize_t)a.size() - (ssize_t)b.size()); +} + +// Support functions +static const char *data_type(hal_type_t type) +{ + switch (type) { + case HAL_BOOL: return "bool "; + case HAL_REAL: return "real "; + case HAL_S32: return "s32 "; + case HAL_U32: return "u32 "; + case HAL_SINT: return "sint "; + case HAL_UINT: return "uint "; + case HAL_PORT: return "port "; + default: return "undef"; // Shouldn't happen... + } +} + +/* Switch function for pin direction for the print_*_list functions */ +static const char *pin_data_dir(hal_pdir_t dir) +{ + switch (dir) { + case HAL_IN: return "IN "; + case HAL_OUT: return "OUT"; + case HAL_IO: return "I/O"; + default: return "???"; // Shouldn't happen... + } +} + +/* Switch function for param direction for the print_*_list functions */ +static const char *param_data_dir(hal_pdir_t dir) +{ + switch (dir) { + case HAL_RO: return "RO"; + case HAL_RW: return "RW"; + default: return "??"; // Shouldn't happen... + } +} + +/* Switch function for arrow direction for the print_*_list functions */ +static const char *data_arrow1(hal_pdir_t dir) +{ + switch (dir) { + case HAL_IN: return "<=="; + case HAL_OUT: return "==>"; + case HAL_IO: return "<=>"; + default: return "???"; // Shouldn't happen... + } +} + +/* Switch function for arrow direction for the print_*_list functions */ +static const char *data_arrow2(hal_pdir_t dir) +{ + switch (dir) { + case HAL_IN: return "==>"; + case HAL_OUT: return "<=="; + case HAL_IO: return "<=>"; + default: return "???"; // Shouldn't happen... + } +} + +static std::string data_value(hal_type_t type, hal_query_value_u val) +{ + switch (type) { + case HAL_BOOL: return val.b ? " TRUE" : " FALSE"; + case HAL_REAL: return fmt::format("{:12.7g}", val.r); + case HAL_PORT: // FIXME + case HAL_S32: + case HAL_SINT: return fmt::format(" {:10d}", val.s); + case HAL_U32: + case HAL_UINT: return fmt::format(" {:08X}", val.u); + default: return " undef "; + } +} + +static std::string data_value2(hal_type_t type, hal_query_value_u val) +{ + switch (type) { + case HAL_BOOL: return val.b ? "TRUE" : "FALSE"; + case HAL_REAL: return fmt::format("{:.7g}", val.r); + case HAL_PORT: // FIXME + case HAL_S32: + case HAL_SINT: return fmt::format("{}", val.s); + case HAL_U32: + case HAL_UINT: return fmt::format("{}", val.u); + default: return "unknown_type"; + } +} + +static std::optional getIniVar(connectionRecType &ctx, const std::string &var, const std::string §ion) +{ + if(ctx.inifilename.empty()) { + return std::nullopt; + } + + IniFile inifile(ctx.inifilename); + if(!inifile) { + return std::nullopt; + } + + return inifile.findString(var, section); +} + +static inline bool isEnabled(const connectionRecType &ctx) +{ + return ctx.enabled; +} + +static inline bool doDisable(connectionRecType &ctx) +{ + if (!isEnabled(ctx)) { + return false; + } + ctx.enabled = false; + info("Disabled '%s' (%d)", ctx.hostname.c_str(), ctx.sock); + return true; +} + +static inline bool doEnable(connectionRecType &ctx) +{ + if (isEnabled(ctx)) { + return false; + } + ctx.enabled = true; + info("Enabled '%s' (%d)", ctx.hostname.c_str(), ctx.sock); + return true; +} + +static inline bool doUnlink(connectionRecType &ctx) +{ + if (!ctx.linked) { + return false; + } + ctx.linked = false; + info("Unlinked '%s' (%d)", ctx.hostname.c_str(), ctx.sock); + return true; +} + +static inline bool doLink(connectionRecType &ctx) +{ + if (ctx.linked) { + return false; + } + ctx.linked = true; + info("Linked '%s' (%d)", ctx.hostname.c_str(), ctx.sock); + return true; +} + +typedef struct { + int value; + const char *keyword; +} keywordValueType; + +// Command argument keyword enum +enum { + kwOn, kwOff, + kwAll, kwLoad, kwConfig, kwParams, kwRun, kwTune, kwNone, +}; + +static int checkKeywords(const std::string &s, const keywordValueType *kv, size_t n) +{ + // Going through the list linearly is O(n), but the lists are no longer + // than 8 so binary search does not really make sense. + for (size_t i = 0; i < n; i++) { + if (!compareNoCase(kv[i].keyword, s)) + return kv[i].value; + } + return -1; +} + +static int checkOnOff(const std::string &s) +{ + static const keywordValueType kv[] = { + {kwOn, "ON" }, + {kwOff, "OFF" }, + {kwOn, "1" }, // Be tolerant + {kwOff, "0" }, + {kwOn, "TRUE" }, // Be very tolerant + {kwOff, "FALSE"}, + {kwOn, "YES" }, // Be extremely tolerant + {kwOff, "NO" }, + }; + return checkKeywords(s, kv, NELEM(kv)); +} + +static int checkLock(const std::string &s) +{ + static const keywordValueType kv[] = { + {kwAll, "ALL" }, + {kwLoad, "LOAD" }, + {kwConfig, "CONFIG"}, + {kwParams, "PARAMS"}, + {kwRun, "RUN" }, + {kwTune, "TUNE" }, + {kwNone, "NONE" }, + }; + return checkKeywords(s, kv, NELEM(kv)); +} + +static inline const char *onOff(bool b) +{ + return b ? "ON" : "OFF"; +} + +static std::string onOffString(const std::string &s, bool b) +{ + return fmt::format("{} {}", s, onOff(b)); +} + + +// Signal handler cannot call exit. +// Any exit in the signal handler would leave shared memory segments behind. +static void sigQuit(int sig) +{ + (void)sig; + quitloop = 1; +} + +// A child died and we need to wait for it to remove the zombie. +static void sigChld(int sig) +{ + (void)sig; + while(true) { // Need to catch all zombies + int status = 0; + int rv = waitpid(0, &status, WNOHANG); + if(rv <= 0) + return; // On error or no zombies left + + if(WIFSTOPPED(status) || WIFCONTINUED(status)) + continue; + + // Get here and some process terminated. Try to find the + // associated process and see if we were waiting for it. + for(auto &cl : clients) { + if(rv == cl.process.pid) { + cl.process.pid = 0; + cl.process.exited = true; + // The process terminated normally or on a signal + if(WIFEXITED(status)) { + cl.process.retval = WEXITSTATUS(status); + } else { + cl.process.retval = -WTERMSIG(status); + } + break; + } + } + } +} + +static pid_t halrmt_systemv_nowait(connectionRecType &ctx, const std::vector &argv) +{ + // Need program name and a NULL + if(argv.size() < 2) { + errornl(ctx, "halrmt_systemv_nowait: no program or arguments"); + errno = EINVAL; + return -1; + } + if(NULL != argv[argv.size()-1]) { + errornl(ctx, "halrmt_systemv_nowait: argument list not NULL terminated"); + errno = EINVAL; + return -1; + } + + // disconnect from the HAL shmem area before forking + hal_lib_exit(); + + int err; + pid_t pid = fork(); + switch(pid) { + case -1: // Error + err = errno; + hal_lib_init(); // Reconnect + errornl(ctx, fmt::format("halrmt_systemv_nowait: Fork failed: errno={} ({})", err, strerror(err))); + errno = err; + return -1; + case 0: // Child + // Restore signals to default before exec + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + execvp(argv[0], (char * const *)argv.data()); + fprintf(stderr, "halrmt: Child execvp(%s,...) failed: %s\n", argv[0], strerror(errno)); + _exit(1); + break; + default: // Parent + hal_lib_init(); // Reconnect + break; + } + return pid; +} + +// FIXME: This is not done using a coroutine. It blocks the main loop and all +// clients. +static int halrmt_systemv(connectionRecType &ctx, const std::vector &argv) +{ + // Setup for the signal handler + ctx.process.clear(); + + // Block SIGCHILD until we have registered the PID or we might miss to + // capture the exit. The signal handler searches for the PID. + sigset_t sigs; + sigemptyset(&sigs); + sigaddset(&sigs, SIGCHLD); + sigprocmask(SIG_BLOCK, &sigs, NULL); + + // Create the new user process + ctx.process.pid = halrmt_systemv_nowait(ctx, argv); + int err = errno; + + // Restore old sigmask now we have the registration in place + sigprocmask(SIG_UNBLOCK, &sigs, NULL); + + if(ctx.process.pid < 0) + return -err; + + while(true) { + if(ctx.process.exited) { + break; + } + mysleep(0.01); + } + + if(ctx.process.retval > 0) { + errornl(ctx, fmt::format("{}: exit value: {}", argv[0], ctx.process.retval)); + return ctx.process.retval; + } else if(ctx.process.retval < 0) { + errornl(ctx, fmt::format("{}: exit on signal {}", argv[0], -ctx.process.retval)); + return -EINTR; + } + return 0; +} + +/*********************************************************************** +* LOCAL FUNCTION DEFINITIONS * +************************************************************************/ + +static int unloadrt_cb(hal_query_t *q, void *arg) +{ + std::vector *comps = reinterpret_cast *>(arg); + if(HAL_COMP_TYPE_REALTIME == q->comp.type) { + comps->push_back(q->name); + } + return 0; +} + +static int unloadrt_comp(connectionRecType &ctx, const std::string &mod_name) +{ + std::vector argv; + +#if defined(RTAPI_USPACE) + argv.push_back(EMC2_BIN_DIR "/rtapi_app"); + argv.push_back("unload"); +#else + argv.push_back(EMC2_BIN_DIR "/linuxcnc_module_helper"); + argv.push_back("remove"); +#endif + argv.push_back(mod_name.c_str()); + argv.push_back(NULL); + + return halrmt_systemv(ctx, argv); +} + +// +// It turns out that it is not portable to reset the state of getopt, so that a +// different argv list can be parsed. +// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=192834 +// +// (though that thread ends with the bug being closed as fixed in lenny, it is +// not fixed or has regressed by debian jessie) +// +static void reset_getopt_state() +{ +#ifdef __GNU_LIBRARY__ + optind = 0; +#else + optind = 1; +#endif +#ifdef HAVE_OPTRESET + optreset = 1; +#endif +} + +static std::string guess_comp_name(const std::string &prog_name) +{ + std::string name; + size_t pos = prog_name.find_last_of('/'); + if(std::string::npos != pos) { + name = prog_name.substr(pos + 1); + } else { + name = prog_name; + } + pos = name.find_last_of('.'); + if(std::string::npos != pos) { + name.erase(pos); + } + return name; +} + +static int get_all_comp_names_cb(hal_query_t *q, void *arg) +{ + std::set *comps = reinterpret_cast *>(arg); + comps->insert(q->name); + return 0; +} + +static std::set get_all_comp_names() { + std::set result; + hal_query_t q = {}; + hal_list_comp(&q, get_all_comp_names_cb, &result); + return result; +} + +static void warn_newly_loaded_comps(connectionRecType &ctx, std::set &names, const std::string &newname) +{ + auto new_names = get_all_comp_names(); + for(const auto &name : new_names) { + if(name == newname) continue; + if(names.find(name) == names.end()) { + errornl(ctx, fmt::format("While waiting for '{}', component '{}' loaded.\r\n" + "Did you specify the correct name via 'loadusr -Wn'?", + newname, name)); + flushall(); + } + } + std::swap(new_names, names); +} + +static int doLoadUsr(connectionRecType &ctx, const std::vector &args) +{ + if(args.size() < 3) { + // We need: dummy prog + // The dummy is for getopt. + errornl(ctx, fmt::format("internal: doLoadUsr: Not enough arguments")); + return -EINVAL; + } + if(NULL != args[args.size() - 1]) { + errornl(ctx, fmt::format("internal: doLoadUsr: Argument list not NULL terminated")); + return -EINVAL; + } + + if(hal_get_lock() & HAL_LOCK_LOAD) { + errornl(ctx, "doLoadUsr: HAL is locked, loading of programs is not permitted"); + return -EPERM; + } + + ctx.process.clear(); + + // This is a loadusr call + // Check for options (-w, -i, -W and/or -n) + reset_getopt_state(); + int optc; + int argc = args.size() - 1; // Skip the terminating NULL in the count + while (-1 != (optc = getopt(argc, (char * const *)args.data(), "+wWin:"))) { + switch(optc) { + case 'w': ctx.process.wait_proc = true; break; + case 'W': ctx.process.wait_comp = true; break; + case 'i': ctx.process.ignore_rv = true; break; + case 'n': ctx.process.name_comp = optarg; break; + default: + errornl(ctx, fmt::format("doLoadUsr: Invalid option '-{}'", isprint(optc) ? optc : '?')); + return -EINVAL; + } + } + // Collect the remainder as the program to execute + std::vector argv; + for(unsigned i = optind; i < args.size(); i++) { + argv.push_back(args[i]); + } + // Double check argument list + if(argv.size() < 2) { + errornl(ctx, fmt::format("doLoadUsr: Not enough arguments for halrmt_systemv. Missing program name?")); + return -EINVAL; + } + if(NULL != argv[argv.size() - 1]) { + errornl(ctx, fmt::format("internal: doLoadUsr: Argument list not NULL terminated for halrmt_systemv")); + return -EINVAL; + } + + ctx.process.name_proc = argv[0]; + // Get component name + if(ctx.process.name_comp.empty()) { + ctx.process.name_comp = guess_comp_name(argv[0]); + } + + // Component status quo + ctx.process.comp_names_pre = get_all_comp_names(); + + // Block SIGCHILD until we have registered the PID or we might miss to + // capture the exit. The signal handler searches for the PID. + sigset_t sigs; + sigemptyset(&sigs); + sigaddset(&sigs, SIGCHLD); + sigprocmask(SIG_BLOCK, &sigs, NULL); + + // Create the new user process + ctx.process.pid = halrmt_systemv_nowait(ctx, argv); + int err = errno; + + // Restore old sigmask now we have the registration in place + sigprocmask(SIG_UNBLOCK, &sigs, NULL); + + if(ctx.process.pid < 0) { + errornl(ctx, fmt::format("doLoadUsr: Failed to execute '{}'", argv[0])); + ctx.process.pid = 0; + ctx.process.exited = true; + return -err; + } + + // The wait sequence can be one of: + // - wait component, wait process + // - wait component + // - wait process + if(ctx.process.wait_comp) { + ctx.process.state = PS_WAITCOMP; + } else if(ctx.process.wait_proc) { + ctx.process.state = PS_WAITPROC; + } + return 0; +} + +static void waitPacify(connectionRecType &ctx) +{ + // Pacify the user on long waits + double delta = mytimeofday() - ctx.process.timestamp; + if(delta >= ctx.process.nextmsg) { + if(0 == (ctx.process.nextmsg - 3) % 30) { + reply(ctx, fmt::format("{}Waiting for component '{}' to become ready.", + ctx.process.pacify ? "\n" : "", ctx.process.name_comp)); + warn_newly_loaded_comps(ctx, ctx.process.comp_names_pre, ctx.process.name_comp); + } else { + reply(ctx, "."); + warn_newly_loaded_comps(ctx, ctx.process.comp_names_pre, ctx.process.name_comp); + } + ctx.process.nextmsg++; + ctx.process.pacify = true; + } +} + +static int waitComponent(connectionRecType &ctx) +{ + // Still processing and waiting for the component to come online + // Check for component from program becoming ready + hal_query_t q = {}; + int rv = hal_comp_by_name(ctx.process.name_comp.c_str(), &q); + ctx.process.ready = !rv && q.comp.ready; + + if(ctx.process.ready) { + return 0; // Done + } + + // This happens when SIGCHLD has hit + if(ctx.process.exited) { + // Getting here means the component does not exist and the process + // responsible for creating it is dead. For rtapi_app, it called + // rtapi_app_main() and waits for it to be done. This is where the + // component should have been created. + if(ctx.process.pacify) { + replynl(ctx, ""); + ctx.process.pacify = false; + } + if(-ENOENT != rv) { + errornl(ctx, fmt::format("searching for component '{}' failed with error={} ({})", + ctx.process.name_comp, rv, hal_strerror(rv))); + } + if(ctx.process.retval < 0) { + errornl(ctx, fmt::format("program '{}' terminated on signal {} with no component", + ctx.process.name_proc, -ctx.process.retval)); + } else { + errornl(ctx, fmt::format("program '{}' terminated with code {} with no component", + ctx.process.name_proc, ctx.process.retval)); + } + ctx.process.state = PS_IDLE; + return 0; + } + + // Process still alive and component not found or not ready yet. + waitPacify(ctx); + return 1; // Not done yet + +} + +static int processDoLoadUsr(connectionRecType &ctx) +{ + int rv; + + switch(ctx.process.state) { + default: + errornl(ctx, fmt::format("internal: processDoLoadUsr: Invalid state {}", (int)ctx.process.state)); + ctx.process.state = PS_IDLE; + return 0; + + case PS_IDLE: + return 0; + + case PS_WAITCOMP: + rv = waitComponent(ctx); + if(rv <= 0) { + // Wait ended + if(ctx.process.pacify) { + // terminate the ... pacifier + replynl(ctx, ""); + } + ctx.process.state = ctx.process.wait_proc ? PS_WAITPROC : PS_IDLE; + } else { + flushall(); + } + return rv > 0; + + case PS_WAITPROC: + // Waiting for the process to exit + if(ctx.process.exited) { + // The SIGCHLD handler caught the exit + if(ctx.process.retval < 0) { + // The process died on a signal + errornl(ctx, fmt::format("doLoadUsr: program '{}' terminated on signal {}", + ctx.process.name_proc, -ctx.process.retval)); + } else if(!ctx.process.ignore_rv) { + if (0 != ctx.process.retval) { + errornl(ctx, fmt::format("doLoadUsr: program '{}' failed, returned {}", + ctx.process.name_proc, ctx.process.retval)); + } + } + // Done waiting + ctx.process.state = PS_IDLE; + return 0; + } + return 1; // Process still going + } +} + +static int processDoLoadRtInsmod(connectionRecType &ctx) +{ + // Module argument string needs to go into hal memory + char *cptr = (char *)hal_malloc(ctx.process.insmod.size() + 1); + if (!cptr) { + errornl(ctx, "LoadRt: Failed to allocate HAL memory"); + ctx.process.retval = -ENOMEM; + } else { + strcpy(cptr, ctx.process.insmod.c_str()); + int rv = hal_comp_insmod_args(ctx.process.name_comp.c_str(), cptr); + if(rv) { + errornl(ctx, fmt::format("LoadRt: insmod args for module '{}' failed, error={} ({})", + ctx.process.name_comp, rv, hal_strerror(rv))); + ctx.process.retval = -EINVAL; + } else { + ctx.process.retval = 0; + } + } + return 0; +} + +#if defined(RTAPI_USPACE) +static int processDoLoadRtWait(connectionRecType &ctx) +{ + // First wait for the process to finish. Then cascade into the insmod + // assignment for HAL. + if(!processDoLoadUsr(ctx)) { + ctx.process.cascade = &processDoLoadRtInsmod; + } + return 1; +} +#endif + +static int doLoadRt(connectionRecType &ctx, const std::vector &args) +{ + int retval; + std::vector argv; + + if (hal_get_lock() & HAL_LOCK_LOAD) { + errornl(ctx, "LoadRt: HAL is locked, loading of modules is not permitted"); + return -EPERM; + } + + std::string mod_name = args[0]; + +#if defined(RTAPI_USPACE) + argv.push_back("loadrt"); // For getopt(), will be ignored + argv.push_back("-W"); + argv.push_back("-n"); + argv.push_back(mod_name.c_str()); + argv.push_back(EMC2_BIN_DIR "/rtapi_app"); + argv.push_back("load"); + for(unsigned i = 0; i < args.size(); i++) { + argv.push_back(args[i].c_str()); + } + argv.push_back(NULL); + retval = doLoadUsr(ctx, argv); + + if (0 != retval) { + errornl(ctx, fmt::format("LoadRt: rtapi_app load '{}' failed, returned {}", mod_name, retval)); + return retval; + } + ctx.process.cascade = &processDoLoadRtWait; + + // Join the module arguments into a single string + ctx.process.insmod = mod_name; + for(unsigned i = 1; i < args.size(); i++) { + ctx.process.insmod += ' '; + ctx.process.insmod += args[i]; + } + + return 0; +#else + // Make full module name '/.o' + std::string mod_path = fmt::format("{}/{}{}", EMC2_RTLIB_DIR, mod_name, MODULE_EXT); + + // Does the module exist? + struct stat stat_buf; + if (stat(mod_path.c_str(), &stat_buf) != 0 ) { + errornl(ctx, fmt::format("LoadRt: Can't find module '{}' in '{}'", mod_name, EMC2_RTLIB_DIR)); + return -ENOENT; + } + + argv.push_back(EMC2_BIN_DIR "/linuxcnc_module_helper"); + argv.push_back("insert"); + for(unsigned i = 0; i < args.size(); i++) { + argv.push_back(args[i].c_str()); + } + argv.push_back(NULL); + + retval = halrmt_systemv(ctx, argv); + + if (0 != retval) { + errornl(ctx, fmt::format("LoadRt: insmod '{}' failed, returned {}", mod_name, retval)); + return retval; + } + + ctx.process.name_comp = mod_name; + + // Join the module arguments into a single string + ctx.process.insmod = mod_name; + for(unsigned i = 1; i < args.size(); i++) { + ctx.process.insmod += ' '; + ctx.process.insmod += args[i]; + } + + processDoLoadRtInsmod(ctx); + return 0; +#endif +} + +static int getCompInfo_cb(hal_query_t *q, void *arg) +{ + connectionRecType *ctx = reinterpret_cast(arg); + const std::string *pattern = reinterpret_cast(q->callerdata.cpval); + if(pattern->empty() || !fnmatch(pattern->c_str(), q->name, FNM_NOESCAPE|FNM_CASEFOLD)) { + replynl(*ctx, fmt::format("COMP {:12s} {:3d} {}", + q->name, q->comp.comp_id, q->comp.type == HAL_COMP_TYPE_REALTIME ? "RT " : "User")); + } + return 0; +} + +static int getCompInfo(connectionRecType &ctx, const std::string &pattern) +{ + hal_query_t q = {}; + q.callerdata.cpval = &pattern; + if(ctx.header) { + replynl(ctx, fmt::format("COMP Name Id Type")); + } + return hal_list_comp(&q, getCompInfo_cb, &ctx); +} + +typedef struct { + connectionRecType *ctx; + const std::string *pattern; + int count; + bool valuesOnly; +} getXinfo_t; + +static int getPinInfo_cb(hal_query_t *q, void *arg) +{ + getXinfo_t *gpi = reinterpret_cast(arg); + if(gpi->pattern->empty() || !fnmatch(gpi->pattern->c_str(), q->name, FNM_NOESCAPE|FNM_CASEFOLD)) { + gpi->count++; + if(gpi->valuesOnly) { + replynl(*gpi->ctx, fmt::format("PINVAL {:30s} {}", + q->name, data_value2(q->pp.type, q->pp.value))); + } else { + replynl(*gpi->ctx, fmt::format("PIN {:30s} {:11s} {:12s}({:3d}) {} {}", + q->name, data_value2(q->pp.type, q->pp.value), + q->pp.comp, q->pp.comp_id, data_type(q->pp.type), pin_data_dir(q->pp.dir))); + } + } + return 0; +} + +static std::pair getPinInfo(connectionRecType &ctx, const std::string &pattern, bool valuesOnly) +{ + getXinfo_t gpi = { &ctx, &pattern, 0, valuesOnly }; + hal_query_t q = {}; + q.qtype = HAL_QTYPE_PIN; + if(ctx.header) { + if(valuesOnly) + replynl(ctx, fmt::format("PINVAL Name Value")); + else + replynl(ctx, fmt::format("PIN Name Value Component ( id) Type Dir")); + } + return {hal_list_p(&q, getPinInfo_cb, &gpi), gpi.count}; +} + +static int getSigInfo_cb(hal_query_t *q, void *arg) +{ + getXinfo_t *gpi = reinterpret_cast(arg); + if(gpi->pattern->empty() || !fnmatch(gpi->pattern->c_str(), q->name, FNM_NOESCAPE|FNM_CASEFOLD)) { + gpi->count++; + if(gpi->valuesOnly) { + replynl(*gpi->ctx, fmt::format("SIGNALVAL {:30s} {}", + q->name, data_value2(q->sig.type, q->sig.value))); + } else { + replynl(*gpi->ctx, fmt::format("SIGNAL {:30s} {:11s} {}", + q->name, data_value2(q->sig.type, q->sig.value), data_type(q->sig.type))); + } + } + return 0; +} + +static std::pair getSigInfo(connectionRecType &ctx, const std::string &pattern, bool valuesOnly) +{ + getXinfo_t gpi = { &ctx, &pattern, 0, valuesOnly }; + hal_query_t q = {}; + if(ctx.header) { + if(valuesOnly) + replynl(ctx, fmt::format("SIGNALVAL Name Value")); + else + replynl(ctx, fmt::format("SIGNAL Name Value Type")); + } + return {hal_list_s(&q, getSigInfo_cb, &gpi), gpi.count}; +} + +static int getParamInfo_cb(hal_query_t *q, void *arg) +{ + getXinfo_t *gpi = reinterpret_cast(arg); + if(gpi->pattern->empty() || !fnmatch(gpi->pattern->c_str(), q->name, FNM_NOESCAPE|FNM_CASEFOLD)) { + gpi->count++; + if(gpi->valuesOnly) { + replynl(*gpi->ctx, fmt::format("PARAMVAL {:30s} {}", + q->name, data_value2(q->pp.type, q->pp.value))); + } else { + replynl(*gpi->ctx, fmt::format("PARAM {:30s} {:11s} {:12s}({:3d}) {} {}", + q->name, data_value2(q->pp.type, q->pp.value), + q->pp.comp, q->pp.comp_id, data_type(q->pp.type), param_data_dir(q->pp.dir))); + } + } + return 0; +} + +static std::pair getParamInfo(connectionRecType &ctx, const std::string &pattern, bool valuesOnly) +{ + getXinfo_t gpi = { &ctx, &pattern, 0, valuesOnly }; + hal_query_t q = {}; + q.qtype = HAL_QTYPE_PARAM; + if(ctx.header) { + if(valuesOnly) + replynl(ctx, fmt::format("PARAMVAL Name Value")); + else + replynl(ctx, fmt::format("PARAM Name Value Component ( id) Type Dir")); + } + return {hal_list_p(&q, getParamInfo_cb, &gpi), gpi.count}; +} + +static int getFunctInfo_cb(hal_query_t *q, void *arg) +{ + connectionRecType *ctx = reinterpret_cast(arg); + const std::string *pattern = reinterpret_cast(q->callerdata.cpval); + if(pattern->empty() || !fnmatch(pattern->c_str(), q->name, FNM_NOESCAPE|FNM_CASEFOLD)) { + replynl(*ctx, fmt::format("FUNCT {:16s} {:12s}({:3d}) {:3d}", + q->name, q->funct.comp, q->funct.comp_id, q->funct.users)); + } + return 0; +} + +static int getFunctInfo(connectionRecType &ctx, const std::string &pattern) +{ + hal_query_t q = {}; + q.callerdata.cpval = reinterpret_cast(&pattern); + if(ctx.header) { + replynl(ctx, "FUNCT Name Component ( id) Users"); + } + return hal_list_funct(&q, getFunctInfo_cb, &ctx); +} + +static rtapi_sint getpin_sint(connectionRecType &ctx, const std::string &name) +{ + hal_query_t q = {}; + q.name = name.c_str(); + int rv = hal_get_p(&q, NULL, NULL); + if(0 != rv) { + errornl(ctx, fmt::format("Cannot find thread's pin '{}', error={}", name, rv)); + return 0; + } + return q.pp.value.s; +} +static int getThreadInfo_cb(hal_query_t *q, void *arg) +{ + connectionRecType *ctx = reinterpret_cast(arg); + const std::string *pattern = reinterpret_cast(q->callerdata.cpval); + if(HAL_QTYPE_THREAD == q->qtype) { + // The thread reference + if(pattern->empty() || !fnmatch(pattern->c_str(), q->name, FNM_NOESCAPE|FNM_CASEFOLD)) { + rtapi_sint tp = getpin_sint(*ctx, fmt::format("{}.time", q->name)); + rtapi_sint tm = getpin_sint(*ctx, fmt::format("{}.tmax", q->name)); + replynl(*ctx, fmt::format("THREAD {:12s} {:11d} {} {}", q->name, q->thread.period, tp, tm)); + } + } else { + // The thread's function reference + replynl(*ctx, fmt::format("THREADFUNCT {:16s} {:2d}", q->thread.funct, q->thread.functidx + 1)); + } + return 0; +} + +static int getThreadInfo(connectionRecType &ctx, const std::string &pattern) +{ + hal_query_t q = {}; + q.qtype = HAL_QTYPE_THREAD_FUNCT; + q.callerdata.cpval = reinterpret_cast(&pattern); + if(ctx.header) { + replynl(ctx, "THREAD Name Period time tmax"); + } + return hal_list_thread(&q, getThreadInfo_cb, &ctx); +} + +static int save_comps_cb(hal_query_t *q, void *arg) +{ + std::string *str = reinterpret_cast(arg); + if(strstr(q->name, HAL_PSEUDO_COMP_PREFIX) == q->name) { + // Those starting with '__' are fake components + return 0; + } + if(HAL_COMP_TYPE_REALTIME == q->comp.type) { + // Realtime components only + if(!q->comp.insmod) { + *str += fmt::format("# loadrt {} (not loaded by loadrt, no args saved)\n", q->name); + } else { + *str += fmt::format("loadrt {} {}\n", q->name, q->comp.insmod); + } + } + return 0; +} + +static void save_comps(std::string &dst) +{ + dst += "# components\n"; + hal_query_t q = {}; + hal_list_comp(&q, save_comps_cb, &dst); +} + +static int save_signals_cb(hal_query_t *q, void *arg) +{ + std::string *str = reinterpret_cast(arg); + *str += fmt::format("newsig {} {}\n", q->name, data_type(q->sig.type)); + return 0; +} + +static void save_signals(std::string &dst) +{ + dst += "# signals\n"; + hal_query_t q = {}; + hal_list_s(&q, save_signals_cb, &dst); +} + +static int save_links_cb(hal_query_t *q, void *arg) +{ + if(q->pp.signal) { + std::string *str = reinterpret_cast(arg); + const char *arrow_str = q->callerdata.sival ? data_arrow1(q->pp.dir) : ""; + *str += fmt::format("linkps {} {} {}\n", q->name, arrow_str, q->pp.signal); + } + return 0; +} + +static void save_links(std::string &dst, int arrow) +{ + dst += "# links\n"; + hal_query_t q = {}; + q.qtype = HAL_QTYPE_PIN; + q.callerdata.sival = arrow; + hal_list_p(&q, save_links_cb, &dst); +} + +static int save_nets_pins_cb(hal_query_t *q, void *arg) +{ + std::string *str = reinterpret_cast(arg); + const char *arrow_str = q->callerdata.sival ? data_arrow2(q->pp.dir) : ""; + *str += fmt::format("linksp {} {} {}\n", q->pp.signal, arrow_str, q->name); + return 0; +} + +static int save_nets_cb(hal_query_t *q, void *arg) +{ + std::string *str = reinterpret_cast(arg); + *str += fmt::format("newsig {} {}\n", q->name, data_type(q->sig.type)); + hal_query_t qp = {}; + qp.name = q->name; + qp.callerdata.sival = q->callerdata.sival; + return hal_list_p_s(&qp, save_nets_pins_cb, arg); +} +static void save_nets(std::string &dst, int arrow) +{ + dst += "# nets\n"; + hal_query_t q = {}; + q.callerdata.sival = arrow; + hal_list_s(&q, save_nets_cb, &dst); +} + +static int save_params_cb(hal_query_t *q, void *arg) +{ + std::string *str = reinterpret_cast(arg); + *str += fmt::format("setp {} {}\n", q->name, data_value(q->pp.type, q->pp.value).c_str()); + return 0; +} + +static void save_params(std::string &dst) +{ + dst += "# parameter values\n"; + hal_query_t q = {}; + q.qtype = HAL_QTYPE_PARAM; + hal_list_p(&q, save_params_cb, &dst); +} + +static int save_threads_cb(hal_query_t *q, void *arg) +{ + if(HAL_QTYPE_THREAD_FUNCT == q->qtype) { + std::string *str = reinterpret_cast(arg); + *str += fmt::format("addf {} {}\n", q->thread.funct, q->name); + } + return 0; +} + +static void save_threads(std::string &dst) +{ + dst += "# realtime thread/function links\n"; + hal_query_t q = {}; + q.qtype = HAL_QTYPE_THREAD_FUNCT; + hal_list_thread(&q, save_threads_cb, &dst); +} + +// initiate session +static int commandHello(connectionRecType &ctx) +{ + // HELLO [] + + if (ctx.toks[2].size() > MAX_HOSTNAME_SIZE) { + errornl(ctx, fmt::format("Hostname too long. Max length allowed is {}", MAX_HOSTNAME_SIZE)); + replynl(ctx, "HELLO NAK"); + return -1; + } + + // Repeating a HELLO means we disable and unlink. + // Linking needs to be reestablished + doDisable(ctx); + doUnlink(ctx); + + // On password errors, don't say too much. + if (ctx.toks[1] != helloPwd) { + replynl(ctx, "HELLO NAK"); + return -1; + } + + std::string version = "1.1"; + if (auto inistring = getIniVar(ctx, "VERSION", "EMC")) + version = *inistring; + + ctx.hostname = ctx.toks[2]; + + // ctx.version defaults to "1.1" when the context is created + if (ctx.toks.size() > 3) + ctx.version = ctx.toks[3]; + + if (ctx.version > version) { + warnnl(ctx, fmt::format("Requested version '{}' is newer than '{}' from [EMC]VERSION", ctx.version, version)); + } + + doLink(ctx); + replynl(ctx, fmt::format("HELLO ACK {} {}", serverName, version)); + return 0; +} + +int commandQuit(connectionRecType &ctx) +{ + doDisable(ctx); + ctx.halfclosed = true; + ctx.inbuf.clear(); // Zap any remaining input + info("Closing connection '%s' (%d)", ctx.hostname.c_str(), ctx.sock); + replynl(ctx, "QUIT ACK"); + return 0; +} + +int commandShutdown(connectionRecType &ctx) +{ + if (isEnabled(ctx)) { + // A shutdown will disconnect all clients from halrmt + info("Shutdown initiated by '%s' (%d)", ctx.hostname.c_str(), ctx.sock); + doDisable(ctx); + // Shutdown is not actually performed... + // It cannot work because there is no infrastructure in place to do a + // remote shutdown. + replynl(ctx, "SHUTDOWN ACK"); + return 0; + } + + errornl(ctx, "SHUTDOWN failed, not enabled"); + replynl(ctx, "SHUTDOWN NAK"); + return -1; +} + +static cmdResponseType getEcho(connectionRecType &ctx) +{ + // GET ECHO + replynl(ctx, onOffString("ECHO", ctx.echo)); + return rtOk; +} + +static cmdResponseType getVerbose(connectionRecType &ctx) +{ + // GET VERBOSE + replynl(ctx, onOffString("VERBOSE", ctx.verbose)); + return rtOk; +} + +static cmdResponseType getHeader(connectionRecType &ctx) +{ + // GET HEADER + replynl(ctx, onOffString("HEADER", ctx.header)); + return rtOk; +} + +static cmdResponseType getEnable(connectionRecType &ctx) +{ + // GET ENABLE + replynl(ctx, onOffString("ENABLE", isEnabled(ctx))); + return rtOk; +} + +static cmdResponseType getExpand(connectionRecType &ctx) +{ + // GET EXPAND + replynl(ctx, onOffString("EXPAND", ctx.expand)); + return rtOk; +} + +static cmdResponseType getComps(connectionRecType &ctx) +{ + // GET COMPS [prefix] + int rv; + if(ctx.toks.size() > 2) + rv = getCompInfo(ctx, ctx.toks[2].c_str()); + else + rv = getCompInfo(ctx, ""); + return 0 == rv ? rtOk : rtError; +} + +static cmdResponseType getPins(connectionRecType &ctx) +{ + // GET PINS [prefix] + std::pair rv; + if(ctx.toks.size() > 2) { + rv = getPinInfo(ctx, ctx.toks[2], false); + } else { + rv = getPinInfo(ctx, "", false); + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getPinVals(connectionRecType &ctx) +{ + // GET PINVALS [prefix] + std::pair rv; + if(ctx.toks.size() > 2) { + rv = getPinInfo(ctx, ctx.toks[2], true); + } else { + rv = getPinInfo(ctx, "", true); + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getSigs(connectionRecType &ctx) +{ + // GET SIGS [prefix] + std::pair rv; + if(ctx.toks.size() > 2) { + rv = getSigInfo(ctx, ctx.toks[2], false); + } else { + rv = getSigInfo(ctx, "", false); + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getSigVals(connectionRecType &ctx) +{ + // GET SIGVALS [prefix] + std::pair rv; + if(ctx.toks.size() > 2) { + rv = getSigInfo(ctx, ctx.toks[2], true); + } else { + rv = getSigInfo(ctx, "", true); + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getParams(connectionRecType &ctx) +{ + // GET PARAMS [prefix] + std::pair rv; + if(ctx.toks.size() > 2) { + rv = getParamInfo(ctx, ctx.toks[2], false); + } else { + rv = getParamInfo(ctx, "", false); + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getParamVals(connectionRecType &ctx) +{ + // GET PARAMVALS [prefix] + std::pair rv; + if(ctx.toks.size() > 2) { + rv = getParamInfo(ctx, ctx.toks[2], true); + } else { + rv = getParamInfo(ctx, "", true); + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getFuncts(connectionRecType &ctx) +{ + // GET FUNCTS [prefix] + int rv; + if(ctx.toks.size() > 2) { + rv = getFunctInfo(ctx, ctx.toks[2]); + } else { + rv = getFunctInfo(ctx, ""); + } + return 0 == rv ? rtOk : rtError; +} + +static cmdResponseType getIni(connectionRecType &ctx) +{ + // GET INI
+ auto inistring = getIniVar(ctx, ctx.toks[2], ctx.toks[3]); + if (inistring) { + replynl(ctx, fmt::format("INI [{}]{}={}", ctx.toks[3], ctx.toks[2], *inistring)); + return rtOk; + } else { + errornl(ctx, fmt::format("INI file entry '[{}]{}' not found", ctx.toks[3], ctx.toks[2])); + return rtError; + } +} + +static cmdResponseType getIniFile(connectionRecType &ctx) +{ + // GET INIFILE + replynl(ctx, fmt::format("INIFILE {}", ctx.inifilename)); + return rtOk; +} + +static cmdResponseType getThreads(connectionRecType &ctx) +{ + // GET THREADS [prefix] + int rv; + if(ctx.toks.size() > 2) { + rv = getThreadInfo(ctx, ctx.toks[2]); + } else { + rv = getThreadInfo(ctx, ""); + } + return 0 == rv ? rtOk : rtError; +} + +static cmdResponseType getComp(connectionRecType &ctx) +{ + // GET COMP + return 0 == getCompInfo(ctx, ctx.toks[2]) ? rtOk : rtError; +} + +static cmdResponseType getPin(connectionRecType &ctx) +{ + // GET PIN + std::pair rv = getPinInfo(ctx, ctx.toks[2], false); + if(0 == rv.first && 0 == rv.second) { + errornl(ctx, fmt::format("Pin '{}' not found", ctx.toks[2])); + return rtError; + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getPinVal(connectionRecType &ctx) +{ + // GET PINVAL + std::pair rv = getPinInfo(ctx, ctx.toks[2], true); + if(0 == rv.first && 0 == rv.second) { + errornl(ctx, fmt::format("Pin '{}' not found", ctx.toks[2])); + return rtError; + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getSig(connectionRecType &ctx) +{ + // GET SIGNAL + std::pair rv = getSigInfo(ctx, ctx.toks[2], false); + if(0 == rv.first && 0 == rv.second) { + errornl(ctx, fmt::format("Signal '{}' not found", ctx.toks[2])); + return rtError; + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getSigVal(connectionRecType &ctx) +{ + // GET SIGNALVAL + std::pair rv = getSigInfo(ctx, ctx.toks[2], true); + if(0 == rv.first && 0 == rv.second) { + errornl(ctx, fmt::format("Signal '{}' not found", ctx.toks[2])); + return rtError; + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getParam(connectionRecType &ctx) +{ + // GET PARAM + std::pair rv = getParamInfo(ctx, ctx.toks[2], false); + if(0 == rv.first && 0 == rv.second) { + errornl(ctx, fmt::format("Parameter '{}' not found", ctx.toks[2])); + return rtError; + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getParamVal(connectionRecType &ctx) +{ + // GET PARAMVAL + std::pair rv = getParamInfo(ctx, ctx.toks[2], true); + if(0 == rv.first && 0 == rv.second) { + errornl(ctx, fmt::format("Parameter '{}' not found", ctx.toks[2])); + return rtError; + } + return 0 == rv.first ? rtOk : rtError; +} + +static cmdResponseType getFunct(connectionRecType &ctx) +{ + // GET FUNCT + return 0 == getFunctInfo(ctx, ctx.toks[2]) ? rtOk : rtError; +} + +static cmdResponseType getThread(connectionRecType &ctx) +{ + // GET THREAD + return 0 == getThreadInfo(ctx, ctx.toks[2]) ? rtOk : rtError; +} + +static cmdResponseType getLock(connectionRecType &ctx) +{ + // GET LOCK + unsigned char lck = hal_get_lock(); + if(HAL_LOCK_NONE == lck) { + replynl(ctx, "LOCK none"); + } else if(HAL_LOCK_ALL == lck) { + replynl(ctx, "LOCK all"); + } else { +#define LOCK_ANY (HAL_LOCK_LOAD | HAL_LOCK_CONFIG | HAL_LOCK_PARAMS | HAL_LOCK_RUN) + std::string l; + if(lck & HAL_LOCK_LOAD) l += " load"; + if(lck & HAL_LOCK_CONFIG) l += " config"; + if(lck & HAL_LOCK_PARAMS) l += " params"; + if(lck & HAL_LOCK_RUN) l += " run"; + if(0 != (lck & ~LOCK_ANY)) { + l += fmt::format(" (unknown values: lock=0x{:02x})", lck); + } + replynl(ctx, fmt::format("LOCK{}", l)); +#undef LOCK_ANY + } + return rtOk; +} + +static int getNet_cb(hal_query_t *q, void *arg) +{ + connectionRecType *ctx = reinterpret_cast(arg); + replynl(*ctx, fmt::format("{} {} {}", q->pp.signal, data_arrow1(q->pp.dir), q->name)); + return 0; +} + +static cmdResponseType getNet(connectionRecType &ctx) +{ + // GET NET + hal_query_t q = {}; + q.name = ctx.toks[2].c_str(); + int rv = hal_list_p_s(&q, getNet_cb, &ctx); + if(0 == rv) { + return rtOk; + } else if(-ENOENT == rv) { + errornl(ctx, fmt::format("Signal '{}' not found", ctx.toks[2])); + return rtError; + } else { + errornl(ctx, fmt::format("Signal '{}' resulted in error={} ({})", ctx.toks[2], rv, hal_strerror(rv))); + return rtError; + } +} + +static cmdResponseType getTimestamp(connectionRecType &ctx) +{ + // GET TIMESTAMP + replynl(ctx, fmt::format("TIMESTAMP {} {}", onOff(ctx.timestamp), onOff(ctx.timefmt))); + return rtOk; +} + +static cmdResponseType getTime(connectionRecType &ctx) +{ + // GET TIME + replynl(ctx, fmt::format("TIME {}", mytimeofday())); + return rtOk; +} + +static cmdResponseType setEcho(connectionRecType &ctx) +{ + // SET ECHO + switch (checkOnOff(ctx.toks[2])) { + default: errornl(ctx, fmt::format("Invalid argument '{}', must be {{on,off}}", ctx.toks[2])); return rtError; + case kwOn: ctx.echo = true; break; + case kwOff: ctx.echo = false; break; + } + return rtOk; +} + +static cmdResponseType setVerbose(connectionRecType &ctx) +{ + // SET VERBOSE + switch (checkOnOff(ctx.toks[2])) { + default: errornl(ctx, fmt::format("Invalid argument '{}', must be {{on,off}}", ctx.toks[2])); return rtError; + case kwOn: ctx.verbose = true; break; + case kwOff: ctx.verbose = false; break; + } + return rtOk; +} + +static cmdResponseType setHeader(connectionRecType &ctx) +{ + // SET HEADER + switch (checkOnOff(ctx.toks[2])) { + default: errornl(ctx, fmt::format("Invalid argument '{}', must be {{on,off}}", ctx.toks[2])); return rtError; + case kwOn: ctx.header = true; break; + case kwOff: ctx.header = false; break; + } + return rtOk; +} + +static cmdResponseType setEnable(connectionRecType &ctx) +{ + // SET ENABLE + if (ctx.toks[2] == enablePwd) { + doEnable(ctx); + return rtOk; + } + + if (!compareNoCase(ctx.toks[2], "OFF")) { + if (!isEnabled(ctx)) { + errornl(ctx, "Trying to set enable to off on a non-enabled link"); + return rtError; + } + doDisable(ctx); + return rtOk; + } + return rtError; +} + +static cmdResponseType setExpand(connectionRecType &ctx) +{ + // SET EXPAND + switch (checkOnOff(ctx.toks[2])) { + default: errornl(ctx, fmt::format("Invalid argument '{}', must be {{on,off}}", ctx.toks[2])); return rtError; + case kwOn: ctx.expand = true; break; + case kwOff: ctx.expand = false; break; + } + return rtOk; +} + +static cmdResponseType setLoadRt(connectionRecType &ctx) +{ + // SET LOADRT [args...] + if(REALTIME_TYPE_UNINITIALIZED == hal_get_realtime_type()) { + errornl(ctx, "Realtime has not been initialized."); + return rtError; + } + + std::vector args; + + for(unsigned i = 2; i < ctx.toks.size(); i++) { + args.push_back(ctx.toks[i]); + } + int rv = doLoadRt(ctx, args); + +#if defined(RTAPI_USPACE) + if(0 == rv) { + ctx.process.acknak = "LOADRT"; + return rtDelayed; + } + return rtError; +#else + return 0 == rv ? rtOk : rtError; +#endif +} + +static cmdResponseType setUnload(connectionRecType &ctx) +{ + // SET UNLOAD |all + if(REALTIME_TYPE_UNINITIALIZED == hal_get_realtime_type()) { + errornl(ctx, "Realtime has not been initialized."); + return rtError; + } + + std::vector comps; + + if("all" == ctx.toks[2]) { + // Unload all components + // Gather all RT components + hal_query_t q = {}; + int rv = hal_list_comp(&q, unloadrt_cb, &comps); + if(0 != rv) { + errornl(ctx, fmt::format("Failed to gather component list, error={} ({})", + ctx.toks[2], rv, hal_strerror(rv))); + return rtError; + } + } else { + // Unload one specific component + hal_query_t q = {}; + int rv = hal_comp_by_name(ctx.toks[2].c_str(), &q); + if(0 != rv) { + errornl(ctx, fmt::format("Component '{}' is not loaded, error={} ({})", + ctx.toks[2], rv, hal_strerror(rv))); + return rtError; + } + if(HAL_COMP_TYPE_REALTIME != q.comp.type) { + errornl(ctx, fmt::format("Component '{}' is not a real-time component", ctx.toks[2])); + return rtError; + } + comps.push_back(ctx.toks[2]); + } + + // Now unload the lot + for(const auto &comp : comps) { + int rv = unloadrt_comp(ctx, comp); + if(0 != rv) { + errornl(ctx, fmt::format("Component '{}' failed to unload, error={} ({})", + ctx.toks[2], rv, hal_strerror(rv))); + return rtError; + } + } + return rtOk; +} + +static cmdResponseType setLoadUsr(connectionRecType &ctx) +{ + // SET LOADUSR [arg [arg...]] + std::vector argv; + argv.push_back("loadusr"); // Will be ignored + for(unsigned i = 2; i < ctx.toks.size(); i++) { + argv.push_back(ctx.toks[i].c_str()); + } + argv.push_back(NULL); + int rv = doLoadUsr(ctx, argv); + if(0 == rv) { + ctx.process.cascade = &processDoLoadUsr; + ctx.process.acknak = "LOADUSR"; + return rtDelayed; + } + return rtError; +} + +static cmdResponseType linkPS(connectionRecType &ctx, const std::string &pin, const std::string &sig) +{ + if(sig.empty()) { + int rv = hal_unlink(pin.c_str()); + if(0 != rv) { + errornl(ctx, fmt::format("Failed to unlink '{}'", pin)); + return rtError; + } + } else { + int rv = hal_link(pin.c_str(), sig.c_str()); + if(0 != rv) { + errornl(ctx, fmt::format("Failed to link pin '{}' to signal '{}'", pin, sig)); + return rtError; + } + } + return rtOk; +} + +static cmdResponseType setLinkPS(connectionRecType &ctx) +{ + // SET LINKPS [] + if(ctx.toks.size() > 3) + return linkPS(ctx, ctx.toks[2], ctx.toks[3]); + else + return linkPS(ctx, ctx.toks[2], ""); +} + +static cmdResponseType setLinkSP(connectionRecType &ctx) +{ + // SET LINKSP + return linkPS(ctx, ctx.toks[3], ctx.toks[2]); +} + +static cmdResponseType setNet(connectionRecType &ctx) +{ + // SET NET [pin [..]] + + hal_query_t qs = {}; + qs.name = ctx.toks[2].c_str(); + int havesig = hal_getref_s(&qs); + if(0 != havesig && -ENOENT != havesig) { + errornl(ctx, fmt::format("Failed to retrieve signal info for '{}' error={} ({})", + ctx.toks[2], havesig, hal_strerror(havesig))); + return rtError; + } + + // The type of the signal (to be) + hal_type_t ts = 0 == havesig ? qs.sig.type : HAL_TYPE_UNINITIALIZED; + + for(unsigned i = 3; i < ctx.toks.size(); i++) { + hal_query_t qp = {}; + qp.qtype = HAL_QTYPE_PIN; + qp.name = ctx.toks[i].c_str(); + int rv = hal_getref_p(&qp); + if(0 != rv) { + errornl(ctx, fmt::format("Failed to retrieve pin info for '{}', error={} ({})", + ctx.toks[i], rv, hal_strerror(rv))); + return rtError; + } + if(HAL_TYPE_UNINITIALIZED == ts) { + ts = qp.pp.type; + } else if(ts != qp.pp.type) { + errornl(ctx, fmt::format("Type mismatch {} != {} ({}!={})", + data_type(ts), data_type(qp.pp.type), (int)ts, (int)qp.pp.type)); + return rtError; + } + if(HAL_OUT == qp.pp.dir && (qs.sig.writers > 0 || qs.sig.bidirs > 0)) { + if(qs.sig.writers > 0) + errornl(ctx, fmt::format("Cannot add OUT pin '{}', signal '{}' already has a writer", qp.name, qs.name)); + else + errornl(ctx, fmt::format("Cannot add OUT pin '{}', signal '{}' already has bidirs", qp.name, qs.name)); + return rtError; + } else if(HAL_IO == qp.pp.dir) { + qs.sig.bidirs++; + if(qs.sig.writers > 0) { + errornl(ctx, fmt::format("Cannot add IO pin '{}', signal '{}' already has a writer", qp.name, qs.name)); + return rtError; + } + } + } + + // Create the signal if it does not yet exist + if(-ENOENT == havesig) { + hal_query_t qp = {}; + qp.name = ctx.toks[2].c_str(); + int rv = hal_getref_p(&qp); + if(0 == rv) { + errornl(ctx, fmt::format("Signal name '{}' is already in use as a {} name", + ctx.toks[2], HAL_QTYPE_PIN == qp.qtype ? "pin" : "param")); + return rtError; + } + if(0 != (rv = hal_signal_new(ctx.toks[2].c_str(), ts))) { + errornl(ctx, fmt::format("Failed to create signal '{}', error={}", ctx.toks[2], rv)); + return rtError; + } + } + + // Link up all the pins + for(unsigned i = 3; i < ctx.toks.size(); i++) { + int rv = hal_link(ctx.toks[i].c_str(), ctx.toks[2].c_str()); + if(0 != rv) { + errornl(ctx, fmt::format("Failed to add pin '{}' to signal '{}', error={}", ctx.toks[i], ctx.toks[2], rv)); + return rtError; + } + } + return rtOk; +} + +static cmdResponseType setUnlinkP(connectionRecType &ctx) +{ + // SET UNLINK + return linkPS(ctx, ctx.toks[2], ""); +} + +static cmdResponseType setLock(connectionRecType &ctx) +{ + // SET LOCK {all|load|config|params|run|tune|none} + int lck; + if((lck = checkLock(ctx.toks[2].c_str())) < 0) { + errornl(ctx, fmt::format("Invalid lock argument '{}'", ctx.toks[2])); + return rtError; + } + unsigned char old = hal_get_lock(); + int rv; + switch(lck) { + case kwAll: rv = hal_set_lock(HAL_LOCK_ALL); break; + case kwLoad: rv = hal_set_lock(old | HAL_LOCK_LOAD); break; + case kwConfig: rv = hal_set_lock(old | HAL_LOCK_CONFIG); break; + case kwParams: rv = hal_set_lock(old | HAL_LOCK_PARAMS); break; + case kwRun: rv = hal_set_lock(old | HAL_LOCK_RUN); break; + case kwTune: rv = hal_set_lock(HAL_LOCK_TUNE); break; + case kwNone: rv = hal_set_lock(HAL_LOCK_NONE); break; + default: + errornl(ctx, fmt::format("internal: Invalid lock key '{}'", lck)); + return rtError; + } + return 0 == rv ? rtOk : rtError; +} + +static cmdResponseType setUnlock(connectionRecType &ctx) +{ + // SET UNLOCK {all|load|config|params|run|tune|none} + int lck; + if((lck = checkLock(ctx.toks[2].c_str())) < 0) { + errornl(ctx, fmt::format("Invalid lock argument '{}'", ctx.toks[2])); + return rtError; + } + unsigned char old = hal_get_lock(); + int rv; + switch(lck) { + case kwAll: rv = hal_set_lock(HAL_LOCK_NONE); break; + case kwLoad: rv = hal_set_lock(old & ~HAL_LOCK_LOAD); break; + case kwConfig: rv = hal_set_lock(old & ~HAL_LOCK_CONFIG); break; + case kwParams: rv = hal_set_lock(old & ~HAL_LOCK_PARAMS); break; + case kwRun: rv = hal_set_lock(old & ~HAL_LOCK_RUN); break; + case kwTune: rv = hal_set_lock(old & ~HAL_LOCK_TUNE); break; + case kwNone: rv = 0; break; + default: + errornl(ctx, fmt::format("internal: Invalid lock key '{}'", lck)); + return rtError; + } + return 0 == rv ? rtOk : rtError; +} + +static hal_type_t haltype_from_str(const std::string &str) +{ + static const struct { + const char *name; + hal_type_t type; + } types[] = { + { "bit", HAL_BOOL }, + { "bool", HAL_BOOL }, + { "float", HAL_REAL }, + { "real", HAL_REAL }, + { "s32", HAL_S32 }, + { "s64", HAL_SINT }, + { "sint", HAL_SINT }, + { "u32", HAL_U32 }, + { "u64", HAL_UINT }, + { "uint", HAL_UINT }, + }; + for(unsigned i = 0; i < NELEM(types); i++) { + if(str == types[i].name) + return types[i].type; + } + return HAL_TYPE_UNSPECIFIED; +} + +static cmdResponseType setNewSig(connectionRecType &ctx) +{ + // SET NEWSIG + hal_type_t type = haltype_from_str(ctx.toks[3]); + if(type <= 0) { + errornl(ctx, fmt::format("Invalid type '{}'", ctx.toks[3])); + return rtError; + } + int rv = hal_signal_new(ctx.toks[2].c_str(), type); + if(0 != rv) { + errornl(ctx, fmt::format("Cannot create signal '{}' with type '{}', error={}", ctx.toks[2], ctx.toks[3], rv)); + return rtError; + } + return rtOk; +} + +static int setDelSig_cb(hal_query_t *q, void *arg) +{ + std::vector *sigs = reinterpret_cast *>(arg); + sigs->push_back(q->name); + return 0; +} + +static cmdResponseType setDelSig(connectionRecType &ctx) +{ + // SET DELSIG + if(ctx.toks[2] == "all") { + std::vector sigs; + hal_query_t q = {}; + int rv = hal_list_s(&q, setDelSig_cb, &sigs); + if(0 != rv) { + errornl(ctx, fmt::format("Failed to gather all signal names, error={}", rv)); + return rtError; + } + bool fail = false; + for(const auto &sig : sigs) { + rv = hal_signal_delete(sig.c_str()); + if(0 != rv) { + errornl(ctx, fmt::format("Cannot delete signal '{}', error={}", sig, rv)); + fail = true; + } + } + if(fail) { + return rtError; + } + } else { + int rv = hal_signal_delete(ctx.toks[2].c_str()); + if(0 != rv) { + errornl(ctx, fmt::format("Cannot delete signal '{}', error={}", ctx.toks[2], rv)); + return rtError; + } + } + return rtOk; +} + +static cmdResponseType setSetP(connectionRecType &ctx) +{ + // SET SETP + hal_query_t query = {}; + query.name = ctx.toks[2].c_str(); + int rv = hal_set_p(&query, setps_common_cb, ctx.toks[3].data()); + if(0 != rv) { + errornl(ctx, fmt::format("Cannot setp '{}' to '{}', error={}", ctx.toks[2], ctx.toks[3], rv)); + return rtError; + } + return rtOk; +} + +static cmdResponseType setSetS(connectionRecType &ctx) +{ + // SET SETS + hal_query_t query = {}; + query.name = ctx.toks[2].c_str(); + int rv = hal_set_s(&query, setps_common_cb, ctx.toks[3].data()); + if(0 != rv) { + errornl(ctx, fmt::format("Cannot sets '{}' to '{}', error={}", ctx.toks[2], ctx.toks[3], rv)); + return rtError; + } + return rtOk; +} + +static cmdResponseType setAddf(connectionRecType &ctx) +{ + // SET ADDF [arg] + int cl = -1; + if (ctx.toks.size() > 4 && !to_int(ctx.toks[4], cl)) { + errornl(ctx, fmt::format("Invalid argument '{}', must be integer", ctx.toks[4])); + return rtError; + } + if(cl < -1) { + errornl(ctx, fmt::format("Invalid argument '{}', must be >= -1", ctx.toks[4])); + return rtError; + } + int rv = hal_add_funct_to_thread(ctx.toks[2].c_str(), ctx.toks[3].c_str(), cl); + if(0 != rv) { + errornl(ctx, fmt::format("Failed to add fucntion '{}' to thread '{}', error={}", ctx.toks[2], ctx.toks[3], rv)); + return rtError; + } + return rtOk; +} + +static cmdResponseType setDelF(connectionRecType &ctx) +{ + // SET DELF + int rv = hal_del_funct_from_thread(ctx.toks[2].c_str(), ctx.toks[3].c_str()); + if(0 != rv) { + errornl(ctx, fmt::format("Failed to remove function '{}' from thread '{}'", ctx.toks[2], ctx.toks[3])); + return rtError; + } + return rtOk; +} + +static cmdResponseType setSave(connectionRecType &ctx) +{ + // SET SAVE {all,comp,sig,link,linka,net,neta,param,thread} [filename] + static std::set keys = {"all","comp","sig","link","linka","net","neta","param","thread"}; + + if(keys.end() == keys.find(ctx.toks[2])) { + errornl(ctx, fmt::format("Invalid save type '{}'", ctx.toks[2])); + return rtError; + } + std::string out; + if("all" == ctx.toks[2]) { + save_comps(out); + save_signals(out); + save_links(out, 0); + save_params(out); + save_threads(out); + } else if("comp" == ctx.toks[2]) { + save_comps(out); + } else if("sig" == ctx.toks[2]) { + save_signals(out); + } else if("link" == ctx.toks[2]) { + save_links(out, 0); + } else if("linka" == ctx.toks[2]) { + save_links(out, 1); + } else if("net" == ctx.toks[2]) { + save_nets(out, 0); + } else if("neta" == ctx.toks[2]) { + save_nets(out, 1); + } else if("param" == ctx.toks[2]) { + save_params(out); + } else if("thread" == ctx.toks[2]) { + save_threads(out); + } else { + errornl(ctx, fmt::format("internal: failed to match key '{}'", ctx.toks[2])); + return rtError; + } + if(ctx.toks.size() > 3) { + // Filename was specified + int fd = creat(ctx.toks[3].c_str(), 0644); + if(!fd) { + errornl(ctx, fmt::format("Cannot open '{}' for write ({})", ctx.toks[2], strerror(errno))); + return rtError; + } + size_t done = 0; + while(done < out.size()) { + ssize_t err = write(fd, out.c_str() + done, out.size() - done); + int e = errno; + if(err < 0) { + if(EINTR == e) + continue; + close(fd); + errornl(ctx, fmt::format("Write to '{}' failed written={} != size={} errno={} ({})", + ctx.toks[2], done, out.size(), e, strerror(e))); + return rtError; + } + done += err; + } + close(fd); + } else { + replynl(ctx, out); + } + return rtOk; +} + +static cmdResponseType setStart(connectionRecType &ctx) +{ + // SET START + if(REALTIME_TYPE_UNINITIALIZED == hal_get_realtime_type()) { + errornl(ctx, "Realtime has not been initialized."); + return rtError; + } + + int rv = hal_start_threads(); + if (rv != 0) { + errornl(ctx, fmt::format("Failed to start realtime threads, error={}", rv)); + } + return 0 == rv ? rtOk : rtError; +} + +static cmdResponseType setStop(connectionRecType &ctx) +{ + // SET STOP + if(REALTIME_TYPE_UNINITIALIZED == hal_get_realtime_type()) { + errornl(ctx, "Realtime has not been initialized."); + return rtError; + } + + int rv = hal_stop_threads(); + if (rv != 0) { + errornl(ctx, fmt::format("Unable to stop realtime threads, error={}", rv)); + } + return 0 == rv ? rtOk : rtError; +} + +static cmdResponseType setTimestamp(connectionRecType &ctx) +{ + // SET TIMESTAMP [ON|OFF] + switch (checkOnOff(ctx.toks[2])) { + default: errornl(ctx, fmt::format("Invalid argument '{}', must be {{on,off}}", ctx.toks[2])); return rtError; + case kwOn: ctx.timestamp = true; break; + case kwOff: ctx.timestamp = false; break; + } + + ctx.timefmt = ctx.toks.size() > 3 && kwOn == checkOnOff(ctx.toks[3]); + return rtOk; +} + +static cmdResponseType setIniFile(connectionRecType &ctx) +{ + // SET INIFILE + IniFile ini(ctx.toks[2]); // Try to open and parse + if(!ini) { + errornl(ctx, fmt::format("Invalid INI-file '{}'", ctx.toks[2])); + return rtError; + } + ctx.inifilename = ctx.toks[2]; + return rtOk; +} + +static cmdResponseType gsNotImpl(connectionRecType &) +{ + return rtError; +} + +static const getsetListType getsetList[] = { + { hcAddF, "ADDF", 0, 2, gsNotImpl, setAddf, alEnable, "", " [order]" }, + { hcComp, "COMP", 1, 0, getComp, gsNotImpl, alEnable, "", "" }, + { hcComps, "COMPS", 0, 0, getComps, gsNotImpl, alEnable, "[pattern]", "" }, + { hcDelF, "DELF", 0, 2, gsNotImpl, setDelF, alEnable, "", " " }, + { hcDelSig, "DELSIG", 0, 1, gsNotImpl, setDelSig, alEnable, "", "" }, + { hcEcho, "ECHO", 0, 1, getEcho, setEcho, alNone, "", "{on|off}" }, + { hcEnable, "ENABLE", 0, 1, getEnable, setEnable, alHello, "", "|{off}" }, + { hcExpand, "EXPAND", 0, 1, getExpand, setExpand, alHello, "", "{on|off}" }, + { hcFunct, "FUNCT", 1, 0, getFunct, gsNotImpl, alEnable, "", "" }, + { hcFuncts, "FUNCTS", 0, 0, getFuncts, gsNotImpl, alEnable, "[pattern]", "" }, + { hcHeader, "HEADER", 0, 1, getHeader, setHeader, alNone, "", "{on|off}" }, + { hcIni, "INI", 2, 0, getIni, gsNotImpl, alHello, "
", "" }, + { hcIniFile, "INIFILE", 0, 1, getIniFile, setIniFile, alHello, "", "" }, + { hcLinkPS, "LINKPS", 0, 1, gsNotImpl, setLinkPS, alEnable, "", " []" }, + { hcLinkSP, "LINKSP", 0, 2, gsNotImpl, setLinkSP, alEnable, "", " " }, + { hcLoadRt, "LOADRT", 0, 1, gsNotImpl, setLoadRt, alEnable, "", "" }, + { hcLock, "LOCK", 0, 1, getLock, setLock, alEnable, "", "{all|load|config|params|run|tune|none}" }, + { hcNet, "NET", 1, 2, getNet, setNet, alHello, "", " [pin [...]]" }, + { hcNewSig, "NEWSIG", 0, 2, gsNotImpl, setNewSig, alEnable, "", " " }, + { hcParam, "PARAM", 1, 0, getParam, gsNotImpl, alEnable, "", "" }, + { hcParams, "PARAMS", 0, 0, getParams, gsNotImpl, alEnable, "[pattern]", "" }, + { hcParamVal, "PARAMVAL", 1, 0, getParamVal, gsNotImpl, alEnable, "", "" }, + { hcParamVals, "PARAMVALS", 0, 0, getParamVals, gsNotImpl, alEnable, "[pattern]", "" }, + { hcPin, "PIN", 1, 0, getPin, gsNotImpl, alEnable, "", "" }, + { hcPins, "PINS", 0, 0, getPins, gsNotImpl, alEnable, "[pattern]", "" }, + { hcPinVal, "PINVAL", 1, 0, getPinVal, gsNotImpl, alEnable, "", "" }, + { hcPinVals, "PINVALS", 0, 0, getPinVals, gsNotImpl, alEnable, "[pattern]", "" }, + { hcSave, "SAVE", 0, 1, gsNotImpl, setSave, alEnable, + "", "{all,comp,sig,link,linka,net,neta,param,thread} [filename]" }, + { hcSetP, "SETP", 0, 2, gsNotImpl, setSetP, alEnable, "", " " }, + { hcSetS, "SETS", 0, 2, gsNotImpl, setSetS, alEnable, "", " " }, + { hcSig, "SIGNAL", 1, 0, getSig, gsNotImpl, alEnable, "", "" }, + { hcSigs, "SIGNALS", 0, 0, getSigs, gsNotImpl, alEnable, "[pattern]", "" }, + { hcSigVal, "SIGVAL", 1, 0, getSigVal, gsNotImpl, alEnable, "", "" }, + { hcSigVals, "SIGVALS", 0, 0, getSigVals, gsNotImpl, alEnable, "[pattern]", "" }, + { hcStart, "START", 0, 0, gsNotImpl, setStart, alEnable, "", "" }, + { hcStop, "STOP", 0, 0, gsNotImpl, setStop, alEnable, "", "" }, + { hcThread, "THREAD", 1, 0, getThread, gsNotImpl, alEnable, "", "" }, + { hcThreads, "THREADS", 0, 0, getThreads, gsNotImpl, alEnable, "[pattern]", "" }, + { hcTime, "TIME", 0, 0, getTime, gsNotImpl, alNone, "", "" }, + { hcTimestamp, "TIMESTAMP", 0, 1, getTimestamp, setTimestamp, alNone, "", "{on|off} [{on|off}]" }, + { hcUnlinkP, "UNLINKP", 0, 1, gsNotImpl, setUnlinkP, alEnable, "", "" }, + { hcUnload, "UNLOAD", 0, 1, gsNotImpl, setUnload, alEnable, "", "|" }, + { hcUnlock, "UNLOCK", 0, 1, gsNotImpl, setUnlock, alEnable, "", "{all|load|config|params|run|tune|none}" }, + { hcVerbose, "VERBOSE", 0, 1, getVerbose, setVerbose, alHello, "", "{on|off}" }, +}; + +static int cmpGSList(const void *a, const void *b) +{ + return compareNoCase(static_cast(a)->name, static_cast(b)->name); +} + +int commandGetSet(connectionRecType &ctx, cmdType getset) +{ + const char *sg = getset == cmdSet ? "SET" : "GET"; + if (ctx.toks.size() < 2) { + // Missing missing parameter + errornl(ctx, fmt::format("Must specify subcommand for {}", sg)); + replynl(ctx, fmt::format("{} NAK", sg)); + return -1; + } + + // Find the command in the list + getsetListType v = {}; + v.name = ctx.toks[1].c_str(); + void *_tag = bsearch(&v, getsetList, NELEM(getsetList), sizeof(getsetList[0]), cmpGSList); + const getsetListType *tag = static_cast(_tag); + if (!tag) { + errornl(ctx, fmt::format("Subcommand not found: {} {}", sg, ctx.toks[1])); + replynl(ctx, fmt::format("{} NAK", sg)); + return -1; + } + + if (getset == cmdGet) { + // GET X command + // Check for enough arguments + if (tag->nget + 2 > ctx.toks.size()) { + errornl(ctx, fmt::format("Too few arguments to GET {}, have {}, need {} or more", + tag->name, ctx.toks.size() - 2, tag->nget)); + replynl(ctx, fmt::format("GET {} NAK", tag->name)); + return -1; + } + cmdResponseType res = tag->getter(ctx); + switch (res) { + case rtOk: + if (ctx.verbose) { + replynl(ctx, fmt::format("GET {} ACK", tag->name)); + } + break; + case rtError: // Standard error response + replynl(ctx, fmt::format("GET {} NAK", tag->name)); + break; + default: replynl(ctx, fmt::format("internal: GET {} returned unknown value '{}'", tag->name, (int)res)); break; + } + } else { + // SET X command + // Check for enough arguments + if (tag->nset + 2 > ctx.toks.size()) { + errornl(ctx, fmt::format("Too few arguments to SET {}, have {}, need {} or more", + tag->name, ctx.toks.size() - 2, tag->nset)); + replynl(ctx, fmt::format("GET {} NAK", tag->name)); + return -1; + } + + if (tag->acclvl >= alHello && !ctx.linked) { + errornl(ctx, fmt::format("A HELLO must be successfully negotiated for SET {}", tag->name)); + replynl(ctx, fmt::format("SET {} NAK", tag->name)); + return -1; + } + + if (tag->acclvl >= alEnable && !isEnabled(ctx)) { + errornl(ctx, fmt::format("Connection must be enabled for SET {}", tag->name)); + replynl(ctx, fmt::format("SET {} NAK", tag->name)); + return -1; + } + + cmdResponseType res = tag->setter(ctx); + switch (res) { + case rtOk: + if (ctx.verbose) { + replynl(ctx, fmt::format("SET {} ACK", tag->name)); + } + break; + case rtError: // Standard error response + replynl(ctx, fmt::format("SET {} NAK", tag->name)); + break; + case rtDelayed: + // A process runs async + break; + default: + replynl(ctx, fmt::format("internal: SET {} returned unknown value '{}'", tag->name, (int)res)); + break; + } + } + return 0; +} + +static int helpGeneral(connectionRecType &ctx) +{ + replynl(ctx, + "Available commands:\r\n" + " HELLO []\r\n" + " GET [parameters...]\r\n" + " SET [parameters...]\r\n" + " SHUTDOWN\r\n" + " HELP \r\n"); + return 0; +} + +static int helpHello(connectionRecType &ctx) +{ + replynl(ctx, + "Usage: HELLO []\r\n" + " Where:\r\n" + " - password: the connection password to allow communications with the server.\r\n" + " - clientname: the name of the client trying to connect.\r\n" + " - protover: the version of the protocol which the client wishes to use.\r\n" + " With valid password, server responds with:\r\n" + " HELLO ACK \r\n" + " Where:\r\n" + " - ACK: acknowledging the connection has been made.\r\n" + " - servername: the name of the LinuxCNC Server to which the client has connected.\r\n" + " - protover: the client requested version or latest version support by server if\r\n" + " the client requests a version later than that supported by the server.\r\n" + " With invalid password, the server responds with:\r\n" + " HELLO NAK\r\n"); + return 0; +} + +static int helpGet(connectionRecType &ctx) +{ + replynl(ctx, + "Usage: GET [parameters...]\r\n" + " All get commands require that a HELLO has been successfully negotiated.\r\n" + " Command may be one of:"); + for (unsigned i = 0; i < NELEM(getsetList); i++) { + const getsetListType *gsl = &getsetList[i]; + if (gsl->getter == gsNotImpl) + continue; + replynl(ctx, fmt::format(" {} {}", gsl->name, gsl->gethelp)); + } + reply(ctx, "\r\n"); + return 0; +} + +static int helpSet(connectionRecType &ctx) +{ + replynl(ctx, + "Usage: SET [parameters...]\r\n" + " All set commands require that a HELLO has been successfully negotiated.\r\n" + " Most set commands require exclusive control over the connection to\r\n" + " LinuxCNC by having successfully executed 'SET ENABLE '.\r\n" + " Set commands not requiring any access level control:"); + for (unsigned i = 0; i < NELEM(getsetList); i++) { + const getsetListType *gsl = &getsetList[i]; + if (gsl->setter == gsNotImpl || gsl->acclvl != alNone) + continue; + replynl(ctx, fmt::format(" {} {}", gsl->name, gsl->sethelp)); + } + reply(ctx, " Set commands requiring HELLO level control:"); + reply(ctx, "\r\n"); + for (unsigned i = 0; i < NELEM(getsetList); i++) { + const getsetListType *gsl = &getsetList[i]; + if (gsl->setter == gsNotImpl || gsl->acclvl != alHello) + continue; + replynl(ctx, fmt::format(" {} {}", gsl->name, gsl->sethelp)); + } + reply(ctx, "\r\n"); + replynl(ctx, " Set commands requiring control enabled:"); + for (unsigned i = 0; i < NELEM(getsetList); i++) { + const getsetListType *gsl = &getsetList[i]; + if (gsl->setter == gsNotImpl || gsl->acclvl != alEnable) + continue; + replynl(ctx, fmt::format(" {} {}", gsl->name, gsl->sethelp)); + } + reply(ctx, "\r\n"); + return 0; +} + +static int helpQuit(connectionRecType &ctx) +{ + replynl(ctx, + "Usage: QUIT\r\n" + " The quit command disconnects the client from the server.\r\n" + " the command has no parameters and no requirements to have negotiated\r\n" + " a hello, or be in control.\r\n"); + return 0; +} + +static int helpShutdown(connectionRecType &ctx) +{ + replynl(ctx, + "Usage: SHUTDOWN\r\n" + " The shutdown command terminates the connections with all clients.\r\n" + " No shutdown of LinuxCNC is performed or attempted. It is not possible\r\n" + " for halrmt to perform any clean shudown procedure.\r\n" + " The command has no parameters, and can only be issued on the connection\r\n" + " that has enable control.\r\n"); + return 0; +} + +static int helpHelp(connectionRecType &ctx) +{ + replynl(ctx, + "Usage: HELP HELP\r\n" + " You need help if you need help on help to help you with help.\r\n" + " It may be time to look into another line of work where computers are\r\n" + " no requirement. Computers will only remind you of the necessary evil\r\n" + " they represent and cause severe helplessness for those requesting help.\r\n"); + return 0; +} + +static int commandHelp(connectionRecType &ctx) +{ + if (ctx.toks.size() < 2) + return helpGeneral(ctx); + std::string s = ctx.toks[1]; + std::transform(s.begin(), s.end(), s.begin(), [](char c) { return std::toupper((unsigned char)c); }); + if (s == "HELLO") return helpHello(ctx); + if (s == "GET") return helpGet(ctx); + if (s == "SET") return helpSet(ctx); + if (s == "QUIT") return helpQuit(ctx); + if (s == "SHUTDOWN") return helpShutdown(ctx); + if (s == "HELP") return helpHelp(ctx); + errornl(ctx, fmt::format("{} is not a valid command. ({})", ctx.toks[1], s)); + return 0; +} + +static int cmpCmdList(const void *a, const void *b) +{ + return compareNoCase(static_cast(a)->name, static_cast(b)->name); +} + +static inline bool isidchar(int ch) +{ + return (isascii(ch & 0xff) && isalnum(ch & 0xff)) || '_' == ch; +} + +static std::string resolveIni(connectionRecType &ctx, const std::string §ion, const std::string &variable) +{ + if(ctx.inifilename.empty()) + return ""; + + IniFile ini(ctx.inifilename); + if(!ini) + return ""; + + if(auto str = ini.findString(variable, section)) + return *str; + errornl(ctx, fmt::format("Ini variable '[{}]{}' not found", section, variable)); + return ""; +} + +static std::string resolveEnv(connectionRecType &ctx, const std::string &variable) +{ + const char *cptr = getenv(variable.c_str()); + if(!cptr) { + errornl(ctx, fmt::format("Environment variable '{}' not found", variable)); + return ""; + } + return cptr; +} + +// +// Expand embedded INI-file and envronment references in form of: +// - [section]variable +// - [section](variable) +// - $envvar +// - $(envvar) +// +static void lineExpand(connectionRecType &ctx, std::string &line) +{ + if(!ctx.expand) + return; + std::string newline; + std::string section; + std::string variable; + enum { ST_START, ST_BOPEN, ST_BCLOSE, ST_POPEN, ST_VAR, ST_ENV, ST_ENVPO, ST_ENVVAR } state = ST_START; + for(const auto &ch : line) { + switch(state) { + default: + case ST_START: + if('[' == ch) { + state = ST_BOPEN; + } else if('$' == ch) { + state = ST_ENV; + } else { + newline += ch; + } + break; + case ST_BOPEN: // Seen '[' + if(']' == ch) { + state = ST_BCLOSE; + } else { + if(!isidchar(ch)) { + // Invalid char in section name. + newline += '[' + section + ch; + section.clear(); + state = ST_START; + } else { + section += ch; + } + } + break; + case ST_BCLOSE: // Seen '[section]' + if('(' == ch) { + state = ST_POPEN; + } else { + if(!isidchar(ch)) { + // Invalid char starting variable + newline += '[' + section + ']' + ch; + variable.clear(); + section.clear(); + state = ST_START; + } else { + state = ST_VAR; + variable += ch; + } + } + break; + case ST_VAR: // Seen '[section]x' + if(!isidchar(ch)) { + if(isspace(ch & 0xff)) { + // Have '[section]variable' --> resolve + newline += resolveIni(ctx, section, variable); + } else { + // Invalid termination --> restore + newline += '[' + section + ']' + variable; + } + newline += ch; + section.clear(); + variable.clear(); + state = ST_START; + } else { + variable += ch; + } + break; + case ST_POPEN: // Seen '[section](' + if(!isidchar(ch)) { + if(')' == ch) { + // Have '[section](variable)' --> resolve + newline += resolveIni(ctx, section, variable); + } else { + // Invalid termination --> restore + newline += '[' + section + "](" + variable + ch; + } + section.clear(); + variable.clear(); + state = ST_START; + } else { + variable += ch; + } + break; + + case ST_ENV: // Seen '$' + if('(' == ch) { + state = ST_ENVPO; + } else { + variable += ch; + state = ST_ENVVAR; + } + break; + case ST_ENVVAR: // Seen '$x' + if(!isidchar(ch)) { + if(isspace(ch & 0xff)) { + // Have '$variable' --> resolve + newline += resolveEnv(ctx, variable); + } else { + // Invalid termination --> restore + newline += '$' + variable; + } + newline += ch; + variable.clear(); + state = ST_START; + } else { + variable += ch; + } + break; + case ST_ENVPO: // Seen '$(' + if(!isidchar(ch)) { + if(')' == ch) { + // Have '$(variable)' --> resolve + newline += resolveEnv(ctx, variable); + } else { + // Invalid termination --> restore + newline += "$(" + variable + ch; + } + variable.clear(); + state = ST_START; + } else { + variable += ch; + } + break; + } + } + + // We can terminate on end-of-string in any state + // Repair any intermediate + switch(state) { + case ST_START: break; + case ST_BOPEN: newline += '[' + section; break; + case ST_BCLOSE: newline += '[' + section + ']'; break; + case ST_POPEN: newline += '[' + section + "](" + variable; break; + case ST_VAR: newline += resolveIni(ctx, section, variable); break; + case ST_ENV: newline += '$'; break; + case ST_ENVVAR: newline += '$' + variable; break; + case ST_ENVPO: newline += "$(" + variable; break; + default: errornl(ctx, fmt::format("internal: Invalid state {} in lineExpand()", (int)state)); break; + } + line = newline; +} + +static int parseCommand(connectionRecType &ctx, std::string &line) +{ + static const char toksep[] = " \t\n\r"; + + size_t pos = 0; + ctx.toks.clear(); // Clear any previous commands/tokens + lineExpand(ctx, line); + while (pos < line.size()) { + // trim left + size_t lft = line.find_first_not_of(toksep, pos); + if (lft == std::string::npos) { + // No non-whitespace characters found to delimit the start + pos = line.size(); // All white-space until end + continue; + } + // Now have: + // arbitrary string with spaces + // ^ + // lft + size_t rgt = line.find_first_of(toksep, lft); + if (rgt == std::string::npos) { + // No whitespace characters found to delimit the end + // The current range [lft,size()) has the token + rgt = line.size(); + } + // Now have: + // arbitrary string with spaces + // ^ ^ + // lft rgt + ctx.toks.push_back(line.substr(lft, rgt - lft)); + pos = rgt; + } + + if (ctx.toks.size() < 1) + return 0; + + // Find the command in the list + commandListType v = {cmdUnknown, 0, ctx.toks[0].c_str()}; + void *_tag = bsearch(&v, commandList, NELEM(commandList), sizeof(commandList[0]), cmpCmdList); + const commandListType *tag = static_cast(_tag); + if (!tag) { + errornl(ctx, fmt::format("Command '{}' not found", ctx.toks[0])); + return 0; + } + + if (ctx.toks.size() < tag->nargs + 1) { + errornl(ctx, fmt::format("Too few arguments for '{}' command", ctx.toks[0])); + return 0; + } + + // Only echo when on, linked and never on HELLO commands + if (ctx.echo && ctx.linked && tag->command != cmdHello) + replynl(ctx, line); + + switch (tag->command) { + case cmdHello: commandHello(ctx); break; + case cmdSet: + case cmdGet: commandGetSet(ctx, tag->command); break; + case cmdQuit: commandQuit(ctx); break; + case cmdHelp: commandHelp(ctx); break; + case cmdShutdown: + if (!commandShutdown(ctx)) { + return -1; + } + break; + default: + replynl(ctx, fmt::format("internal: Invalid command ID '{}' on name='{}'", (int)tag->command, tag->name)); + break; + } + return 0; +} + +/*********************************************************************** +* MAIN PROGRAM * +************************************************************************/ + +static int initSocket() +{ + int optval = 1; + int err; + int sockfd; + struct sockaddr_in6 address = {}; + + sockfd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sockfd < 0) { + xperror("socket()"); + return -1; + } + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); + // Using AF_INET6 will also allow IPv4 to connect (v4-mapped-on-v6) + address.sin6_family = AF_INET6; + address.sin6_addr = IN6ADDR_ANY_INIT; + address.sin6_port = htons(port); + err = bind(sockfd, reinterpret_cast(&address), sizeof(address)); + if (err) { + close(sockfd); + xperror("bind()"); + return -1; + } + + err = listen(sockfd, 5); + if (err) { + close(sockfd); + xperror("listen()"); + return -1; + } + + return sockfd; +} + +static void closeClient(connectionRecType &ctx) +{ + if (ctx.sock < 0) + return; + info("Connection terminated '%s' (%d)", ctx.hostname.c_str(), ctx.sock); + close(ctx.sock); + ctx.sock = -1; +} + +static int sockMain(int svrfd) +{ + std::vector rdbuf(1600); // Declare at top, no need to realloc each round in the loop + std::vector pfds(2); + bool shutdown = false; + + while (!quitloop && (clients.size() || !shutdown)) { + // Make sure we have room for clients and server socket + pfds.resize(clients.size() + 1); + + // Setup poll data for all sockets + pfds[0].fd = svrfd; + pfds[0].events = POLLIN; + pfds[0].revents = 0; + int pto = -1; // Default to infinite poll timeout + if (shutdown) + pto = 0; // Immediate return in shutdown mode + + for (size_t i = 0; i < clients.size(); i++) { + // Close connection when about to shutdown and all is flushed. All + // clients should already have halfclosed set and the input buffer + // cleared. + if (!clients[i].outbuf.size() && shutdown) { + closeClient(clients[i]); + // poll will ignore this entry + } + pfds[i + 1].fd = clients[i].sock; + pfds[i + 1].events = POLLPRI | (!clients[i].halfclosed ? POLLIN : 0) | (clients[i].outbuf.size() ? POLLOUT : 0); + pfds[i + 1].revents = 0; + + // If there is a process launch in progress, don't poll wait + // forever, but use 10ms timeout. + if (clients[i].process.cascade && 0 != pto) { + pto = 10; + } else if (clients[i].inbuf.find_first_of("\r\n") != std::string::npos) { + // If there is still a full line of data in an input buffer then + // make poll return asap. It will still check the descriptors for + // I/O activity. + pto = 0; + } + } + + // Wait until something happens + int err = poll(pfds.data(), pfds.size(), pto); + if (err < 0) { + if (EINTR == errno) { + continue; + } else { + error("poll() returned errno=%d (%s), quiting", errno, strerror(errno)); + return EXIT_FAILURE; + } + } + + // Check server socket first + if (pfds[0].revents & POLLERR) { + // This should never happen...famous...last...words... + error("Server socket received an error on poll, quiting"); + return EXIT_FAILURE; + } else if (pfds[0].revents & POLLIN) { + // POLLIN on a listen socket means new connection available + int cfd; + struct sockaddr_in6 csa; + socklen_t csal = sizeof(csa); + cfd = accept4(svrfd, reinterpret_cast(&csa), &csal, SOCK_CLOEXEC | SOCK_NONBLOCK); + if (cfd < 0) { + switch (errno) { + // The usual errors that should make us retry: + case EAGAIN: + case ECONNABORTED: + break; + // Network related errors should be treated as EAGAIN according + // to accept(2) manual + case ENETDOWN: + case EPROTO: + case ENOPROTOOPT: + case EHOSTDOWN: + case ENONET: + case EHOSTUNREACH: + case EOPNOTSUPP: + case ENETUNREACH: + mysleep(1.0); // Don't busy loop waiting for the net to be up again + break; + // Various restartable errors + case ETIMEDOUT: // Linux kernel thingy, apparently + case ERESTART: // During trace (in accept(2) manual named ERESTARTSYS) + break; + default: + // Any other error will be fatal + xperror("accept()"); + return EXIT_FAILURE; + } + } else { + if (!maxSessions || clients.size() < maxSessions) { + // The accept has set non-blocking and close-on-exec. + // Disable Nagle's algo + int nd = 1; + setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, &nd, sizeof(nd)); + connectionRecType cr = {}; + cr.sock = cfd; + cr.hostname = fmt::format("Default-{}", cfd); + cr.version = "1.1"; + cr.echo = true; + cr.inifilename = inifilename; // Inherit global default + clients.push_back(cr); + char addr[INET6_ADDRSTRLEN] = {}; + inet_ntop(AF_INET6, &csa.sin6_addr, addr, sizeof(addr)); + info("New connection from %s:%d", addr, ntohs(csa.sin6_port)); + } else { + close(cfd); + } + } + } + + // Note that we can have one extra client already added but it does not + // have a pfds entry. That happens when we accept a new connection + // above. However, we still need to check all other clients for + // read/write. + for (size_t i = 0; i < clients.size() && i < pfds.size() - 1; i++) { + if (pfds[i + 1].revents & POLLERR) { + // An error on a socket means we make it to close/abort + clients[i].halfclosed = true; + clients[i].inbuf.clear(); + clients[i].outbuf.clear(); + } + + if (pfds[i + 1].revents & POLLPRI) { + ssize_t res = recv(clients[i].sock, rdbuf.data(), rdbuf.size(), MSG_OOB); + if (res < 0) { + if (errno != EINTR && errno != EAGAIN) { + xperror("recv() OOB data"); + } + } else { + info("Received %zd bytes of OOB data (using telnet?), ignoring", res); + } + } + + if (pfds[i + 1].revents & POLLOUT) { + // Writable... + ssize_t res = write(clients[i].sock, clients[i].outbuf.c_str(), clients[i].outbuf.size()); + if (res < 0) { + if (errno != EINTR && errno != EAGAIN) { + xperror("client write()"); + // Make this connection to close/abort + clients[i].halfclosed = true; + clients[i].inbuf.clear(); + clients[i].outbuf.clear(); + } + } else { + clients[i].outbuf.erase(0, res); // Remove written data from buffer + } + } + + if (pfds[i + 1].revents & POLLIN) { + // Readable... + ssize_t len = read(clients[i].sock, rdbuf.data(), rdbuf.size()); + if (len < 0) { + if (errno != EAGAIN && errno != EINTR) { + xperror("client read()"); + // Make this connection to close but still try flush output + clients[i].halfclosed = true; + clients[i].inbuf.clear(); + } + } else if (0 == len) { + // Socket closed + info("Read close '%s' (%d)", clients[i].hostname.c_str(), clients[i].sock); + clients[i].halfclosed = true; + } else { + // Append the read data + clients[i].inbuf.append(rdbuf.data(), len); + } + } + + if (clients[i].halfclosed && !clients[i].inbuf.size() && !clients[i].outbuf.size()) { + // The input is closed and no more data to handle or write. + // Terminate the connection. + closeClient(clients[i]); + } + } + + // Remove fully closed clients + for (auto i = clients.begin(); i != clients.end();) { + if (-1 == i->sock) { + i = clients.erase(i); + } else { + ++i; + } + } + + // Handle input, one line at a time for each client (round robin) + for (auto i = clients.begin(); i != clients.end(); ++i) { + // Handle the process wait situation. + if (i->process.cascade) { + // Zero return means we're done + if(i->process.cascade(*i)) { + continue; // Non-zero, keep going + } + // Async processing finished + i->process.cascade = NULL; + if(!i->process.acknak.empty()) { + if(i->verbose || i->process.retval) + replynl(*i, fmt::format("SET {} {}", i->process.acknak, i->process.retval ? "NAK" : "ACK")); + i->process.acknak.clear(); + } + } + + // Split into lines and send to parser + size_t nl = i->inbuf.find_first_of("\r\n"); + if (nl != std::string::npos) { + // Isolate the line and remove from input buffer + std::string line = i->inbuf.substr(0, nl); + // and remove any trailing \r\n too + size_t enl = i->inbuf.find_first_not_of("\r\n", nl); + i->inbuf.erase(0, enl); + if (line.size()) { + // Parse the line if there is something on it + // It returns non-zero when the shutdown has been executed + if (parseCommand(*i, line)) { + // We may still have data in the outbuf that needs to + // be flushed. Any remaining output will still be sent + // and then the client will be fully closed and removed. + shutdown = true; + // Halfclose all clients and zap remaining input. + for (auto c = clients.begin(); c != clients.end(); ++c) { + c->halfclosed = true; + c->inbuf.clear(); + } + } + } + } + // Else: Not a finished line, must wait for rest + } + } + + return 0; +} + +static void usage(void) +{ + static const char usage_str[] = + "Usage: halrmt [options]\n" + "Options:\n" + " -h,--help This help\n" + " -p,--port Listen on port 'num' (default=%d)\n" + " -n,--name Set this server's name to 'str' (default=%s)\n" + " -w,--connectpw Set connect/hello password to 'pwd' (default=%s)\n" + " -e,--enablepw Set enable password to 'pwd' (default=%s)\n" + " -s,--sessions Restrict number of session to 'num' (default=%u) (0 for no limit)\n" + " -i,--ini Use 'file' as inifile (default=env[INI_FILE_NAME])\n" + " -q,--quiet Don't print informational messages\n" + "\n" + "Session commands: Connect and use 'help', 'help get' and 'help set' for details.\n" + ; + printf(usage_str, port, serverName.c_str(), helloPwd.c_str(), enablePwd.c_str(), maxSessions); +} + +static bool check_white(const std::string &s) +{ + for (const char &c : s) { + if (std::iscntrl((unsigned char)c) || std::isspace((unsigned char)c)) + return true; + } + return false; +} + +static struct option longopts[] = { + {"help", 0, NULL, 'h'}, + {"name", 1, NULL, 'n'}, + {"port", 1, NULL, 'p'}, + {"connectpw", 1, NULL, 'w'}, + {"enablepw", 1, NULL, 'e'}, + {"sessions", 1, NULL, 's'}, + {"ini", 1, NULL, 'i'}, + {"quiet", 0, NULL, 'q'}, + {} +}; + +int main(int argc, char **argv) +{ + int optc; + int lose = 0; + char *eptr; + + /* set default level of output - 'quiet' */ + rtapi_set_msg_level(RTAPI_MSG_ERR); + /* set default for other options */ + // process halrmt command line args + while(-1 != (optc = getopt_long(argc, argv, "e:hi:n:p:qs:w:", longopts, NULL))) { + switch(optc) { + case 'h': + usage(); + return EXIT_FAILURE; + + case 'e': + enablePwd = optarg; + if (!compareNoCase(enablePwd, "OFF")) { + error("Invalid enable password. You cannot use 'OFF' as enable password"); + lose++; + } else { + if (check_white(enablePwd)) { + error("You cannot have control or space characters in the enable password"); + lose++; + } + } + break; + case 'i': { + inifilename = optarg; + IniFile ini(inifilename); // Parse file to see it alright (and cache it) + if(!ini) + lose++; + break; } + case 'q': + quiet++; + break; + case 'n': + serverName = optarg; + if (check_white(serverName)) { + error("You cannot have control or space characters in the server name"); + lose++; + } + break; + case 'w': + helloPwd = optarg; + if (check_white(helloPwd)) { + error("You cannot have control or space characters in the password"); + lose++; + } + break; + case 's': + maxSessions = strtoul(optarg, &eptr, 0); + if (eptr == optarg || *eptr) { + error("Invalid maxSessions '%s', must be integer in range [0,...)", optarg); + lose++; + } + break; + case 'p': + port = strtol(optarg, &eptr, 0); + if (eptr == optarg || *eptr || port <= 0 || port >= 65535) { + error("Invalid port '%s', must be integer in range [1,65534]", optarg); + lose++; + } + break; + + default: + lose++; + break; + } + } + + if(inifilename.empty()) { + const char *cptr = getenv("INI_FILE_NAME"); + if(!cptr) { + info("No inifile available"); + } else { + inifilename = cptr; + IniFile ini(inifilename); + if(!ini) + lose++; + } + } + + if(lose) + return EXIT_FAILURE; + + // Initialize the listen socket + int sockfd = initSocket(); + if (sockfd < 0) { + return EXIT_FAILURE; + } + + // Get us access to HAL + int rv = hal_lib_init(); + if(0 != rv) { + close(sockfd); + return EXIT_FAILURE; + } + + // Make sure we quit when requested + signal(SIGINT, sigQuit); + signal(SIGQUIT, sigQuit); + signal(SIGTERM, sigQuit); + // Catch termination of our sub-processes + signal(SIGCHLD, sigChld); + // No need to kill ourselves + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + rv = sockMain(sockfd); + + // Disconnect from HAL shared memory + hal_lib_exit(); + + return rv; +} +// vim: ts=4 sw=4 et