root/tools/ipmiservicelogd.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. getStringExecOutput
  2. getSerialNumber
  3. getProductName
  4. con_usage
  5. usage
  6. ipmi2servicelog
  7. sensor_threshold_event_handler
  8. sensor_discrete_event_handler
  9. sensor_change
  10. entity_change
  11. setup_done
  12. main

   1 /*
   2  * ipmiservicelogd.c
   3  *
   4  * A program that listens to IPMI events and writes them
   5  * out to servicelog.
   6  *
   7  * Author: International Business Machines, IBM
   8  *         Mark Hamzy <hamzy@us.ibm.com>
   9  * Author: Intel Corporation
  10  *         Jeff Zheng <Jeff.Zheng@Intel.com>
  11  *
  12  * Copyright 2009 International Business Machines, IBM
  13  *
  14  *  This program is free software; you can redistribute it and/or
  15  *  modify it under the terms of the GNU Lesser General Public License
  16  *  as published by the Free Software Foundation; either version 2 of
  17  *  the License, or (at your option) any later version.
  18  *
  19  *
  20  *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
  21  *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  22  *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  23  *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  24  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  25  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  26  *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  27  *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  28  *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
  29  *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  *
  31  *  You should have received a copy of the GNU Lesser General Public
  32  *  License along with this program; if not, write to the Free
  33  *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  34  */
  35 
  36 /* gcc -o ipmiservicelogd -g `pkg-config --cflags --libs OpenIPMI OpenIPMIposix servicelog-1` ipmiservicelogd.c
  37  */
  38 /* ./ipmiservicelogd smi 0
  39  */
  40 
  41 #include <stdio.h>
  42 #include <stdlib.h>
  43 #include <string.h>
  44 #include <malloc.h>
  45 #include <sys/types.h>
  46 #include <sys/stat.h>
  47 #include <fcntl.h>
  48 #include <unistd.h>
  49 #include <netdb.h>
  50 #include <ctype.h>
  51 #include <time.h>
  52 #include <sys/wait.h>
  53 #include <sys/utsname.h>
  54 
  55 #include <OpenIPMI/ipmiif.h>
  56 #include <OpenIPMI/ipmi_smi.h>
  57 #include <OpenIPMI/ipmi_err.h>
  58 #include <OpenIPMI/ipmi_auth.h>
  59 #include <OpenIPMI/ipmi_lan.h>
  60 #include <OpenIPMI/ipmi_posix.h>
  61 #include <OpenIPMI/ipmi_fru.h>
  62 
  63 #include <servicelog.h>
  64 
  65 #include <crm/crm.h>
  66 
  67 #define COMPLEX 1
  68 
  69 static os_handler_t *os_hnd;
  70 
  71 char *getStringExecOutput(char *args[]);
  72 char *getSerialNumber(void);
  73 char *getProductName(void);
  74 static void con_usage(const char *name, const char *help, void *cb_data);
  75 static void usage(const char *progname);
  76 void ipmi2servicelog(struct sl_data_bmc *bmc_data);
  77 static int sensor_threshold_event_handler(ipmi_sensor_t * sensor, enum ipmi_event_dir_e dir,
  78                                           enum ipmi_thresh_e threshold,
  79                                           enum ipmi_event_value_dir_e high_low,
  80                                           enum ipmi_value_present_e value_present,
  81                                           unsigned int raw_value, double value, void *cb_data,
  82                                           ipmi_event_t * event);
  83 static int sensor_discrete_event_handler(ipmi_sensor_t * sensor, enum ipmi_event_dir_e dir,
  84                                          int offset, int severity, int prev_severity, void *cb_data,
  85                                          ipmi_event_t * event);
  86 static void sensor_change(enum ipmi_update_e op, ipmi_entity_t * ent, ipmi_sensor_t * sensor,
  87                           void *cb_data);
  88 static void entity_change(enum ipmi_update_e op, ipmi_domain_t * domain, ipmi_entity_t * entity,
  89                           void *cb_data);
  90 void setup_done(ipmi_domain_t * domain, int err, unsigned int conn_num, unsigned int port_num,
  91                 int still_connected, void *user_data);
  92 
  93 char *
  94 getStringExecOutput(char *args[])
     /* [previous][next][first][last][top][bottom][index][help] */
  95 {
  96     int rc;
  97     pid_t pid;
  98     int pipefd[2];
  99 
 100     rc = pipe2(pipefd, 0);
 101 
 102     if (rc == -1) {
 103 
 104         crm_err("Error: pipe errno = %d", errno);
 105 
 106         return NULL;
 107     }
 108 
 109     pid = fork();
 110 
 111     if (0 < pid) {
 112 
 113         /* Parent */
 114         int childExitStatus;
 115         char serialNumber[256];
 116         ssize_t sizeRead;
 117 
 118         /* close write end of pipe */
 119         rc = close(pipefd[1]);
 120         if (rc == -1) {
 121             crm_err("Error: parent close (pipefd[1]) = %d", errno);
 122         }
 123 
 124         /* make 0 same as read-from end of pipe */
 125         rc = dup2(pipefd[0], 0);
 126         if (rc == -1) {
 127             crm_err("Error: parent dup2 (pipefd[0]) = %d", errno);
 128         }
 129 
 130         /* close excess fildes */
 131         rc = close(pipefd[0]);
 132         if (rc == -1) {
 133             crm_err("Error: parent close (pipefd[0]) = %d", errno);
 134         }
 135 
 136         waitpid(pid, &childExitStatus, 0);
 137 
 138         if (!WIFEXITED(childExitStatus)) {
 139 
 140             crm_err("waitpid() exited with an error: status = %d", WEXITSTATUS(childExitStatus));
 141 
 142             return NULL;
 143 
 144         } else if (WIFSIGNALED(childExitStatus)) {
 145 
 146             crm_err("waitpid() exited due to a signal = %d", WTERMSIG(childExitStatus));
 147 
 148             return NULL;
 149 
 150         }
 151 
 152         memset(serialNumber, 0, sizeof(serialNumber));
 153 
 154         sizeRead = read(0, serialNumber, sizeof(serialNumber) - 1);
 155 
 156         if (sizeRead > 0) {
 157 
 158             char *end = serialNumber + strlen(serialNumber) - 1;
 159             char *retSerialNumber = NULL;
 160 
 161             while (end > serialNumber
 162                    && (*end == '\n' || *end == '\r' || *end == '\t' || *end == ' ')
 163                 ) {
 164                 *end = '\0';
 165                 end--;
 166             }
 167 
 168             retSerialNumber = malloc(strlen(serialNumber) + 1);
 169 
 170             if (retSerialNumber) {
 171 
 172                 strcpy(retSerialNumber, serialNumber);
 173 
 174             }
 175 
 176             return retSerialNumber;
 177 
 178         }
 179 
 180         return NULL;
 181 
 182     } else if (pid == 0) {
 183 
 184         /* Child */
 185 
 186         /* close read end of pipe */
 187         rc = close(pipefd[0]);
 188         if (rc == -1) {
 189             crm_err("Error: child close (pipefd[0]) = %d", errno);
 190         }
 191 
 192         /* make 1 same as write-to end of pipe */
 193         rc = dup2(pipefd[1], 1);
 194         if (rc == -1) {
 195             crm_err("Error: child dup2 (pipefd[1]) = %d", errno);
 196         }
 197 
 198         /* close excess fildes */
 199         rc = close(pipefd[1]);
 200         if (rc == -1) {
 201             crm_err("Error: child close (pipefd[1]) = %d", errno);
 202         }
 203 
 204         rc = execvp(args[0], args);
 205 
 206         if (rc == -1) {
 207             crm_err("Error: child execvp = %d", errno);
 208         }
 209 
 210         /* In case of error */
 211         return NULL;
 212 
 213     } else {
 214 
 215         /* Error */
 216         crm_err("fork errno = %d", errno);
 217 
 218         return NULL;
 219     }
 220 
 221     return NULL;
 222 }
 223 
 224 char *
 225 getSerialNumber(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 226 {
 227     char *dmiArgs[] = {
 228         "dmidecode",
 229         "--string",
 230         "system-serial-number",
 231         NULL
 232     };
 233 
 234     return getStringExecOutput(dmiArgs);
 235 }
 236 
 237 char *
 238 getProductName(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 239 {
 240     char *dmiArgs[] = {
 241         "dmidecode",
 242         "--string",
 243         "system-product-name",
 244         NULL
 245     };
 246 
 247     return getStringExecOutput(dmiArgs);
 248 }
 249 
 250 static void
 251 con_usage(const char *name, const char *help, void *cb_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 252 {
 253     printf("\n%s%s", name, help);
 254 }
 255 
 256 static void
 257 usage(const char *progname)
     /* [previous][next][first][last][top][bottom][index][help] */
 258 {
 259     printf("Usage:\n");
 260     printf(" %s <con_parms>\n", progname);
 261     printf(" Where <con_parms> is one of:");
 262     ipmi_parse_args_iter_help(con_usage, NULL);
 263 }
 264 
 265 void
 266 ipmi2servicelog(struct sl_data_bmc *bmc_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 267 {
 268     servicelog *slog = NULL;
 269     struct sl_event sl_event;
 270     uint64_t new_id = 0;
 271     struct utsname name;
 272     char *serial_number = NULL;
 273     char *product_name = NULL;
 274     int rc;
 275 
 276     if (uname(&name) == -1) {
 277         crm_err("Error: uname failed");
 278         return;
 279     }
 280 
 281     rc = servicelog_open(&slog, 0);     /* flags is one of SL_FLAG_xxx */
 282 
 283     if (!slog) {
 284         crm_err("Error: servicelog_open failed, rc = %d", rc);
 285         return;
 286     }
 287 
 288     serial_number = getSerialNumber();
 289     if (serial_number) {
 290         if (strlen(serial_number) > 20) {
 291             serial_number[20] = '\0';
 292         }
 293     }
 294 
 295     product_name = getProductName();
 296     if (product_name) {
 297         if (strlen(product_name) > 20) {
 298             product_name[20] = '\0';
 299         }
 300     }
 301 
 302     memset(&sl_event, 0, sizeof(sl_event));
 303 
 304 /* *INDENT-OFF* */
 305     sl_event.next             = NULL;                 /* only used if in a linked list */
 306     sl_event.id               = 0;                    /* unique identifier - filled in by API call */
 307     sl_event.time_logged      = time (NULL);
 308     sl_event.time_event       = time (NULL);
 309     sl_event.time_last_update = time (NULL);
 310     sl_event.type             = SL_TYPE_BMC;          /* one of SL_TYPE_* */
 311     sl_event.severity         = SL_SEV_WARNING;       /* one of SL_SEV_* */
 312     sl_event.platform         = name.machine;         /* ppc64, etc */
 313     sl_event.machine_serial   = serial_number;
 314     sl_event.machine_model    = product_name;         /* it may not have the serial # within the first 20 chars */
 315     sl_event.nodename         = name.nodename;
 316     sl_event.refcode          = "ipmi";
 317     sl_event.description      = "ipmi event";
 318     sl_event.serviceable      = 1;                    /* 1 or 0 */
 319     sl_event.predictive       = 0;                    /* 1 or 0 */
 320     sl_event.disposition      = SL_DISP_RECOVERABLE;  /* one of SL_DISP_* */
 321     sl_event.call_home_status = SL_CALLHOME_NONE;     /* one of SL_CALLHOME_*,
 322                                                       only valid if serviceable */
 323     sl_event.closed           = 1;                    /* 1 or 0, only valid if serviceable */
 324     sl_event.repair           = 0;                    /* id of repairing repair_action */
 325     sl_event.callouts         = NULL;
 326     sl_event.raw_data_len     = 0;
 327     sl_event.raw_data         = NULL;
 328     sl_event.addl_data        = &bmc_data;            /* pointer to an sl_data_* struct */
 329 /* *INDENT-ON* */
 330 
 331     rc = servicelog_event_log(slog, &sl_event, &new_id);
 332 
 333     if (rc != 0) {
 334         crm_err("Error: servicelog_event_log, rc = %d (\"%s\")", rc, servicelog_error(slog));
 335     } else {
 336         crm_debug("Sending to servicelog database");
 337     }
 338 
 339     free(serial_number);
 340     free(product_name);
 341 
 342     servicelog_close(slog);
 343 }
 344 
 345 static int
 346 sensor_threshold_event_handler(ipmi_sensor_t * sensor,
     /* [previous][next][first][last][top][bottom][index][help] */
 347                                enum ipmi_event_dir_e dir,
 348                                enum ipmi_thresh_e threshold,
 349                                enum ipmi_event_value_dir_e high_low,
 350                                enum ipmi_value_present_e value_present,
 351                                unsigned int raw_value,
 352                                double value, void *cb_data, ipmi_event_t * event)
 353 {
 354     ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor);
 355     int id, instance;
 356     char name[IPMI_ENTITY_NAME_LEN];
 357     struct sl_data_bmc bmc_data;
 358     uint32_t sel_id;
 359     uint32_t sel_type;
 360     uint16_t generator;
 361     uint8_t version;
 362     uint8_t sensor_type;
 363     int sensor_lun;
 364     int sensor_number;
 365     uint8_t event_class;
 366     uint8_t event_type;
 367     int direction;
 368 
 369     id = ipmi_entity_get_entity_id(ent);
 370     instance = ipmi_entity_get_entity_instance(ent);
 371     ipmi_sensor_get_id(sensor, name, sizeof(name));
 372 
 373     ipmi_sensor_get_num(sensor, &sensor_lun, &sensor_number);
 374 
 375     sel_id = ipmi_entity_get_entity_id(ent);
 376     sel_type = ipmi_entity_get_type(ent);
 377     generator = ipmi_entity_get_slave_address(ent) | (sensor_lun << 5); /* LUN (2 bits) | SLAVE ADDRESS (5 bits) */
 378     version = 0x04;
 379     sensor_type = ipmi_sensor_get_sensor_type(sensor);
 380     event_class = 0;            /* @TBD - where does this come from? */
 381     event_type = ipmi_event_get_type(event);
 382     direction = dir;
 383 
 384     memset(&bmc_data, 0, sizeof(bmc_data));
 385 
 386     bmc_data.sel_id = sel_id;
 387     bmc_data.sel_type = sel_type;
 388     bmc_data.generator = generator;
 389     bmc_data.version = version;
 390     bmc_data.sensor_type = sensor_type;
 391     bmc_data.sensor_number = sensor_number;
 392     bmc_data.event_class = event_class;
 393     bmc_data.event_type = event_type;
 394     bmc_data.direction = direction;
 395 
 396     crm_debug("Writing bmc_data (%08x, %08x, %04x, %02x, %02x, %02x, %02x, %02x, %d)",
 397               bmc_data.sel_id,
 398               bmc_data.sel_type,
 399               bmc_data.generator,
 400               bmc_data.version,
 401               bmc_data.sensor_type,
 402               bmc_data.sensor_number,
 403               bmc_data.event_class, bmc_data.event_type, bmc_data.direction);
 404 
 405     ipmi2servicelog(&bmc_data);
 406 
 407     /* This passes the event on to the main event handler, which does
 408        not exist in this program. */
 409     return IPMI_EVENT_NOT_HANDLED;
 410 }
 411 
 412 static int
 413 sensor_discrete_event_handler(ipmi_sensor_t * sensor,
     /* [previous][next][first][last][top][bottom][index][help] */
 414                               enum ipmi_event_dir_e dir,
 415                               int offset,
 416                               int severity, int prev_severity, void *cb_data, ipmi_event_t * event)
 417 {
 418     ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor);
 419     int id, instance;
 420     char name[IPMI_ENTITY_NAME_LEN];
 421     struct sl_data_bmc bmc_data;
 422     uint32_t sel_id;
 423     uint32_t sel_type;
 424     uint16_t generator;
 425     uint8_t version;
 426     uint8_t sensor_type;
 427     int sensor_lun;
 428     int sensor_number;
 429     uint8_t event_class;
 430     uint8_t event_type;
 431     int direction;
 432 
 433     id = ipmi_entity_get_entity_id(ent);
 434     instance = ipmi_entity_get_entity_instance(ent);
 435     ipmi_sensor_get_id(sensor, name, sizeof(name));
 436 
 437     sel_id = ipmi_entity_get_entity_id(ent);
 438     sel_type = ipmi_entity_get_type(ent);
 439     generator = ipmi_entity_get_slave_address(ent) | (sensor_lun << 5); /* LUN (2 bits) | SLAVE ADDRESS (5 bits) */
 440     version = 0x04;
 441     sensor_type = ipmi_sensor_get_sensor_type(sensor);
 442 
 443     ipmi_sensor_get_num(sensor, &sensor_lun, &sensor_number);
 444 
 445     event_class = 0;            /* @TBD - where does this come from? */
 446     event_type = ipmi_event_get_type(event);
 447     direction = dir;
 448 
 449     memset(&bmc_data, 0, sizeof(bmc_data));
 450 
 451     bmc_data.sel_id = sel_id;
 452     bmc_data.sel_type = sel_type;
 453     bmc_data.generator = generator;
 454     bmc_data.version = version;
 455     bmc_data.sensor_type = sensor_type;
 456     bmc_data.sensor_number = sensor_number;
 457     bmc_data.event_class = event_class;
 458     bmc_data.event_type = event_type;
 459     bmc_data.direction = direction;
 460 
 461     crm_debug("Writing bmc_data (%08x, %08x, %04x, %02x, %02x, %02x, %02x, %02x, %d)",
 462               bmc_data.sel_id,
 463               bmc_data.sel_type,
 464               bmc_data.generator,
 465               bmc_data.version,
 466               bmc_data.sensor_type,
 467               bmc_data.sensor_number,
 468               bmc_data.event_class, bmc_data.event_type, bmc_data.direction);
 469 
 470     ipmi2servicelog(&bmc_data);
 471 
 472     /* This passes the event on to the main event handler, which does
 473        not exist in this program. */
 474     return IPMI_EVENT_NOT_HANDLED;
 475 }
 476 
 477 /* Whenever the status of a sensor changes, the function is called
 478    We display the information of the sensor if we find a new sensor
 479 */
 480 static void
 481 sensor_change(enum ipmi_update_e op, ipmi_entity_t * ent, ipmi_sensor_t * sensor, void *cb_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 482 {
 483     int rv;
 484 
 485     if (op == IPMI_ADDED) {
 486         if (ipmi_sensor_get_event_reading_type(sensor) == IPMI_EVENT_READING_TYPE_THRESHOLD)
 487             rv = ipmi_sensor_add_threshold_event_handler(sensor,
 488                                                          sensor_threshold_event_handler, NULL);
 489         else
 490             rv = ipmi_sensor_add_discrete_event_handler(sensor,
 491                                                         sensor_discrete_event_handler, NULL);
 492         if (rv)
 493             crm_err("Unable to add the sensor event handler: %x", rv);
 494     }
 495 }
 496 
 497 /* Whenever the status of an entity changes, the function is called
 498    When a new entity is created, we search all sensors that belong 
 499    to the entity */
 500 static void
 501 entity_change(enum ipmi_update_e op, ipmi_domain_t * domain, ipmi_entity_t * entity, void *cb_data)
     /* [previous][next][first][last][top][bottom][index][help] */
 502 {
 503     int rv;
 504     int id, instance;
 505 
 506     id = ipmi_entity_get_entity_id(entity);
 507     instance = ipmi_entity_get_entity_instance(entity);
 508     if (op == IPMI_ADDED) {
 509         /* Register callback so that when the status of a
 510            sensor changes, sensor_change is called */
 511         rv = ipmi_entity_add_sensor_update_handler(entity, sensor_change, entity);
 512         if (rv) {
 513             crm_err("ipmi_entity_set_sensor_update_handler: 0x%x", rv);
 514             crm_exit(pcmk_err_generic);
 515         }
 516     }
 517 }
 518 
 519 /* After we have established connection to domain, this function get called
 520    At this time, we can do whatever things we want to do. Herr we want to
 521    search all entities in the system */
 522 void
 523 setup_done(ipmi_domain_t * domain,
     /* [previous][next][first][last][top][bottom][index][help] */
 524            int err,
 525            unsigned int conn_num, unsigned int port_num, int still_connected, void *user_data)
 526 {
 527     int rv;
 528 
 529     /* Register a callback functin entity_change. When a new entities 
 530        is created, entity_change is called */
 531     rv = ipmi_domain_add_entity_update_handler(domain, entity_change, domain);
 532     if (rv) {
 533         crm_err("ipmi_domain_add_entity_update_handler return error: %d", rv);
 534         return;
 535     }
 536 
 537 }
 538 
 539 int
 540 main(int argc, char *argv[])
     /* [previous][next][first][last][top][bottom][index][help] */
 541 {
 542     int rv;
 543     int curr_arg = 1;
 544     ipmi_args_t *args;
 545     ipmi_con_t *con;
 546 
 547     /* OS handler allocated first. */
 548     os_hnd = ipmi_posix_setup_os_handler();
 549     if (!os_hnd) {
 550         crm_err("ipmi_smi_setup_con: Unable to allocate os handler");
 551         crm_exit(pcmk_err_generic);
 552     }
 553 
 554     /* Initialize the OpenIPMI library. */
 555     ipmi_init(os_hnd);
 556 
 557 #ifdef COMPLEX
 558     rv = ipmi_parse_args2(&curr_arg, argc, argv, &args);
 559     if (rv) {
 560         crm_err("Error parsing command arguments, argument %d: %s", curr_arg, strerror(rv));
 561         usage(argv[0]);
 562         crm_exit(pcmk_err_generic);
 563     }
 564 #endif
 565 
 566     crm_make_daemon("ipmiservicelogd", TRUE, "/var/run/ipmiservicelogd.pid0");
 567 
 568     crm_log_init("ipmiservicelogd", LOG_INFO, FALSE, TRUE, argc, argv);
 569 
 570 #ifdef COMPLEX
 571     rv = ipmi_args_setup_con(args, os_hnd, NULL, &con);
 572     if (rv) {
 573         crm_err("ipmi_ip_setup_con: %s", strerror(rv));
 574         crm_err("Error: Is IPMI configured correctly?");
 575         crm_exit(pcmk_err_generic);
 576     }
 577 #else
 578     /* If all you need is an SMI connection, this is all the code you
 579        need. */
 580     /* Establish connections to domain through system interface.  This
 581        function connect domain, selector and OS handler together.
 582        When there is response message from domain, the status of file
 583        descriptor in selector is changed and predefined callback is
 584        called. After the connection is established, setup_done will be
 585        called. */
 586     rv = ipmi_smi_setup_con(0, os_hnd, NULL, &con);
 587     if (rv) {
 588         crm_err("ipmi_smi_setup_con: %s", strerror(rv));
 589         crm_err("Error: Is IPMI configured correctly?");
 590         crm_exit(pcmk_err_generic);
 591     }
 592 #endif
 593 
 594     rv = ipmi_open_domain("", &con, 1, setup_done, NULL, NULL, NULL, NULL, 0, NULL);
 595     if (rv) {
 596         crm_err("ipmi_init_domain: %s", strerror(rv));
 597         crm_exit(pcmk_err_generic);
 598     }
 599 
 600     /* This is the main loop of the event-driven program. 
 601        Try <CTRL-C> to exit the program */
 602     /* Let the selector code run the select loop. */
 603     os_hnd->operation_loop(os_hnd);
 604 
 605     /* Technically, we can't get here, but this is an example. */
 606     os_hnd->free_os_handler(os_hnd);
 607 }

/* [previous][next][first][last][top][bottom][index][help] */