---
name: "io_ctrl"
section: "3"
platform: "linux"
package: "libnessus-dev"
version: "1.0.10-2"
release: "Debian 3.0 (Woody)"
platforms: ["linux"]
url: "https://new.carta.tech/io_ctrl"
---

# io\_ctrl(3)

generic functions for the socket replacement io layer

## NAME

io\_ctrl - generic functions for the socket replacement io layer

## SYNOPSIS

```
#include <peks.h>

int io_ctrl (unsigned fd, io_ctrl_request request,
             int *arg argument, unsigned how);
```

## DESCRIPTION

Refering to the man page for **[io\_push(3)](/io_push/3.md)**, this function calls simply

- **(\****ioctl\_fn***) (***desc***,** *request***,** *arg***)**

  where **desc = desc (fd, how)** is the data area initialized as described in **[io\_push(3)](/io_push/3.md)**, and **ioctl\_fn()** is the call back function passed as argument to **io\_push()** upon initialization. There is a set of defined standard requests all defined in the **peks.h** header file, as there are:

- **IO\_RESIZE\_BUF**

  Argument is **(int\*)***buffer\_size*, or 0

  Return code is the buffer size currently effective

  Any sender or receiver define a new size of the maximal data block that can be processed at once.  The request returns the current buffer size (which might be different from the argument size due to some adjustments,) or -1 to indicate some error.

  When the sender defines a new buffer size limit, it also adjusts its working buffer to that limit and forwards that information to the receiver not until the next data packet is sent.

  If the working buffer size sent is not larger than the limit on the receiver, it will decrease the internal working buffer size (still remembering the buffer size limit) to the new size. Otherwise it will ignore that information.

  By default, the internal buffer size limit on the receiver is CBC\_IO\_BUFLEN\_MAX, and the working buffers are set to CBC\_IO\_BUFLEN on both, sender and receiver.

- **IO\_PUT\_SESSION\_KEY**

  Argument is *key\_data***\[KEYLENGTH\_MAX\]**

  Return code is the 0, or -1 on error.

  If the argument *key\_data* exists, a new master session key  will be set up by hashing the current master session key with the argument data. In any case, the use of the master session key will be enabled. So the master session key will be hashed with every new session key refresh.

  The client also registeres, that the next data packet to be received *must* invoke a session key update, otherwise an error will be detected.  The server will immediately change the session key and send the key change request to the client.

  Upon error -1 is returned.

- **IO\_SET\_KEY\_SCHEDULE**

  Argument is **(int\*)***key\_schedule*

  Return code is the time to refresh the session key next, or the  key refresh interval previosly effective.

  The integer value passed by the argument pointer defines a key schedule: A positive value sets the time interval (in seconds) to the next change while the absolute value of a negative argument implies a time period (in seconds), when the key has to be changed at regular intervals.  A value 0 disables any automated session key refresh.

  The request returns a non negative value indicating the old  setting of the time due when the argument was positive, or the key schedule when the argument pointer was NULL, or the argument value was negative or zero.  Upon error -1 is returned.

- **IO\_REGISTER\_THREAD**

  Argument is ignored

  Return code is the id *threadID* of the io thread just created

- **IO\_ACTIVATE\_THREAD**

  Argument is **(int\*)***threadID* or 0

  Return code is the id *threadID* of the io thread previously active

  A thread is an independent instance of the sender descriptor piping through the same io channel. There is always just one active thread. The thread ID that was active before the new one was activated is the  return code passed back from the registry service (unless -1 to indicate  an error.)

- **IO\_UNLINK\_THREAD**

  Argument is **(int\*)***threadID* or 0

  Return code is the id *threadID* of the io thread record just removed

- **IO\_UNLINK\_THREAD\_PID**

  Argument is **(unsigned long\*)***PID* or 0

  Return code is the number of io thread records, removed

