From 70844bcd5cf8b229150cbeecc502ac604a89c70b Mon Sep 17 00:00:00 2001 From: Kern Sibbald Date: Mon, 19 Feb 2007 12:09:01 +0000 Subject: [PATCH] kes Expand new BSOCK class adding signal() and new BNET signals. kes Begin enhancing the dot commands adding a new API function that returns more information and will make it easier to interface to the Director from a program. git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@4207 91ce42f0-d328-0410-95d8-f526ca767f89 --- bacula/src/dird/ua.h | 17 +-- bacula/src/dird/ua_cmds.c | 39 +++---- bacula/src/dird/ua_dotcmds.c | 212 +++++++++++++++++++++-------------- bacula/src/dird/ua_status.c | 16 ++- bacula/src/lib/bnet.c | 8 +- bacula/src/lib/bsock.c | 12 ++ bacula/src/lib/bsock.h | 15 ++- bacula/src/version.h | 4 +- bacula/technotes-2.1 | 4 + 9 files changed, 201 insertions(+), 126 deletions(-) diff --git a/bacula/src/dird/ua.h b/bacula/src/dird/ua.h index 9b71b8d856..eb849b2189 100644 --- a/bacula/src/dird/ua.h +++ b/bacula/src/dird/ua.h @@ -1,14 +1,7 @@ -/* - * Includes specific to the Director User Agent Server - * - * Kern Sibbald, August MMI - * - * Version $Id$ - */ /* Bacula® - The Network Backup Solution - Copyright (C) 2001-2006 Free Software Foundation Europe e.V. + Copyright (C) 2001-2007 Free Software Foundation Europe e.V. The main author of Bacula is Kern Sibbald, with contributions from many others, a complete list can be found in the file AUTHORS. @@ -32,6 +25,13 @@ (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, Switzerland, email:ftf@fsfeurope.org. */ +/* + * Includes specific to the Director User Agent Server + * + * Kern Sibbald, August MMI + * + * Version $Id$ + */ #ifndef __UA_H_ #define __UA_H_ 1 @@ -51,6 +51,7 @@ struct UAContext { char **prompt; /* list of prompts */ int max_prompts; /* max size of list */ int num_prompts; /* current number in list */ + int api; /* For programs want an API */ bool auto_display_messages; /* if set, display messages */ bool user_notified_msg_pending; /* set when user notified */ bool automount; /* if set, mount after label */ diff --git a/bacula/src/dird/ua_cmds.c b/bacula/src/dird/ua_cmds.c index 8b3c18fdaf..79bb548bf4 100644 --- a/bacula/src/dird/ua_cmds.c +++ b/bacula/src/dird/ua_cmds.c @@ -1,15 +1,7 @@ -/* - * - * Bacula Director -- User Agent Commands - * - * Kern Sibbald, September MM - * - * Version $Id$ - */ /* Bacula® - The Network Backup Solution - Copyright (C) 2000-2006 Free Software Foundation Europe e.V. + Copyright (C) 2000-2007 Free Software Foundation Europe e.V. The main author of Bacula is Kern Sibbald, with contributions from many others, a complete list can be found in the file AUTHORS. @@ -33,7 +25,15 @@ (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, Switzerland, email:ftf@fsfeurope.org. */ - +/* + * + * Bacula Director -- User Agent Commands + * + * Kern Sibbald, September MM + * + * Version $Id$ + */ + #include "bacula.h" #include "dird.h" @@ -494,11 +494,11 @@ static int cancel_cmd(UAContext *ua, const char *cmd) * Pool DB base record from a Pool Resource. We handle * the setting of MaxVols and NumVols slightly differently * depending on if we are creating the Pool or we are - * simply bringing it into agreement with the resource (update). + * simply bringing it into agreement with the resource (updage). * * Caution : RecyclePoolId isn't setup in this function. * You can use set_pooldbr_recyclepoolid(); - * + * */ void set_pooldbr_from_poolres(POOL_DBR *pr, POOL *pool, e_pool_op op) { @@ -542,19 +542,20 @@ bool set_pooldbr_recyclepoolid(JCR *jcr, B_DB *db, POOL_DBR *pr, POOL *pool) bstrncpy(rpool.Name, pool->RecyclePool->name(), sizeof(rpool.Name)); if (db_get_pool_record(jcr, db, &rpool)) { - pr->RecyclePoolId = rpool.PoolId; + pr->RecyclePoolId = rpool.PoolId; } else { - Jmsg(jcr, M_WARNING, 0, - _("Can't set %s RecyclePool to %s, %s is not in database, try to update it with 'update pool=%s'\n"),pool->name(),rpool.Name, rpool.Name,pool->name()); - - ret = false; + Jmsg(jcr, M_WARNING, 0, + _("Can't set %s RecyclePool to %s, %s is not in database, try to upda + + ret = false; } - } else { /* no RecyclePool used, set it to 0 */ + } else { /* no RecyclePool used, set it to 0 */ pr->RecyclePoolId = 0; } return ret; } + /* * Create a pool record from a given Pool resource * Also called from backup.c @@ -1469,7 +1470,7 @@ static int use_cmd(UAContext *ua, const char *cmd) int quit_cmd(UAContext *ua, const char *cmd) { - ua->quit = TRUE; + ua->quit = true; return 1; } diff --git a/bacula/src/dird/ua_dotcmds.c b/bacula/src/dird/ua_dotcmds.c index 04e87c0da9..53326616d7 100644 --- a/bacula/src/dird/ua_dotcmds.c +++ b/bacula/src/dird/ua_dotcmds.c @@ -1,19 +1,7 @@ -/* - * - * Bacula Director -- User Agent Commands - * These are "dot" commands, i.e. commands preceded - * by a period. These commands are meant to be used - * by a program, so there is no prompting, and the - * returned results are (supposed to be) predictable. - * - * Kern Sibbald, April MMII - * - * Version $Id$ - */ /* Bacula® - The Network Backup Solution - Copyright (C) 2002-2006 Free Software Foundation Europe e.V. + Copyright (C) 2002-2007 Free Software Foundation Europe e.V. The main author of Bacula is Kern Sibbald, with contributions from many others, a complete list can be found in the file AUTHORS. @@ -37,6 +25,18 @@ (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, Switzerland, email:ftf@fsfeurope.org. */ +/* + * + * Bacula Director -- User Agent Commands + * These are "dot" commands, i.e. commands preceded + * by a period. These commands are meant to be used + * by a program, so there is no prompting, and the + * returned results are (supposed to be) predictable. + * + * Kern Sibbald, April MMII + * + * Version $Id$ + */ #include "bacula.h" #include "dird.h" @@ -50,40 +50,46 @@ extern struct s_res resources[]; extern void do_messages(UAContext *ua, const char *cmd); extern int quit_cmd(UAContext *ua, const char *cmd); extern int qhelp_cmd(UAContext *ua, const char *cmd); -extern int qstatus_cmd(UAContext *ua, const char *cmd); +extern bool dot_status_cmd(UAContext *ua, const char *cmd); + /* Forward referenced functions */ -static int diecmd(UAContext *ua, const char *cmd); -static int jobscmd(UAContext *ua, const char *cmd); -static int filesetscmd(UAContext *ua, const char *cmd); -static int clientscmd(UAContext *ua, const char *cmd); -static int msgscmd(UAContext *ua, const char *cmd); -static int poolscmd(UAContext *ua, const char *cmd); -static int storagecmd(UAContext *ua, const char *cmd); -static int defaultscmd(UAContext *ua, const char *cmd); -static int typescmd(UAContext *ua, const char *cmd); -static int backupscmd(UAContext *ua, const char *cmd); -static int levelscmd(UAContext *ua, const char *cmd); -static int getmsgscmd(UAContext *ua, const char *cmd); - -struct cmdstruct { const char *key; int (*func)(UAContext *ua, const char *cmd); const char *help; }; +static bool diecmd(UAContext *ua, const char *cmd); +static bool jobscmd(UAContext *ua, const char *cmd); +static bool filesetscmd(UAContext *ua, const char *cmd); +static bool clientscmd(UAContext *ua, const char *cmd); +static bool msgscmd(UAContext *ua, const char *cmd); +static bool poolscmd(UAContext *ua, const char *cmd); +static bool storagecmd(UAContext *ua, const char *cmd); +static bool defaultscmd(UAContext *ua, const char *cmd); +static bool typescmd(UAContext *ua, const char *cmd); +static bool backupscmd(UAContext *ua, const char *cmd); +static bool levelscmd(UAContext *ua, const char *cmd); +static bool getmsgscmd(UAContext *ua, const char *cmd); + +static bool api_cmd(UAContext *ua, const char *cmd); +static bool dot_quit_cmd(UAContext *ua, const char *cmd); +static bool dot_help_cmd(UAContext *ua, const char *cmd); + +struct cmdstruct { const char *key; bool (*func)(UAContext *ua, const char *cmd); const char *help; }; static struct cmdstruct commands[] = { - { NT_(".backups"), backupscmd, NULL}, - { NT_(".clients"), clientscmd, NULL}, - { NT_(".defaults"), defaultscmd, NULL}, - { NT_(".die"), diecmd, NULL}, - { NT_(".exit"), quit_cmd, NULL}, - { NT_(".filesets"), filesetscmd, NULL}, - { NT_(".help"), qhelp_cmd, NULL}, - { NT_(".jobs"), jobscmd, NULL}, - { NT_(".levels"), levelscmd, NULL}, - { NT_(".messages"), getmsgscmd, NULL}, - { NT_(".msgs"), msgscmd, NULL}, - { NT_(".pools"), poolscmd, NULL}, - { NT_(".quit"), quit_cmd, NULL}, - { NT_(".status"), qstatus_cmd, NULL}, - { NT_(".storage"), storagecmd, NULL}, - { NT_(".types"), typescmd, NULL} + { NT_(".api"), api_cmd, NULL}, + { NT_(".backups"), backupscmd, NULL}, + { NT_(".clients"), clientscmd, NULL}, + { NT_(".defaults"), defaultscmd, NULL}, + { NT_(".die"), diecmd, NULL}, + { NT_(".exit"), dot_quit_cmd, NULL}, + { NT_(".filesets"), filesetscmd, NULL}, + { NT_(".help"), dot_help_cmd, NULL}, + { NT_(".jobs"), jobscmd, NULL}, + { NT_(".levels"), levelscmd, NULL}, + { NT_(".messages"), getmsgscmd, NULL}, + { NT_(".msgs"), msgscmd, NULL}, + { NT_(".pools"), poolscmd, NULL}, + { NT_(".quit"), dot_quit_cmd, NULL}, + { NT_(".status"), dot_status_cmd, NULL}, + { NT_(".storage"), storagecmd, NULL}, + { NT_(".types"), typescmd, NULL} }; #define comsize (sizeof(commands)/sizeof(struct cmdstruct)) @@ -93,10 +99,10 @@ static struct cmdstruct commands[] = { int do_a_dot_command(UAContext *ua, const char *cmd) { int i; - int len, stat; + int len; + bool ok = false; bool found = false; - - stat = 1; + BSOCK *sock = ua->UA_sock; Dmsg1(1400, "Dot command: %s\n", ua->UA_sock->msg); if (ua->argc == 0) { @@ -111,21 +117,38 @@ int do_a_dot_command(UAContext *ua, const char *cmd) if (strncasecmp(ua->argk[0], _(commands[i].key), len) == 0) { bool gui = ua->gui; ua->gui = true; - stat = (*commands[i].func)(ua, cmd); /* go execute command */ + if (ua->api) { + sock->signal(BNET_CMD_BEGIN); + } + ok = (*commands[i].func)(ua, cmd); /* go execute command */ ua->gui = gui; found = true; break; } } if (!found) { - pm_strcat(ua->UA_sock->msg, _(": is an invalid command\n")); - ua->UA_sock->msglen = strlen(ua->UA_sock->msg); - bnet_send(ua->UA_sock); + pm_strcat(sock->msg, _(": is an invalid command\n")); + sock->msglen = strlen(sock->msg); + sock->send(); + sock->signal(BNET_INVALID_CMD); } - return stat; + sock->signal(ok?BNET_CMD_OK:BNET_CMD_FAILED); + return 1; +} + +static bool dot_quit_cmd(UAContext *ua, const char *cmd) +{ + quit_cmd(ua, cmd); + return true; +} + +static bool dot_help_cmd(UAContext *ua, const char *cmd) +{ + qhelp_cmd(ua, cmd); + return true; } -static int getmsgscmd(UAContext *ua, const char *cmd) +static bool getmsgscmd(UAContext *ua, const char *cmd) { if (console_msg_pending) { do_messages(ua, cmd); @@ -136,7 +159,7 @@ static int getmsgscmd(UAContext *ua, const char *cmd) /* * Create segmentation fault */ -static int diecmd(UAContext *ua, const char *cmd) +static bool diecmd(UAContext *ua, const char *cmd) { JCR *jcr = NULL; int a; @@ -144,10 +167,10 @@ static int diecmd(UAContext *ua, const char *cmd) bsendmsg(ua, _("The Director will segment fault.\n")); a = jcr->JobId; /* ref NULL pointer */ jcr->JobId = 1000; /* another ref NULL pointer */ - return 0; + return true; } -static int jobscmd(UAContext *ua, const char *cmd) +static bool jobscmd(UAContext *ua, const char *cmd) { JOB *job = NULL; LockRes(); @@ -157,10 +180,10 @@ static int jobscmd(UAContext *ua, const char *cmd) } } UnlockRes(); - return 1; + return true; } -static int filesetscmd(UAContext *ua, const char *cmd) +static bool filesetscmd(UAContext *ua, const char *cmd) { FILESET *fs = NULL; LockRes(); @@ -170,10 +193,10 @@ static int filesetscmd(UAContext *ua, const char *cmd) } } UnlockRes(); - return 1; + return true; } -static int clientscmd(UAContext *ua, const char *cmd) +static bool clientscmd(UAContext *ua, const char *cmd) { CLIENT *client = NULL; LockRes(); @@ -183,10 +206,10 @@ static int clientscmd(UAContext *ua, const char *cmd) } } UnlockRes(); - return 1; + return true; } -static int msgscmd(UAContext *ua, const char *cmd) +static bool msgscmd(UAContext *ua, const char *cmd) { MSGS *msgs = NULL; LockRes(); @@ -194,10 +217,10 @@ static int msgscmd(UAContext *ua, const char *cmd) bsendmsg(ua, "%s\n", msgs->name()); } UnlockRes(); - return 1; + return true; } -static int poolscmd(UAContext *ua, const char *cmd) +static bool poolscmd(UAContext *ua, const char *cmd) { POOL *pool = NULL; LockRes(); @@ -207,10 +230,10 @@ static int poolscmd(UAContext *ua, const char *cmd) } } UnlockRes(); - return 1; + return true; } -static int storagecmd(UAContext *ua, const char *cmd) +static bool storagecmd(UAContext *ua, const char *cmd) { STORE *store = NULL; LockRes(); @@ -220,18 +243,18 @@ static int storagecmd(UAContext *ua, const char *cmd) } } UnlockRes(); - return 1; + return true; } -static int typescmd(UAContext *ua, const char *cmd) +static bool typescmd(UAContext *ua, const char *cmd) { bsendmsg(ua, "Backup\n"); bsendmsg(ua, "Restore\n"); bsendmsg(ua, "Admin\n"); bsendmsg(ua, "Verify\n"); bsendmsg(ua, "Migrate\n"); - return 1; + return true; } static int client_backups_handler(void *ctx, int num_field, char **row) @@ -242,28 +265,49 @@ static int client_backups_handler(void *ctx, int num_field, char **row) return 0; } -static int backupscmd(UAContext *ua, const char *cmd) +/* + * If this command is called, it tells the director that we + * are a program that wants a sort of API, and hence, + * we will probably suppress certain output, include more + * error codes, and most of all send back a good number + * of new signals that indicate whether or not the command + * succeeded. + */ +static bool api_cmd(UAContext *ua, const char *cmd) +{ + /* Eventually we will probably have several levels or + * capabilities enabled by this. + */ + ua->api = 1; + return true; +} + +/* + * Return the backups for this client + */ +static bool backupscmd(UAContext *ua, const char *cmd) { if (!open_client_db(ua)) { - return 1; + return true; } if (ua->argc != 3 || strcmp(ua->argk[1], "client") != 0 || strcmp(ua->argk[2], "fileset") != 0) { - return 1; + return true; } if (!acl_access_ok(ua, Client_ACL, ua->argv[1]) || !acl_access_ok(ua, FileSet_ACL, ua->argv[2])) { - return 1; + return true; } Mmsg(ua->cmd, client_backups, ua->argv[1], ua->argv[2]); if (!db_sql_query(ua->db, ua->cmd, client_backups_handler, (void *)ua)) { bsendmsg(ua, _("Query failed: %s. ERR=%s\n"), ua->cmd, db_strerror(ua->db)); - return 1; + return true; } - return 1; + return true; } -static int levelscmd(UAContext *ua, const char *cmd) + +static bool levelscmd(UAContext *ua, const char *cmd) { bsendmsg(ua, "Incremental\n"); bsendmsg(ua, "Full\n"); @@ -271,13 +315,13 @@ static int levelscmd(UAContext *ua, const char *cmd) bsendmsg(ua, "Catalog\n"); bsendmsg(ua, "InitCatalog\n"); bsendmsg(ua, "VolumeToCatalog\n"); - return 1; + return true; } /* * Return default values for a job */ -static int defaultscmd(UAContext *ua, const char *cmd) +static bool defaultscmd(UAContext *ua, const char *cmd) { JOB *job; CLIENT *client; @@ -285,13 +329,13 @@ static int defaultscmd(UAContext *ua, const char *cmd) POOL *pool; if (ua->argc != 2 || !ua->argv[1]) { - return 1; + return true; } /* Job defaults */ if (strcmp(ua->argk[1], "job") == 0) { if (!acl_access_ok(ua, Job_ACL, ua->argv[1])) { - return 1; + return true; } job = (JOB *)GetResWithName(R_JOB, ua->argv[1]); if (job) { @@ -313,7 +357,7 @@ static int defaultscmd(UAContext *ua, const char *cmd) /* Client defaults */ else if (strcmp(ua->argk[1], "client") == 0) { if (!acl_access_ok(ua, Client_ACL, ua->argv[1])) { - return 1; + return true; } client = (CLIENT *)GetResWithName(R_CLIENT, ua->argv[1]); if (client) { @@ -329,7 +373,7 @@ static int defaultscmd(UAContext *ua, const char *cmd) /* Storage defaults */ else if (strcmp(ua->argk[1], "storage") == 0) { if (!acl_access_ok(ua, Storage_ACL, ua->argv[1])) { - return 1; + return true; } storage = (STORE *)GetResWithName(R_STORAGE, ua->argv[1]); DEVICE *device; @@ -351,7 +395,7 @@ static int defaultscmd(UAContext *ua, const char *cmd) /* Pool defaults */ else if (strcmp(ua->argk[1], "pool") == 0) { if (!acl_access_ok(ua, Pool_ACL, ua->argv[1])) { - return 1; + return true; } pool = (POOL *)GetResWithName(R_POOL, ua->argv[1]); if (pool) { @@ -372,5 +416,5 @@ static int defaultscmd(UAContext *ua, const char *cmd) bsendmsg(ua, "recycle=%d", pool->Recycle); } } - return 1; + return true; } diff --git a/bacula/src/dird/ua_status.c b/bacula/src/dird/ua_status.c index b5fa59f537..feb3aa790a 100644 --- a/bacula/src/dird/ua_status.c +++ b/bacula/src/dird/ua_status.c @@ -52,7 +52,8 @@ static char DotStatusJob[] = "JobId=%s JobStatus=%c JobErrors=%d\n"; /* * .status command */ -int qstatus_cmd(UAContext *ua, const char *cmd) + +bool dot_status_cmd(UAContext *ua, const char *cmd) { JCR* njcr = NULL; s_last_job* job; @@ -62,7 +63,7 @@ int qstatus_cmd(UAContext *ua, const char *cmd) if ((ua->argc != 3) || (strcasecmp(ua->argk[1], "dir"))) { bsendmsg(ua, "1900 Bad .status command, missing arguments.\n"); - return 1; + return false; } if (strcasecmp(ua->argk[2], "current") == 0) { @@ -85,9 +86,18 @@ int qstatus_cmd(UAContext *ua, const char *cmd) } } else { bsendmsg(ua, "1900 Bad .status command, wrong argument.\n"); - return 1; + return false; } + return true; +} + +/* This is the *old* command handler, so we must return + * 1 or it closes the connection + */ +int qstatus_cmd(UAContext *ua, const char *cmd) +{ + dot_status_cmd(ua, cmd); return 1; } diff --git a/bacula/src/lib/bnet.c b/bacula/src/lib/bnet.c index a5ffb49556..20986a4f2b 100644 --- a/bacula/src/lib/bnet.c +++ b/bacula/src/lib/bnet.c @@ -1113,13 +1113,9 @@ void bnet_restore_blocking (BSOCK *bsock, int flags) * Returns: false on failure * true on success */ -bool bnet_sig(BSOCK * bs, int sig) +bool bnet_sig(BSOCK * bs, int signal) { - bs->msglen = sig; - if (sig == BNET_TERMINATE) { - bs->suppress_error_msgs = true; - } - return bnet_send(bs); + return bs->signal(signal); } /* diff --git a/bacula/src/lib/bsock.c b/bacula/src/lib/bsock.c index 9b1cdf153e..b290240662 100644 --- a/bacula/src/lib/bsock.c +++ b/bacula/src/lib/bsock.c @@ -130,3 +130,15 @@ bool BSOCK::fsend(const char *fmt, ...) } return send(); } + +/* + * Send a signal + */ +bool BSOCK::signal(int signal) +{ + msglen = signal; + if (signal == BNET_TERMINATE) { + suppress_error_msgs = true; + } + return send(); +} diff --git a/bacula/src/lib/bsock.h b/bacula/src/lib/bsock.h index 923771b863..31b57fd534 100644 --- a/bacula/src/lib/bsock.h +++ b/bacula/src/lib/bsock.h @@ -73,6 +73,7 @@ public: /* methods -- in bsock.c */ bool send(); bool fsend(const char*, ...); + bool signal(int signal); }; @@ -89,7 +90,11 @@ enum { BNET_BTIME = -9, /* Send UTC btime */ BNET_BREAK = -10, /* Stop current command -- ctl-c */ BNET_START_SELECT = -11, /* Start of a selection list */ - BNET_END_SELECT = -12 /* End of a select list */ + BNET_END_SELECT = -12, /* End of a select list */ + BNET_INVALID_CMD = -13, /* Invalid command sent */ + BNET_CMD_FAILED = -14, /* Command failed */ + BNET_CMD_OK = -15, /* Command succeeded */ + BNET_CMD_BEGIN = -16 /* Start command execution */ }; #define BNET_SETBUF_READ 1 /* Arg for bnet_set_buffer_size */ @@ -106,9 +111,11 @@ enum { * TLS enabling values. Value is important for comparison, ie: * if (tls_remote_need < BNET_TLS_REQUIRED) { ... } */ -#define BNET_TLS_NONE 0 /* cannot do TLS */ -#define BNET_TLS_OK 1 /* can do, but not required on my end */ -#define BNET_TLS_REQUIRED 2 /* TLS is required */ +enum { + BNET_TLS_NONE = 0, /* cannot do TLS */ + BNET_TLS_OK = 1, /* can do, but not required on my end */ + BNET_TLS_REQUIRED = 2 /* TLS is required */ +}; int32_t read_nbytes(BSOCK * bsock, char *ptr, int32_t nbytes); int32_t write_nbytes(BSOCK * bsock, char *ptr, int32_t nbytes); diff --git a/bacula/src/version.h b/bacula/src/version.h index 3c2c061141..b537b3c126 100644 --- a/bacula/src/version.h +++ b/bacula/src/version.h @@ -4,8 +4,8 @@ #undef VERSION #define VERSION "2.1.4" -#define BDATE "18 February 2007" -#define LSMDATE "18Feb07" +#define BDATE "19 February 2007" +#define LSMDATE "19Feb07" #define PROG_COPYRIGHT "Copyright (C) %d-2007 Free Software Foundation Europe e.V.\n" #define BYEAR "2007" /* year for copyright messages in progs */ diff --git a/bacula/technotes-2.1 b/bacula/technotes-2.1 index 4058629b02..fbfb78eb72 100644 --- a/bacula/technotes-2.1 +++ b/bacula/technotes-2.1 @@ -13,6 +13,10 @@ ebl finish RecyclePool feature ... } TODO: update manual +kes Expand new BSOCK class adding signal() and new BNET signals. +kes Begin enhancing the dot commands adding a new API function + that returns more information and will make it easier + to interface to the Director from a program. 18Feb07 kes Make DEVICE fd private. Its new name is m_fd, and can be obtained with dev->fd() outside the class. This is the -- 2.39.5