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

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