- **IO\_DESTROY\_THREAD**

  Argument is **(int\*)***threadID* or 0

  Return code is the id *threadID* of the io thread just closed

  Before forking, the sender has a new thread registered on sender and receiver.  After forking, the child activates the thread while the  parent unlinks the thread without notifying the receiver.  When the child finishes, it destroys the tread, finally. Upon success, the destroy or unlink functions return the id of the affected thread, and  -1, otherwise while errno is set to indicate the type of error.

  Example:

  ```
  int id = io_ctrl (socket, IO_REGISTER_THREAD, 0, 1);
  ...
  if (fork () == 0) {
  /* child */
  io_ctrl (socket, IO_ACTIVATE_THREAD, &id, 1);
  ...
  io_close (socket);
  /* implies io_ctrl (, IO_DESTROY_THREAD, ); */
  exit (0);
  }
  /* parent */
  io_ctrl (socket, IO_UNLINK_THREAD, &id, 1);
  ...
  ```

- **IO\_DESTROY\_THREAD\_PID**

  Argument is **(unsigned long\*)***PID* or 0

  Return code is the number of io thread records locally found

  The unlink/destroy requests are available in some other flavour, as  well.  When a thread is registered, or activated the process id is  stored.  Upon pid-unlinking or pid-destroying, all those threads are  canceled, that have been created/activated by the  PID  given as argument.  On argument value 0, all threads are unlinked or destroyed, when the NULL pointer is passed, the current process id is used.  So, with the same effect as before, the last **io\_ctrl()** statement in the example, above can be replaced by

  ```
  ...
  }
  /* parent */
  io_ctrl (socket, IO_UNLINK_THREAD_PID, 0, 1);
  ...
  ```

  Upon return, the non-negative number of threads unlinked, or  destroyed is returned, and -1 if an error occured (while the errno  variable is set.)

- **IO\_PURGE\_THREAD**

  Argument is **(int\*)***threadID* or 0

  Return code is the id *threadID* of the io thread just closed

  This request is mainly the same as **IO\_DESTROY\_THREAD** (see above) with a lazy way to remove the io threads on the receiver. On the reciever, a purged io thread is not deleted unless the threads table reaches its maximum size. Then the oldest io thread that has been purged is deleted, first.

  As a side effect, the receiver will continue accepting messages on purged io threads (but not pass it to the invoking function) up until the io thread is finally removed from the table.

- **IO\_PURGE\_THREAD\_PID**

  Argument is **(unsigned long\*)***PID* or 0

  Return code is the number of io thread records locally found

  This function is basically the same as **IO\_DESTROY\_THREAD\_PID** but with the laziness on the receiver as described in **IO\_PURGE\_THREAD\_PID**.

- **IO\_SYNTHETIC\_PID**

  Argument is **(unsigned long\*)***PID* or 0

  Return code is the synthetic *PID* previosly effective

  In order to control the result of **getpid()** used, when threads are created or activated, a static value can be stored on both, sender and receiver thread. If the argument value is 0, it is deactivated.  If the argument is NULL, the current pid is used. Upon return, the old value of the synthetic pid is used (which might be negative, but errno is zero.)  On error, -1 is returned and the variable errno is set.

- **IO\_COMPRESS\_LEVEL**

  Argument is **(int\*)***CompressLevel*, or 0

  Return code is the previous state (0 or 1) of the compression for this channel.

  if the argument *CompessLevel* points to some non-negative integer, the current io channel will be compressed according to the integer value. If the interger value is negative, a current compression is removed from the channel.

  Sensible compression values are 0 =\> maximal speed, ... 9 =\> maximal compression. Other values will produce some sensible default.

  When forking (see REGISTER\_THREAD), the newly reated thread inherits the compression property from the current one.

  Upon return, 0 is returned if there was no compression previously active, and 1 otherwise.   On error, -1 is returned and the variable errno is set, accordingly.

- **IO\_MAX\_THREADS**

  Argument is **(int\*)***ThreadsMax*, or 0

  Return code is the maximal number *ThreadsMax*, previously effective

  The number of active threads can be controlled by the this request on receiver.  Note, that the receiver will be most likely unable to  resyncronize, when this limit is exeeded.  All that currently can be done is to close, and reopen the dialogue.

  On error, -1 is returned and the variable errno is set.

- **IO\_HWMBL\_THREADS**

  Argument is **(int\*)***HighWaterMarkBl*, or 0

  Return code is the value of the *HighWaterMarkBl*, previously effective.

  In addition to the maximal value of threads, the receiver can impose a default *high water mark*, so that an action occurs whenever the number of threads exceeds this value. The *high water mark* for threads trapping is calculated as

  ThreadsMax - HighWaterMarkBl

  where first term is described earlier (see **IO\_MAX\_THREADS**.)

  On error, -1 is returned and the variable errno is set.

- **IO\_TRAP\_THREAD\_VRFY**

  Arg is **(void(\*\*)(void\*,unsigned long\*,int,int))***TrapFn*, or 0

  Return code is 1 if the previous trap was non-NULL, and 0 otherwise while in case that the argument pointer is non-NULL, the target value is replaced by the trap, previosly effective.

  By setting trap functions a defensive feature is provided to prevent the receiver from a threads table overflow. Of course, this happens only if the sender continues creating new threads without cleaning up the old ones which have expired, already.

  Whenever the number of threads on the receiver exeeds a certain *high water mark* (see **IO\_HWMBL\_THREAD**) the trap function is called. This trap function receives a random list of threads that may be passed to the sender (see **IO\_SEND\_THREAD\_VRFY**) for checking. The trap function will be invoked as **TrapFn(***fd***,***list***,1)** where *fd* is the current file descriptor of type *void\**  (you need to cast it as *(int)fd*) and *list* is a  *unsigned short\** array of at most 8 thread ids to be checked, followed by a terminating 0 entry. On the receiver, the third argument will always be zero.

  Example (receiver):

  ```
  #include <assert.h>

  void trap
    (void            *fd,
     unsigned long *list,
     int       using_pid,
     int             how)
  {
  assert (using_pid == 0)	;
  io_ctrl ((int)fd, IO_SEND_THREAD_VRFY, list);
  }
  ```

  When the sender receives a trap function (note that we assume a data channel in either direction) on the other channel, it invokes the trap function as **TrapFn(***fd***,***list***,0),** where the list is array of at most 8 thread ids to be checked, followed by a terminating 0 entry as passed to the function  **io\_ctrl(***(int)fd***,***IO\_SEND\_THREAD\_VRFY***,***list***)** in the receiver example, above. On the receiving sender, the third argument will be zero the **io\_ctrl()** function was used with the request **IO\_SEND\_THREAD\_VRFY**, and 1 if the request was  **IO\_SEND\_THREAD\_VRFY\_PID** (see below.)

  Example (sender):

  ```
  void trap
    (void          *fd,
     unsigned long *id,
     int     using_pid,
     int           how)
  {
  while (*id) {
  if (using_pid) {
  if (!that_pid_exists (*id))
  io_ctrl ((int)fd, IO_PURGE_THREAD_PID, id);
  } else {
  if (!that_thread_id_exists (*id))
  io_ctrl ((int)fd, IO_PURGE_THREAD, id);
  }
  id ++ ;
  }
  }
  ```

  It makes sense to combine the preceeding two examples to a single trap function interpreting the third argument value *how*.

- **IO\_SEND\_THREAD\_VRFY**

  Argument is **(unsigned long\*)***TidList*, or 0

  Return code is the number of the first non-zero entries in the *TidList\[\]*.

  The argument list is an array of at most 8 entries, the last entry is a 0. This function sends short numbers interpreted as thread ids to the receiver. If installed, a trap function will be invoked on the other end as described above (see **IO\_TRAP\_TREAD\_VRFY**.)

  When a trap function **void(\*)(void\*,unsigned long\*,int,int)** is invoked on the receiver, the third argument is always zero.

  If there are more than 8 entries provided, the extra will be  ignored. On error, -1 is returned and the variable errno is set.

- **IO\_SEND\_THREAD\_VRFY\_PID**

  Argument is **(unsigned long\*)***TidList*, or 0

  Return code is the number of the first non-zero entries in the *TidList\[\]*.

  This request is basically the same as **IO\_SEND\_THREAD\_VRFY** but acting upon process ids (see **IO\_SYNTHETIC\_PID**) rather than thread ids in the receiver.

  When a trap function **void(\*)(void\*,unsigned long\*,int,int)** is invoked on the receiver, the third argument is always 1.

- **IO\_CATCH\_THREAD**

  Argument is **(int(\*\*)(char\*,unsigned,void \*\*))***CallBackFn*, or 0

  Return code is the id of the current call back function while in the case of **CallBackFn != 0**, the pointer target is replaced by the value of the call back function previously active/registered.

  When called from a normal context on the receiver and non NULL  \*CallBackFn argument, this request registeres a call back function to be assigned to the next thread that will be created (due to a  **REGISTER\_THREAD** on the sender).  After that, the call back function  will have been moved to the active thread an not be available anymore. So you need another **IO\_CATCH\_THREAD** request if you like the next thread to be catched, as well. The call back function looks like

  ```
  int CallBackFn (char *data, unsigned len,
                  void *socket, void **context);
  ```

  where data and len describe the data input buffer as passed to  **io\_read()**, len in the current size of input data just received.  The third value context points to a zero initialized static data area large enough to hold a pointer variable (pointing to a pseudostatic function context).

  Only when the thread is to be called for the first or the last time, the function is invoked with **buf==0**, where the arguments indicate the events listed below:

  ```
  buf len event
  -------------------------------------------------
  0   0  destructor
  0   1  constructor, first call of that function
  0   2  constructor, fn was cloned on a new thread
  ```

  Unless invoked for the first time or with **buf==0** (wich is the last time), the return code for this call back function is handled as follows:

  *return code \< 0* , **io\_read()** will return -1 (you should set the  errno variable in this case)

  *return code \>= 0* , **io\_read()** returns this number as data length,  read (so you should adjust the contents of the  input buffer pointed to by the argument buf)

  If you process data with the call back function, you should always invoke  an **IO\_FLUSH\_CACHE** control request before you leave the function as data in the cache that came with this thread cannot be handled with a call back function, anymore.

  If the **IO\_CATCH\_THREAD** request is dispatched from within an active call back function, the registration will refer to the currently active thread.  So it is possible to change the call back function from within the currently running call back function.

  If the CallBackFn argument points to a zero value, the currently  registered call back function is removed or deactivated, depending on the calling context.

  Example:

  ```
  int thread_catcher
    (char    *buf,
     unsigned len,
     void *socket,
     void     **p)
  {
  CTX* context ;

  /* get my thread id */
  int myself = io_ctrl
  ((int)socket, IO_CATCH_TRHEAD,0,0);

  if (buf == 0) {
  if (len == 0) {
  free (context);
  return 0 ;
  }
  *p = malloc (sizeof (CTX)) ;
  }
  context = *p ;

  /* so lets look what the data are like */
  if (len > 0) {
  ...
  }

  /* ok, that's it */
  if (ok != 0)
  return len;
  ...

  /* error */
  return -1;
  }
  ```

  On error, -1 is returned and the variable errno is set.

- **IO\_CATCH\_CLONING**

  Argument is **(int\*)***CloneFlag*, or 0

  Return code is the *CloneFlag*, previously effective

  The *CloneFlag* controls on the receiver, if any io thread can clone its call back functions when creating a new thread (invoked by the sender).  By default, this flag is reset.

  This directive works on the same context as the **IO\_CATCH\_THREAD** directives.  Preferrably it should be invoked by the running call back function in which case it acts on a local context.  If a call back function is cloned, the *CloneFlag* flag will not be copied but reset.

- **IO\_CATCH\_ENVP**

  Argument is **(void \*\*)***ENV*, or 0

  Return code is 0, if there was no catcher function active, and 1 otherwise.

  In the latter case, if *ENV* is non-NULL, the  pointer target is replaced by the value of ENV previously  active/registered.

  If a catcher function has been registered with **IO\_CATCH\_THREAD**, the **IO\_CATCH\_ENVP** request will attach an environment pointer to that function.

  This environment simply pre-initialize the last argument passed to an active thread call back function **CallBackFn()**.

  Once registred, this environment will be moved to the active thread, when it is initialized.  So you need another **IO\_CATCH\_ENVP** request after the next **IO\_CATCH\_THREAD** request, if you like the next thread to have attached another environment, as well.

- **IO\_UNCATCH\_THREAD**

  Argument is **(int \*)***CallBackFnID*, or 0

  Return code is the id of the currently removed call back function

  The request removes a currently active thread for the given *CallBackFnID* and returns the number if the thread, removed. When the argument is NULL, the thread currently waiting to be assigned to the next created thread is removed.

- **IO\_PUBLIC\_DESTROY**

  Argument is **(int\*)***PublicDestroyFlag*, or 0

  Return code is the*PublicDestroyFlag*, previously effective

  The *PublicDestroyFlag* controls on the receiver, if any io stream can destroy another io stream different from the current one.  By default, this flag is reset with the effect, that an io stream can destroy itself, only.

- **IO\_STOPONEMPTY\_STATE**

  Argument is **(int\*)***StopOnEmptyFlag*, or 0

  Return code is the *StopOnEmptyFlag* value, previously effective

  With the following commands, a stop-on-empty-frame flag is controlled. At some requests, the sender needs to inform the receiver about  changes of the io channel state.  This is done by sending an empty  frame with embedded commands.  In **IO\_STOPONEMPTY\_STATE** with an argument pionter to a zero value, the application does not see that empty frame (which is the default). If a non-zero argument is pointed,  to these empty frames are returned to the **io\_recv()** calling process.  The **io\_ctrl()** function returns the state of the stop-on-empty-frame flag that was active before the change took place.

- **IO\_EOF\_STATE**

  Argument is **(int\*)***EofFlag*, or 0

  Return code is the *EofFlag* value, previously effective

  When an empty frame which is not an embedded command is received, the  eof flag will be set.  This can be used to distinguish a real empty  frame in **IO\_STOPONEMPTY\_STATE** active from an empty frame caused by an embedded command, in which case the eof-state is false.  In order to read on after having received an empty frame, the eof state must be reset.  The **io\_ctrl()** function return the state of the eof flag that was active before the change took place.

- **IO\_TOTAL\_COUNTER**

  Argument is **(int\*)***CounterState*, or 0

  Return code is the *CounterState* value, previously effective

- **IO\_PAYLOAD\_COUNTER**

  Argument is **(int\*)***CounterState*, or 0

  Return code is the **(int\*)***CounterState* value, previously effective

  The underlying protocol layer updates two counters, keeping track of  the user bytes processed and the total protocol overhead (including the user payload.) The latter counter does not take into account the  protocol overhead that is induced by using the TCP/IP (or whatever)  transport protocol. Note, that the interface to reading out the counters  is made up by an int data type, which may overflow causeing a negative  return value.  In this case, the errno register is set to 0.

- **IO\_FLUSH\_CACHE**

  Argument is ignored

  Return code is the number of bytes, flushed

  Any cached data not processed, yet are flushed.  Currently, this has an effect on the receiver of the data stream, only.  In general,  transferring data blockwise, tehre is no need for the sender to cache data, but for the receiver when it getts more data in than requested by the application.

  It is save calling the function **io\_ctrl()** without having installed a data stream diversion using **io\_push()**.

## ERROR CODES

When there was no data stream, pushed the **io\_ctrl()** will immediately return with -1. The the global variable **errno** wil be set to **EBADF** (Bad file descriptor), see [errno(3)](/errno/3.md) for details on error codes.

Other important error return symbols, see peks\_stderr(3):

```
IOST_NO_RECV_CTLFN   No receiver input fn, assigned
IOST_NO_SEND_CTLFN   No sender control fn, assigned
```

## SEE ALSO

[**iostream**(3)](/iostream/3.md), [**getpid**(2)](/getpid/2.md), [**errno**(3)](/errno/3.md), [**peks-errnum**(3)](/peks-errnum/3.md),  [**io\_push**(3)](/io_push/3.md), [**io\_send**(3)](/io_send/3.md), [**fork**(2)](/fork/2.md),

## AUTHOR

The **peks** package where these functions belong to was written by Jordan Hrycaj \<jordan@teddy-net.com\>, originally to support the **nessus** security scanner.
