From e9be59e1502a41bab9891d96d753102a7dafef0b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 08:40:13 +0200 Subject: Adding upstream version 1.0.12. Signed-off-by: Daniel Baumann --- .hg_archival.txt | 2 + .hgignore | 92 + .hgsigs | 4 + .hgtags | 68 + AUTHORS | 19 + COPYING | 339 ++ COPYING.LIB | 504 +++ ChangeLog | 262 ++ GNUmakefile | 47 + Makefile.am | 39 + NEWS | 1 + README | 1 + autogen.sh | 193 + cluster-glue-fedora.spec | 249 ++ cluster-glue-suse.spec | 307 ++ config/Makefile.am | 19 + config/byteorder_test.c | 15 + configure.ac | 1439 +++++++ doc/Makefile.am | 53 + doc/ha_logd.xml.in | 134 + doc/ha_logger.xml.in | 110 + doc/hb_report.8.txt | 478 +++ doc/meatclient.xml.in | 77 + doc/stonith.xml.in | 315 ++ doc/stonith/Makefile.am | 37 + doc/stonith/README.bladehpi | 101 + doc/stonith/README.cyclades | 61 + doc/stonith/README.drac3 | 18 + doc/stonith/README.dracmc | 87 + doc/stonith/README.external | 90 + doc/stonith/README.ibmrsa | 9 + doc/stonith/README.ibmrsa-telnet | 55 + doc/stonith/README.ipmilan | 131 + doc/stonith/README.ippower9258 | 68 + doc/stonith/README.meatware | 26 + doc/stonith/README.rackpdu | 21 + doc/stonith/README.rcd_serial | 186 + doc/stonith/README.riloe | 36 + doc/stonith/README.vacm | 40 + doc/stonith/README.vcenter | 90 + doc/stonith/README.wti_mpc | 85 + doc/stonith/README_kdumpcheck.txt | 151 + hb_report/Makefile.am | 26 + hb_report/ha_cf_support.sh | 83 + hb_report/hb_report.in | 1445 +++++++ hb_report/openais_conf_support.sh | 97 + hb_report/utillib.sh | 752 ++++ include/Makefile.am | 25 + include/clplumbing/GSource.h | 236 ++ include/clplumbing/GSource_internal.h | 111 + include/clplumbing/Gmain_timeout.h | 44 + include/clplumbing/Makefile.am | 59 + include/clplumbing/apphb_cs.h | 75 + include/clplumbing/base64.h | 50 + include/clplumbing/cl_log.h | 99 + include/clplumbing/cl_misc.h | 31 + include/clplumbing/cl_pidfile.h | 25 + include/clplumbing/cl_plugin.h | 29 + include/clplumbing/cl_poll.h | 46 + include/clplumbing/cl_quorum.h | 44 + include/clplumbing/cl_quorumd.h | 48 + include/clplumbing/cl_random.h | 81 + include/clplumbing/cl_reboot.h | 6 + include/clplumbing/cl_signal.h | 91 + include/clplumbing/cl_syslog.h | 32 + include/clplumbing/cl_tiebreaker.h | 35 + include/clplumbing/cl_uuid.h | 40 + include/clplumbing/coredumps.h | 36 + include/clplumbing/cpulimits.h | 66 + include/clplumbing/ipc.h | 788 ++++ include/clplumbing/loggingdaemon.h | 32 + include/clplumbing/longclock.h | 143 + include/clplumbing/lsb_exitcodes.h | 92 + include/clplumbing/md5.h | 49 + include/clplumbing/mkstemp_mode.h | 34 + include/clplumbing/netstring.h | 48 + include/clplumbing/proctrack.h | 120 + include/clplumbing/realtime.h | 65 + include/clplumbing/replytrack.h | 208 + include/clplumbing/setproctitle.h | 64 + include/clplumbing/timers.h | 23 + include/clplumbing/uids.h | 32 + include/compress.h | 50 + include/glue_config.h.in | 104 + include/ha_msg.h | 464 +++ include/lha_internal.h | 182 + include/lrm/Makefile.am | 22 + include/lrm/lrm_api.h | 455 +++ include/lrm/lrm_msg.h | 160 + include/lrm/racommon.h | 29 + include/lrm/raexec.h | 157 + include/pils/Makefile.am | 25 + include/pils/generic.h | 118 + include/pils/interface.h | 159 + include/pils/plugin.h.in | 736 ++++ include/replace_uuid.h | 50 + include/stonith/Makefile.am | 25 + include/stonith/expect.h | 61 + include/stonith/st_ttylock.h | 21 + include/stonith/stonith.h | 187 + include/stonith/stonith_plugin.h | 125 + lib/Makefile.am | 20 + lib/clplumbing/GSource.c | 1864 +++++++++ lib/clplumbing/Makefile.am | 99 + lib/clplumbing/base64.c | 422 ++ lib/clplumbing/base64_md5_test.c | 113 + lib/clplumbing/cl_compress.c | 500 +++ lib/clplumbing/cl_log.c | 1261 ++++++ lib/clplumbing/cl_malloc.c | 1044 +++++ lib/clplumbing/cl_misc.c | 179 + lib/clplumbing/cl_msg.c | 2537 ++++++++++++ lib/clplumbing/cl_msg_types.c | 1736 +++++++++ lib/clplumbing/cl_netstring.c | 570 +++ lib/clplumbing/cl_pidfile.c | 294 ++ lib/clplumbing/cl_plugin.c | 140 + lib/clplumbing/cl_poll.c | 809 ++++ lib/clplumbing/cl_random.c | 164 + lib/clplumbing/cl_reboot.c | 59 + lib/clplumbing/cl_signal.c | 209 + lib/clplumbing/cl_syslog.c | 149 + lib/clplumbing/cl_uuid.c | 180 + lib/clplumbing/coredumps.c | 309 ++ lib/clplumbing/cpulimits.c | 219 ++ lib/clplumbing/ipcsocket.c | 2767 +++++++++++++ lib/clplumbing/ipctest.c | 1377 +++++++ lib/clplumbing/ipctransient.h | 50 + lib/clplumbing/ipctransientclient.c | 222 ++ lib/clplumbing/ipctransientlib.c | 97 + lib/clplumbing/ipctransientserver.c | 204 + lib/clplumbing/longclock.c | 275 ++ lib/clplumbing/md5.c | 335 ++ lib/clplumbing/mkstemp_mode.c | 56 + lib/clplumbing/netstring_test.c | 255 ++ lib/clplumbing/ocf_ipc.c | 594 +++ lib/clplumbing/proctrack.c | 515 +++ lib/clplumbing/realtime.c | 354 ++ lib/clplumbing/replytrack.c | 643 ++++ lib/clplumbing/setproctitle.c | 235 ++ lib/clplumbing/timers.c | 119 + lib/clplumbing/transient-test.sh | 120 + lib/clplumbing/uids.c | 140 + lib/lrm/Makefile.am | 36 + lib/lrm/clientlib.c | 1612 ++++++++ lib/lrm/lrm_msg.c | 212 + lib/lrm/racommon.c | 178 + lib/pils/Makefile.am | 57 + lib/pils/main.c | 122 + lib/pils/pils.c | 2152 +++++++++++ lib/pils/test.c | 107 + lib/plugins/InterfaceMgr/HBauth.c | 171 + lib/plugins/InterfaceMgr/Makefile.am | 33 + lib/plugins/InterfaceMgr/generic.c | 452 +++ lib/plugins/Makefile.am | 20 + lib/plugins/compress/Makefile.am | 52 + lib/plugins/compress/bz2.c | 142 + lib/plugins/compress/zlib.c | 135 + lib/plugins/lrm/Makefile.am | 58 + lib/plugins/lrm/dbus/Makefile.am | 16 + .../lrm/dbus/com.ubuntu.Upstart.Instance.xml | 45 + lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml | 71 + lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml | 57 + lib/plugins/lrm/raexechb.c | 416 ++ lib/plugins/lrm/raexeclsb.c | 609 +++ lib/plugins/lrm/raexecocf.c | 496 +++ lib/plugins/lrm/raexecupstart.c | 222 ++ lib/plugins/lrm/upstart-dbus.c | 406 ++ lib/plugins/lrm/upstart-dbus.h | 36 + lib/plugins/stonith/Makefile.am | 216 ++ lib/plugins/stonith/apcmaster.c | 822 ++++ lib/plugins/stonith/apcmastersnmp.c | 890 +++++ lib/plugins/stonith/apcmastersnmp.cfg.example | 39 + lib/plugins/stonith/apcsmart.c | 1028 +++++ lib/plugins/stonith/apcsmart.cfg.example | 1 + lib/plugins/stonith/baytech.c | 924 +++++ lib/plugins/stonith/bladehpi.c | 1101 ++++++ lib/plugins/stonith/cyclades.c | 650 ++++ lib/plugins/stonith/drac3.c | 359 ++ lib/plugins/stonith/drac3_command.c | 342 ++ lib/plugins/stonith/drac3_command.h | 29 + lib/plugins/stonith/drac3_hash.c | 106 + lib/plugins/stonith/drac3_hash.h | 28 + lib/plugins/stonith/external.c | 868 +++++ lib/plugins/stonith/external/Makefile.am | 33 + lib/plugins/stonith/external/drac5.in | 113 + lib/plugins/stonith/external/dracmc-telnet | 377 ++ lib/plugins/stonith/external/hetzner | 139 + lib/plugins/stonith/external/hmchttp | 218 ++ lib/plugins/stonith/external/ibmrsa | 157 + lib/plugins/stonith/external/ibmrsa-telnet | 320 ++ lib/plugins/stonith/external/ipmi | 276 ++ lib/plugins/stonith/external/ippower9258.in | 316 ++ lib/plugins/stonith/external/kdumpcheck.in | 274 ++ lib/plugins/stonith/external/libvirt | 298 ++ lib/plugins/stonith/external/nut | 302 ++ lib/plugins/stonith/external/rackpdu | 280 ++ lib/plugins/stonith/external/riloe | 530 +++ lib/plugins/stonith/external/ssh.in | 176 + lib/plugins/stonith/external/vcenter | 280 ++ lib/plugins/stonith/external/vmware | 216 ++ lib/plugins/stonith/external/xen0 | 253 ++ .../stonith/external/xen0-ha-dom0-stonith-helper | 72 + lib/plugins/stonith/external/xen0-ha.in | 96 + lib/plugins/stonith/ibmhmc.c | 1261 ++++++ lib/plugins/stonith/ipmi_os_handler.c | 257 ++ lib/plugins/stonith/ipmilan.c | 587 +++ lib/plugins/stonith/ipmilan.h | 41 + lib/plugins/stonith/ipmilan_command.c | 399 ++ lib/plugins/stonith/ipmilan_test.c | 63 + lib/plugins/stonith/meatware.c | 351 ++ lib/plugins/stonith/null.c | 260 ++ lib/plugins/stonith/nw_rpc100s.c | 779 ++++ lib/plugins/stonith/rcd_serial.c | 602 +++ lib/plugins/stonith/rhcs.c | 1035 +++++ lib/plugins/stonith/ribcl.py.in | 101 + lib/plugins/stonith/riloe.c | 338 ++ lib/plugins/stonith/rps10.c | 1070 ++++++ lib/plugins/stonith/ssh.c | 351 ++ lib/plugins/stonith/stonith_config_xml.h | 157 + lib/plugins/stonith/stonith_expect_helpers.h | 120 + lib/plugins/stonith/stonith_plugin_common.h | 127 + lib/plugins/stonith/stonith_signal.h | 68 + lib/plugins/stonith/suicide.c | 274 ++ lib/plugins/stonith/vacm.c | 485 +++ lib/plugins/stonith/wti_mpc.c | 856 +++++ lib/plugins/stonith/wti_nps.c | 813 ++++ lib/stonith/Makefile.am | 54 + lib/stonith/README | 31 + lib/stonith/expect.c | 539 +++ lib/stonith/ha_log.sh | 114 + lib/stonith/main.c | 727 ++++ lib/stonith/meatclient.c | 152 + lib/stonith/st_ttylock.c | 225 ++ lib/stonith/stonith.c | 636 +++ logd/Makefile.am | 56 + logd/ha_logd.c | 1085 ++++++ logd/ha_logger.1 | 38 + logd/ha_logger.c | 142 + logd/logd.cf | 66 + logd/logd.in | 101 + logd/logd.service.in | 13 + logd/logtest.c | 129 + lrm/Makefile.am | 20 + lrm/admin/Makefile.am | 40 + lrm/admin/cibsecret.in | 350 ++ lrm/admin/lrmadmin.c | 1129 ++++++ lrm/admin/lrmadmin.txt | 60 + lrm/lrmd/Makefile.am | 42 + lrm/lrmd/audit.c | 191 + lrm/lrmd/cib_secrets.c | 205 + lrm/lrmd/lrmd.c | 4053 ++++++++++++++++++++ lrm/lrmd/lrmd.h | 282 ++ lrm/lrmd/lrmd_fdecl.h | 111 + lrm/test/LRMBasicSanityCheck.in | 55 + lrm/test/Makefile.am | 48 + lrm/test/README.regression | 164 + lrm/test/apitest.c | 317 ++ lrm/test/apitest.exp | 122 + lrm/test/callbacktest.c | 204 + lrm/test/defaults | 9 + lrm/test/descriptions | 55 + lrm/test/evaltest.sh | 171 + lrm/test/language | 16 + lrm/test/lrmadmin-interface | 43 + lrm/test/lrmregtest-lsb | 54 + lrm/test/lrmregtest.in | 220 ++ lrm/test/plugintest.c | 84 + lrm/test/regression.sh.in | 248 ++ lrm/test/testcases/BSC | 4 + lrm/test/testcases/Makefile.am | 27 + lrm/test/testcases/basicset | 6 + lrm/test/testcases/common.filter | 27 + lrm/test/testcases/flood | 19 + lrm/test/testcases/flood.exp | 1354 +++++++ lrm/test/testcases/metadata | 29 + lrm/test/testcases/metadata.exp | 31 + lrm/test/testcases/ra-list.sh | 12 + lrm/test/testcases/rscexec | 48 + lrm/test/testcases/rscexec.exp | 117 + lrm/test/testcases/rscmgmt | 29 + lrm/test/testcases/rscmgmt.exp | 74 + lrm/test/testcases/rscmgmt.log_filter | 13 + lrm/test/testcases/serialize | 33 + lrm/test/testcases/serialize.exp | 100 + lrm/test/testcases/stonith | 2 + lrm/test/testcases/stonith.exp | 2 + lrm/test/testcases/xmllint.sh | 20 + replace/Makefile.am | 29 + replace/NoSuchFunctionName.c | 31 + replace/alphasort.c | 53 + replace/daemon.c | 83 + replace/inet_pton.c | 245 ++ replace/scandir.c | 236 ++ replace/setenv.c | 50 + replace/strerror.c | 37 + replace/strlcat.c | 33 + replace/strlcpy.c | 32 + replace/strndup.c | 38 + replace/strnlen.c | 31 + replace/unsetenv.c | 51 + replace/uuid_parse.c | 519 +++ 300 files changed, 82087 insertions(+) create mode 100644 .hg_archival.txt create mode 100644 .hgignore create mode 100644 .hgsigs create mode 100644 .hgtags create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 COPYING.LIB create mode 100644 ChangeLog create mode 100644 GNUmakefile create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100755 autogen.sh create mode 100644 cluster-glue-fedora.spec create mode 100644 cluster-glue-suse.spec create mode 100644 config/Makefile.am create mode 100644 config/byteorder_test.c create mode 100644 configure.ac create mode 100644 doc/Makefile.am create mode 100644 doc/ha_logd.xml.in create mode 100644 doc/ha_logger.xml.in create mode 100644 doc/hb_report.8.txt create mode 100644 doc/meatclient.xml.in create mode 100644 doc/stonith.xml.in create mode 100644 doc/stonith/Makefile.am create mode 100644 doc/stonith/README.bladehpi create mode 100644 doc/stonith/README.cyclades create mode 100644 doc/stonith/README.drac3 create mode 100644 doc/stonith/README.dracmc create mode 100644 doc/stonith/README.external create mode 100644 doc/stonith/README.ibmrsa create mode 100644 doc/stonith/README.ibmrsa-telnet create mode 100644 doc/stonith/README.ipmilan create mode 100644 doc/stonith/README.ippower9258 create mode 100644 doc/stonith/README.meatware create mode 100644 doc/stonith/README.rackpdu create mode 100644 doc/stonith/README.rcd_serial create mode 100644 doc/stonith/README.riloe create mode 100644 doc/stonith/README.vacm create mode 100644 doc/stonith/README.vcenter create mode 100644 doc/stonith/README.wti_mpc create mode 100644 doc/stonith/README_kdumpcheck.txt create mode 100644 hb_report/Makefile.am create mode 100644 hb_report/ha_cf_support.sh create mode 100755 hb_report/hb_report.in create mode 100644 hb_report/openais_conf_support.sh create mode 100644 hb_report/utillib.sh create mode 100644 include/Makefile.am create mode 100644 include/clplumbing/GSource.h create mode 100644 include/clplumbing/GSource_internal.h create mode 100644 include/clplumbing/Gmain_timeout.h create mode 100644 include/clplumbing/Makefile.am create mode 100644 include/clplumbing/apphb_cs.h create mode 100644 include/clplumbing/base64.h create mode 100644 include/clplumbing/cl_log.h create mode 100644 include/clplumbing/cl_misc.h create mode 100644 include/clplumbing/cl_pidfile.h create mode 100644 include/clplumbing/cl_plugin.h create mode 100644 include/clplumbing/cl_poll.h create mode 100644 include/clplumbing/cl_quorum.h create mode 100644 include/clplumbing/cl_quorumd.h create mode 100644 include/clplumbing/cl_random.h create mode 100644 include/clplumbing/cl_reboot.h create mode 100644 include/clplumbing/cl_signal.h create mode 100644 include/clplumbing/cl_syslog.h create mode 100644 include/clplumbing/cl_tiebreaker.h create mode 100644 include/clplumbing/cl_uuid.h create mode 100644 include/clplumbing/coredumps.h create mode 100644 include/clplumbing/cpulimits.h create mode 100644 include/clplumbing/ipc.h create mode 100644 include/clplumbing/loggingdaemon.h create mode 100644 include/clplumbing/longclock.h create mode 100644 include/clplumbing/lsb_exitcodes.h create mode 100644 include/clplumbing/md5.h create mode 100644 include/clplumbing/mkstemp_mode.h create mode 100644 include/clplumbing/netstring.h create mode 100644 include/clplumbing/proctrack.h create mode 100644 include/clplumbing/realtime.h create mode 100644 include/clplumbing/replytrack.h create mode 100644 include/clplumbing/setproctitle.h create mode 100644 include/clplumbing/timers.h create mode 100644 include/clplumbing/uids.h create mode 100644 include/compress.h create mode 100644 include/glue_config.h.in create mode 100644 include/ha_msg.h create mode 100644 include/lha_internal.h create mode 100644 include/lrm/Makefile.am create mode 100644 include/lrm/lrm_api.h create mode 100644 include/lrm/lrm_msg.h create mode 100644 include/lrm/racommon.h create mode 100644 include/lrm/raexec.h create mode 100644 include/pils/Makefile.am create mode 100644 include/pils/generic.h create mode 100644 include/pils/interface.h create mode 100644 include/pils/plugin.h.in create mode 100644 include/replace_uuid.h create mode 100644 include/stonith/Makefile.am create mode 100644 include/stonith/expect.h create mode 100644 include/stonith/st_ttylock.h create mode 100644 include/stonith/stonith.h create mode 100644 include/stonith/stonith_plugin.h create mode 100644 lib/Makefile.am create mode 100644 lib/clplumbing/GSource.c create mode 100644 lib/clplumbing/Makefile.am create mode 100644 lib/clplumbing/base64.c create mode 100644 lib/clplumbing/base64_md5_test.c create mode 100644 lib/clplumbing/cl_compress.c create mode 100644 lib/clplumbing/cl_log.c create mode 100644 lib/clplumbing/cl_malloc.c create mode 100644 lib/clplumbing/cl_misc.c create mode 100644 lib/clplumbing/cl_msg.c create mode 100644 lib/clplumbing/cl_msg_types.c create mode 100644 lib/clplumbing/cl_netstring.c create mode 100644 lib/clplumbing/cl_pidfile.c create mode 100644 lib/clplumbing/cl_plugin.c create mode 100644 lib/clplumbing/cl_poll.c create mode 100644 lib/clplumbing/cl_random.c create mode 100644 lib/clplumbing/cl_reboot.c create mode 100644 lib/clplumbing/cl_signal.c create mode 100644 lib/clplumbing/cl_syslog.c create mode 100644 lib/clplumbing/cl_uuid.c create mode 100644 lib/clplumbing/coredumps.c create mode 100644 lib/clplumbing/cpulimits.c create mode 100644 lib/clplumbing/ipcsocket.c create mode 100644 lib/clplumbing/ipctest.c create mode 100644 lib/clplumbing/ipctransient.h create mode 100644 lib/clplumbing/ipctransientclient.c create mode 100644 lib/clplumbing/ipctransientlib.c create mode 100644 lib/clplumbing/ipctransientserver.c create mode 100644 lib/clplumbing/longclock.c create mode 100644 lib/clplumbing/md5.c create mode 100644 lib/clplumbing/mkstemp_mode.c create mode 100644 lib/clplumbing/netstring_test.c create mode 100644 lib/clplumbing/ocf_ipc.c create mode 100644 lib/clplumbing/proctrack.c create mode 100644 lib/clplumbing/realtime.c create mode 100644 lib/clplumbing/replytrack.c create mode 100644 lib/clplumbing/setproctitle.c create mode 100644 lib/clplumbing/timers.c create mode 100755 lib/clplumbing/transient-test.sh create mode 100644 lib/clplumbing/uids.c create mode 100644 lib/lrm/Makefile.am create mode 100644 lib/lrm/clientlib.c create mode 100644 lib/lrm/lrm_msg.c create mode 100644 lib/lrm/racommon.c create mode 100644 lib/pils/Makefile.am create mode 100644 lib/pils/main.c create mode 100644 lib/pils/pils.c create mode 100644 lib/pils/test.c create mode 100644 lib/plugins/InterfaceMgr/HBauth.c create mode 100644 lib/plugins/InterfaceMgr/Makefile.am create mode 100644 lib/plugins/InterfaceMgr/generic.c create mode 100644 lib/plugins/Makefile.am create mode 100644 lib/plugins/compress/Makefile.am create mode 100644 lib/plugins/compress/bz2.c create mode 100644 lib/plugins/compress/zlib.c create mode 100644 lib/plugins/lrm/Makefile.am create mode 100644 lib/plugins/lrm/dbus/Makefile.am create mode 100644 lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml create mode 100644 lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml create mode 100644 lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml create mode 100644 lib/plugins/lrm/raexechb.c create mode 100644 lib/plugins/lrm/raexeclsb.c create mode 100644 lib/plugins/lrm/raexecocf.c create mode 100644 lib/plugins/lrm/raexecupstart.c create mode 100644 lib/plugins/lrm/upstart-dbus.c create mode 100644 lib/plugins/lrm/upstart-dbus.h create mode 100644 lib/plugins/stonith/Makefile.am create mode 100644 lib/plugins/stonith/apcmaster.c create mode 100644 lib/plugins/stonith/apcmastersnmp.c create mode 100644 lib/plugins/stonith/apcmastersnmp.cfg.example create mode 100644 lib/plugins/stonith/apcsmart.c create mode 100644 lib/plugins/stonith/apcsmart.cfg.example create mode 100644 lib/plugins/stonith/baytech.c create mode 100644 lib/plugins/stonith/bladehpi.c create mode 100644 lib/plugins/stonith/cyclades.c create mode 100644 lib/plugins/stonith/drac3.c create mode 100644 lib/plugins/stonith/drac3_command.c create mode 100644 lib/plugins/stonith/drac3_command.h create mode 100644 lib/plugins/stonith/drac3_hash.c create mode 100644 lib/plugins/stonith/drac3_hash.h create mode 100644 lib/plugins/stonith/external.c create mode 100644 lib/plugins/stonith/external/Makefile.am create mode 100644 lib/plugins/stonith/external/drac5.in create mode 100644 lib/plugins/stonith/external/dracmc-telnet create mode 100755 lib/plugins/stonith/external/hetzner create mode 100644 lib/plugins/stonith/external/hmchttp create mode 100644 lib/plugins/stonith/external/ibmrsa create mode 100644 lib/plugins/stonith/external/ibmrsa-telnet create mode 100644 lib/plugins/stonith/external/ipmi create mode 100755 lib/plugins/stonith/external/ippower9258.in create mode 100644 lib/plugins/stonith/external/kdumpcheck.in create mode 100644 lib/plugins/stonith/external/libvirt create mode 100644 lib/plugins/stonith/external/nut create mode 100644 lib/plugins/stonith/external/rackpdu create mode 100644 lib/plugins/stonith/external/riloe create mode 100644 lib/plugins/stonith/external/ssh.in create mode 100755 lib/plugins/stonith/external/vcenter create mode 100644 lib/plugins/stonith/external/vmware create mode 100644 lib/plugins/stonith/external/xen0 create mode 100755 lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper create mode 100755 lib/plugins/stonith/external/xen0-ha.in create mode 100644 lib/plugins/stonith/ibmhmc.c create mode 100644 lib/plugins/stonith/ipmi_os_handler.c create mode 100644 lib/plugins/stonith/ipmilan.c create mode 100644 lib/plugins/stonith/ipmilan.h create mode 100644 lib/plugins/stonith/ipmilan_command.c create mode 100644 lib/plugins/stonith/ipmilan_test.c create mode 100644 lib/plugins/stonith/meatware.c create mode 100644 lib/plugins/stonith/null.c create mode 100644 lib/plugins/stonith/nw_rpc100s.c create mode 100644 lib/plugins/stonith/rcd_serial.c create mode 100644 lib/plugins/stonith/rhcs.c create mode 100644 lib/plugins/stonith/ribcl.py.in create mode 100644 lib/plugins/stonith/riloe.c create mode 100644 lib/plugins/stonith/rps10.c create mode 100644 lib/plugins/stonith/ssh.c create mode 100644 lib/plugins/stonith/stonith_config_xml.h create mode 100644 lib/plugins/stonith/stonith_expect_helpers.h create mode 100644 lib/plugins/stonith/stonith_plugin_common.h create mode 100644 lib/plugins/stonith/stonith_signal.h create mode 100644 lib/plugins/stonith/suicide.c create mode 100644 lib/plugins/stonith/vacm.c create mode 100644 lib/plugins/stonith/wti_mpc.c create mode 100644 lib/plugins/stonith/wti_nps.c create mode 100644 lib/stonith/Makefile.am create mode 100644 lib/stonith/README create mode 100644 lib/stonith/expect.c create mode 100755 lib/stonith/ha_log.sh create mode 100644 lib/stonith/main.c create mode 100644 lib/stonith/meatclient.c create mode 100644 lib/stonith/st_ttylock.c create mode 100644 lib/stonith/stonith.c create mode 100644 logd/Makefile.am create mode 100644 logd/ha_logd.c create mode 100644 logd/ha_logger.1 create mode 100644 logd/ha_logger.c create mode 100644 logd/logd.cf create mode 100755 logd/logd.in create mode 100644 logd/logd.service.in create mode 100644 logd/logtest.c create mode 100644 lrm/Makefile.am create mode 100644 lrm/admin/Makefile.am create mode 100755 lrm/admin/cibsecret.in create mode 100644 lrm/admin/lrmadmin.c create mode 100644 lrm/admin/lrmadmin.txt create mode 100644 lrm/lrmd/Makefile.am create mode 100644 lrm/lrmd/audit.c create mode 100644 lrm/lrmd/cib_secrets.c create mode 100644 lrm/lrmd/lrmd.c create mode 100644 lrm/lrmd/lrmd.h create mode 100644 lrm/lrmd/lrmd_fdecl.h create mode 100755 lrm/test/LRMBasicSanityCheck.in create mode 100644 lrm/test/Makefile.am create mode 100644 lrm/test/README.regression create mode 100644 lrm/test/apitest.c create mode 100644 lrm/test/apitest.exp create mode 100644 lrm/test/callbacktest.c create mode 100644 lrm/test/defaults create mode 100644 lrm/test/descriptions create mode 100755 lrm/test/evaltest.sh create mode 100644 lrm/test/language create mode 100644 lrm/test/lrmadmin-interface create mode 100644 lrm/test/lrmregtest-lsb create mode 100644 lrm/test/lrmregtest.in create mode 100644 lrm/test/plugintest.c create mode 100755 lrm/test/regression.sh.in create mode 100644 lrm/test/testcases/BSC create mode 100644 lrm/test/testcases/Makefile.am create mode 100644 lrm/test/testcases/basicset create mode 100755 lrm/test/testcases/common.filter create mode 100644 lrm/test/testcases/flood create mode 100644 lrm/test/testcases/flood.exp create mode 100644 lrm/test/testcases/metadata create mode 100644 lrm/test/testcases/metadata.exp create mode 100755 lrm/test/testcases/ra-list.sh create mode 100644 lrm/test/testcases/rscexec create mode 100644 lrm/test/testcases/rscexec.exp create mode 100644 lrm/test/testcases/rscmgmt create mode 100644 lrm/test/testcases/rscmgmt.exp create mode 100755 lrm/test/testcases/rscmgmt.log_filter create mode 100644 lrm/test/testcases/serialize create mode 100644 lrm/test/testcases/serialize.exp create mode 100644 lrm/test/testcases/stonith create mode 100644 lrm/test/testcases/stonith.exp create mode 100755 lrm/test/testcases/xmllint.sh create mode 100644 replace/Makefile.am create mode 100644 replace/NoSuchFunctionName.c create mode 100644 replace/alphasort.c create mode 100644 replace/daemon.c create mode 100644 replace/inet_pton.c create mode 100644 replace/scandir.c create mode 100644 replace/setenv.c create mode 100644 replace/strerror.c create mode 100644 replace/strlcat.c create mode 100644 replace/strlcpy.c create mode 100644 replace/strndup.c create mode 100644 replace/strnlen.c create mode 100644 replace/unsetenv.c create mode 100644 replace/uuid_parse.c diff --git a/.hg_archival.txt b/.hg_archival.txt new file mode 100644 index 0000000..22a0b4f --- /dev/null +++ b/.hg_archival.txt @@ -0,0 +1,2 @@ +repo: e3ffdd7ae81c596b2be7e1e110d2c1255161340e +node: 0a7add1d9996b6d869d441da6c82fb7b8abcef4f diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..8ce7ca5 --- /dev/null +++ b/.hgignore @@ -0,0 +1,92 @@ +syntax: glob + +# Autofoo entries +*.o +*.la +*.lo +*.loT +*.pyc +.libs +.deps +*.cache +.cvsignore +compile +configure +configure.status +configure.lineno +depcomp +aclocal.m4 +libtool +ltmain.sh +ltconfig +libltdl +mkinstalldirs +install-sh +missing +py-compile +autom4te* +libtool.m4 +ltdl.m4 +libltdl.tar +autoconf +autoheader +automake +include/glue_config.h +include/stamp-h1 +include/pils/plugin.h +include/stamp-h2 +ylwrap + +# BEAM Entries +*.beam +parser-messages +MISC_ERRORS +cscope.files +cscope.out +patches +updates +logs + +# OS and Editor Artifacts +.DS_Store +*.diff +*.patch +*~ + +# Project build targets +lib/clplumbing/base64_md5_test +lib/clplumbing/ipctest +lib/clplumbing/ipctransientclient +lib/clplumbing/ipctransientserver +logd/ha_logd +logd/ha_logger +logd/logtest +lrm/admin/lrmadmin +lrm/lrmd/lrmd +lrm/test/apitest +lrm/test/callbacktest +lrm/test/plugintest +lrm/test/lrmregtest +lrm/test/lrmregtest-heartbeat +lrm/test/lrmregtest-lsb +lrm/test/regression.sh +lrm/test/LRMBasicSanityCheck +lrm/test/simple_ops + +# Misc +GPATH +GRTAGS +GSYMS +GTAGS +HTML +TAGS +.gres.* +*.orig +.gdb_history + +# Entries better done as regexp's to avoid matching too broadly +syntax: regexp +^config\.* +README$ +Makefile$ +Makefile.in$ diff --git a/.hgsigs b/.hgsigs new file mode 100644 index 0000000..ddb9f3d --- /dev/null +++ b/.hgsigs @@ -0,0 +1,4 @@ +b6dca003bb176978af803eeb33019b6aef3c58b0 0 iEYEABECAAYFAktnGJAACgkQWnQN9wr0w1ywBACghXYwYkv/70Xg5AQMzVjRWKZecIoAnjRUytRoYl+dhhqbhfdXSD+/Bfvw +6007185b487e3f2dc3b24674a9105761b2cde6ea 0 iEYEABECAAYFAktoWfsACgkQWnQN9wr0w1ySZwCfQILyC2VJrCnVEU2zvTIyI7ustDAAn37hhb9JM8JQVKLfPEbqIloz1m3m +979c4ffae287976631a30d10258903aea6fb28fa 0 iEYEABECAAYFAktoY38ACgkQWnQN9wr0w1wHxgCeMZyOt8ccxmIsvIHg4/y6KmqtTVAAn2jn7dOmFMjA8m4ju59YaQ1Bznhb +798645ead29e20b361af883fce695b85caf3392b 0 iEYEABECAAYFAlPJCM4ACgkQWnQN9wr0w1wv+QCeJQOjaYNXNJZA61n7Fu8f63CeVBEAnja4WqiYC+TS4HvmRJz6oNi6p48u diff --git a/.hgtags b/.hgtags new file mode 100644 index 0000000..6e84e98 --- /dev/null +++ b/.hgtags @@ -0,0 +1,68 @@ +01ecac8670a6c2e47202a9ce2f5e27e9dcdbeff6 STABLE-1.1.3 +19d11d8d62c270c48a3feab5ed66b18897c9cc8d sle11-rc9 +2185d55c12e37c48abc239dd1f8b3b9ef012fd6b obs-2.1.2-1 +235a71009062702c906cc68f23904ddcbe17535f STABLE-2.0.6 +29540582671a9e33ae2122d319c68258346f1a3f STABLE-2.1.1 +2cb36a1c01c76ef3e3a449f16b13730c761efff2 STABLE-2.1.3 +2ece20ad31a4271076e5c43dd3f2ea25caa55635 Series-Root-1.0 +3b8dc33a402daaf7e3754acadd1898c0fe69072f STABLE-2.0.3 +45c377d7a35dba92d46321d2f824bc0d9b17f54e obs-2.1.2-24 +67a443d135f128ca28f15e4e8999d3e0caabed61 sle11-rc3 +68de68ef5f0a7b97a4ff0d9806c598527c8659b8 STABLE-2.0.4 +705b21e4b623f7d2fc5c83d99beffb709905c996 STABLE-0.4.9c +7190f69e29a08350bcec753509eb37f53593334a beta-2.99.0 +7f90244e5c25372e70178f77f44c76a8564e1665 SLE10-SP1 +7f90244e5c25372e70178f77f44c76a8564e1665 STABLE-2.1.0 +823208439a98179d7c01d6eed1db50dd96802663 sle11-rc7 +86fa06f08a123868eb272a20bf850cec1805a12f STABLE-2.0.8 +97d025dd33648a1e50a3a1bf40573669440dd1b6 obs-2.1.2-4 +9b34f480b8e8966e9ed4276507cc562564763720 beta-2.99.2 +9eb2a4db4ff595d18302426029b03153fad77ef7 beta-2.99.1 +a230062a445096b89cf75bef85e285ee55626a78 obs-2.1.2-2 +af867b71bcc645f3d3c56fe8fdd883b17a851e46 STABLE-2.0.0 +b4a0a0ffd15eb2dd1285bdbd86ea9716a9d0bf36 STABLE-2.0.5 +b906db882c37647abfd21fa1473950445ad7813c STABLE-2.1.2 +ba476a3948ea0cf52098fa050a27a8856a214825 sle11-rc2 +be0d49da51a810e870356b7f2a52013e5c775c0d Beta-0.4.9a +c77ad4549888539e7fc9a6b56cccdb1403749198 STABLE-0.4.9e +c7d672b9f3ece79ad26fb8a7df20265bcb596515 sle11-beta6 +cf0265eed1b5b3b3f25f7e56eb807d21ca261d68 SLES10-GA-2.0.7-1.5 +cf0265eed1b5b3b3f25f7e56eb807d21ca261d68 STABLE-2.0.7 +d1899e1eecc09b7a6e66a02609408272bb856c6a STABLE-1.1.5 +dae6b0b3e109afc5df29a7127ab6dd9e1bd0a20a sle11-rc5 +e3691501a2d0631c3796b6a728fadf7d90691203 obs-2.1.2-15 +e3855af19554339204b5b2b2a199a7bc31e22843 STABLE-2.0.1 +e3855af19554339204b5b2b2a199a7bc31e22843 STABLE-2.0.2 +e6637f62c87ada212a83942ec5b2a4bf30b98c3f Series-Root-1.2 +f6c2cd2593f365f984ce051db61466738ac05dcd Beta-0.4.9f +940fa13f6a0a929d15a01af9a0b62c16e4d2706a glue-1.0 +130b1d7af88912d077d32a7c386c3c94d0b2da16 glue-1.0.2-rc1 +78894a112c0a134dc709d2a8772085180444c40c glue-1.0.2-rc2 +7700902a4de3ee84fa2007a4b4602693c5ac26a8 glue-1.0.2-rc2a +97fcdf789e174b0a0b23e28dcabe2f7d579d426f glue-1.0.2 +0a64e6f77894da1364b17dc3c73b65561717f4aa glue-1.0.3 +0a64e6f77894da1364b17dc3c73b65561717f4aa glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +979c4ffae287976631a30d10258903aea6fb28fa glue-1.0.3 +979c4ffae287976631a30d10258903aea6fb28fa glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +9bcd134f1ebff7baf80f4b21c3b5f620b0ee976e glue-1.0.3 +9bcd134f1ebff7baf80f4b21c3b5f620b0ee976e glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +0000000000000000000000000000000000000000 glue-1.0.3 +2e33ecd820b2673755d1280a259489a026921f63 glue-1.0.3 +761edff8c35ea2cdf3e1bd37d600b06233e61d4f glue-1.0.4-rc1 +3229873980e1028bf05de81f5bafccb3a92b9aa4 glue-1.0.4 +3af80b93d9e5d5e441f3f4c3aad16775ea27d2d9 glue-1.0.5 +1c87a0c58c59fc384b93ec11476cefdbb6ddc1e1 glue-1.0.6 +61200fbe18358e420cdc2037d87e803e150c1eac glue-1.0.7-rc1 +5e06b2ddd24b37ad6c1c25d958d7a9dda7d02f93 glue-1.0.7 +5740338816e1ff07d0e37f36214f442e183984d7 glue-1.0.8-rc1 +c69dc6ace936f501776df92dab3d611c2405f69e glue-1.0.8 +0a08a469fdc8a0db1875369497bc83c0523ceb21 glue-1.0.9 +12055ca2b025ab250a544701edaa1f5aaf63aef1 glue-1.0.10 +02bdcf58f9a098b717784746308e199e12eeb005 glue-1.0.11 +c64d6e96f20ad5ba245f7fb9e1295b14fa179e29 glue-1.0.12-rc1 +d05229decc34d66c4752536dc7c9d812d1e6d5ca glue-1.0.12 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..ec67fd0 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,19 @@ +Alan Robertson +Andreas Mock +Andrew Beekhof +Dave Blaschke +David Lee +Dejan Muhamedagic +Hannes Eder +Huang Zhen +Junko Ikeda +Lars Marowsky-Bree +Martin Bene +Phil Carns +Satomi Taniguchi +Sean Reifschneider +Sebastian Reitenbach +Serge Dubrouski +Simon Horman +Xinwei Hu + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000..602bfc9 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..4366ee3 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,262 @@ +* Fri Jul 18 2014 Dejan Muhamedagic , Lars Ellenberg , and many others +- stable release 1.0.12 +- hb_report: add -Q option for quick runs +- hb_report: dot is not illegal in file names (bnc#884079, deb#715391) +- build: update spec files for systemd +- hb_report: update interface to zypper +- hb_report: support logs with varied timestamps +- stonith: external/vcenter: add parameter for SSL hostname + verification (bnc#851990) +- hb_report: fix ssh passwords again (bnc#867365) +- hb_report: Don't use deprecated corosync-fplay (bnc#870913) +- logd: Add systemd unit file for logd (bnc#863248) +- hb_report: Add support for xz compression (bnc#854060) + +* Thu Sep 26 2013 Dejan Muhamedagic , Lars Ellenberg , and many others +- release candidate 1.0.12-rc1 +- clplumbing: increase listen backlog to 128 +- hb_report: add -X option for extra ssh options +- hb_report: add support for the rfc5424 syslog date format +- stonith: external/libvirt: fix exit code in reset +- stonith: external/vcenter: do not list vms in status + (bnc#825765) +- stonith: fix memory leak in external.c +- hb_report: enable ssh to prompt for passwords (bnc#808373) +- hb_report: collect RA trace files +- hb_report: look for nodes in the archived CIB if pacemaker is + not running +- sbd plugin now lives at http://hg.linux-ha.org/sbd/ +- stonith: if debug's not set, do not send debug messages to the + logger (bnc#792124) +- stonith: log status message at the debug level (bnc#792124) +- stonith: don't always log debug level messages (bnc#792124) +- stonith: external/vcenter: fix gethosts to produce list of + nodes not vms (bnc#792704) + +* Mon Oct 15 2012 Dejan Muhamedagic , Lars Ellenberg , and many others +- stable release 1.0.11 +- lrmd: set max-children depending on the number of processors +- lrmd: don't send parameters from ops back to crmd +- stonith: external/libvirt: support for reboot reset method +- hb_report: node's type got optional +- hb_report: make use of bash trace features +- hb_report: compatibility code for pacemaker v1.1.8 +- build: link libstonith with stonith2 agents + +* Mon Jul 16 2012 Dejan Muhamedagic , Lars Ellenberg , and many others +- stable release 1.0.10 +- clplumbing: ipc: fix message size checks (bnc#752231) +- clplumbing: load bz2 compression module by default +- clplumbing: cl_msg: try compressing message before rejecting it + as too big +- clplumbing: cl_msg: don't use traditional compression by default +- clplumbing: cl_msg: increase compression threshold +- clplumbing: fix memleak for Gmain_timeout +- LRM: lrmd: add basic authentication (lf#2547) +- LRM: lrmd: use the resource timeout as an override to the + default dbus timeout for upstart RA +- LRM: lrmd: if set, get max-children from the LRMD_MAX_CHILDREN + environment var +- stonith: add CRM stonith resource name to log messages (bnc#728579) +- stonith: adjust timeouts in the meta-data template (bnc#733337) +- stonith: external/vcenter: return list of configured hosts on + gethosts +- stonith: external/libvirt: add more search strings for domain + start and stop +- stonith: rhcs: pass the action via stdin too +- stonith: rhcs: avoid false error if parameter isn't set +- logd: remove runlevel 4 from the LSB info section in the logd + init script (bnc#744120) +- logd: add try-restart action to the logd init script +- sbd: Use async IO for disk reads to increase resilience against + hung IO (bnc#738295) +- sbd: Handle IO errors during slot allocation properly (bnc#753559) +- sbd: Debug mode added (bnc#753559, bnc#738295) +- hb_report: improve performance +- hb_report: get corosync blackbox records if available +- hb_report: add node time information + +* Mon Nov 28 2011 Dejan Muhamedagic , Lars Ellenberg , and many others +- stable release 1.0.9 +- stonith: external/ipmi: add missing double quote +- stonith: external/ipmi: add the priv parameter (ipmitool -L) +- LRM: lrmd: set op status to cancelled for running monitor operations +- ha_log: increase MAXENTITY size to accommodate long stonith strings +- hb_report: improve destination directory handling (bnc#727295) +* Tue Oct 18 2011 Dejan Muhamedagic , Lars Ellenberg , and many others +- stable release 1.0.8 +- cl_log: log spamming control +- LRM: raexecocf: list resource agents properly (bnc#664409) +- LRM: lrmd: allow storing parameters in local files (lf#2415) +- LRM: lrmd: limit number of "stayed too long in operation list" + log messages (bnc#636576) +- stonith: external/libvirt: new plugin for libvirt virtualization technologies +- stonith: external/vcenter: new plugin +- stonith: external/hetzner: new plugin +- stonith: sbd: support for multiple devices +- stonith: sbd: Fix timeout setting on archs where int != long (bnc#635690) +- stonith: sbd: abort start if watchdog cannot be initialized (bnc#680109) +- stonith: sbd: Make failing to set the watchdog timeout non-fatal but annoying +- stonith: sbd: Make the restart interval for servants configurable +- stonith: sbd: Maximize scheduler and IO priority in the child processes (bnc#702907) +- stonith: external/sbd: Fix ordering of arguments in reset +- stonith: external/ipmi: fix unique parameters' attributes +- stonith: external/rackpdu: split off assignment from local to + make it work with non-bash shells +- stonith: external: avoid false error if parameter isn't set (bnc#646205) +- hb_report: add .info files with the last byte pos for all logs +- hb_report: use sudo for remove collectors if connecting with + user other than root +- hb_report: install debuginfo packages on platforms with zypper (bnc#641979) +- hb_report: improve detecting ssh user + +* Tue Nov 30 2010 Dejan Muhamedagic , Lars Ellenberg , and many others +- stable release 1.0.7 +- clplumbing: ipc: adjust socket buffers size when adjusting ipc queue length +- logd: add a SIGHUP signal handler to timely close/open log files +- logd: use buffered io with fflush and fsync +- logd: reopen logfiles on inode change (logrotate) +- clplumbing: cl_log: keep logfiles open, but default to non-buffered io (lf#2470) +- clplumbing: cl_log: add new optional common syslog message prefix +- stonith: use ST_DEVICEID for the short description in meta-data +- stonith: external: interpret properly exit codes from external stonith + plugins (bnc#630357) +- stonith: external: avoid false out of memory error if a parameter isn't set (bnc#646205) +- stonith: external: check if PATH already contains GLUE_SHARED_DIR + (memory leak, lf#2484) +- stonith(8): reduce the number of stonith plugin invocations (bnc#630357) +- stonith(8): use cl_log for logging if invoked by stonithd (pcmk 1.1) +- stonith: external/sbd: make sbd use realtime priority for IO (works only with CFQ) +- stonith: cyclades: add the serial_port parameter to the meta-data +- stonith: external/riloe: add support for http proxies +- stonith: external/ipmi: provide opt param "passwd_method" to hide + the ipmi password from config and logs +- stonith: external/nut: support for the Network UPS Tools +- stonith: external/rackpdu: remove displaced local command +- stonith: rcd_serial: rename dtr|rts parameter to dtr_rts +- configure: test for POSIX signals (fixes rcd_serial) + +* Fri Jul 9 2010 Dejan Muhamedagic +- stable release 1.0.6 +- clplumbing: Add identity info of the user on the other side of socket +- ha_logger: log strings longer than 1024 +- lrmd: remove operation history on client unregister (lf#2161) +- lrmd: don't allow cancelled operations to get back to the repeating op list (lf#2417) +- lrmd: exclude stonith resources from child count (bnc#612387) +- lrmd,clientlib: asynchronous resource delete notification (lf#2439) +- stonith: add -V (version) to stonith +- stonith: add -E option to get the configuration from the environment +- stonith: ha_log: feed the message to stdout and not on command line +- stonith: external/sbd,xen0: fix wrong reference from ha_log to ha_log.sh (deb#585120) +- stonith: external/sbd: reduce monitoring +- stonith: external/rackpdu: check the snmpset and snmpwalk exit codes +- hb_report: create cib.txt after sanitizing the CIB (lf#2415) + +* Mon Apr 15 2010 Dejan Muhamedagic +- stable release 1.0.5 +- clplumbing: revert changeset 81ad41d14f72 which breaks the ABI + +* Mon Apr 12 2010 Dejan Muhamedagic +- stable release 1.0.4 +- clplumbing: fix memory leak in cl_msg/lrmd (lf#1841,2389) +- clplumbing: Add identity info of the user on the other side of socket +- clplumbing: Fix erroneous "Stack hogger failed 0xffffffff" warnings +- lrmd: fix possible null pointer dereference +- lrmd: raise severity from debug to info for some log messages +- lrmd: on shutdown exit once all operations finished (lf#2340) +- lrmd: don't add the cancel option in flush to the running operations (bnc#578644) +- lrmd: check if tables exist before free_str_table and prevent + segfault (bnc#587887) +- stonith: new external/ippower9258 plugin +- stonith: external/sbd: fix status operation +- stonith: external/sbd: add support for heartbeat +- stonith: external/ibmrsa-telnet: fix ha_log.sh invocation +- stonith: external/ibmrsa-telnet: fix expect regex +- stonith: external/ipmi: make reset work when the node is off +- stonith: external/riloe: log error message on unrecognized power method +- hb_report: don't create dot files if there are more than 20 PE files +- hb_report: make dot and png files for PE inputs (if there are + not too many) +- hb_report: do not filter CIB/PE files by default (use -s to + force filtering) +- hb_report: add -Z option to force destination directory cleanup +- hb_report: allow for default destination +- hb_report: when creating cts reports get information from the log +- hb_report: new option -d to keep the directory +- hb_report: don't give up early when creating backtraces (lf#2350) + +* Tue Feb 02 2010 Dejan Muhamedagic +- bugfix release 1.0.3 +- lrmd: don't flush operations which don't belong to the requesting client (lf#2161) + +* Mon Feb 01 2010 Dejan Muhamedagic and MANY others +- stable release 1.0.2 +- clplumbing: fix a potential resource leak in cl_random (bnc#525393) +- clplumbing: change the default log format to syslog format +- lrmd: log outcome of monitor once an hour +- lrmd: lookup clients by name (lf#2161) +- lrmd: remove operation history on client unregister (lf#2161) +- lrmd: fix return code on LSB class RA exec failure (lf#2194) +- lrmd: close the logd fd too when executing agents (lf#2267) +- lrmd: restore reset scheduler for children (bnc#551971,lf#2296) +- lrmd: reset scheduler and priority for children (resource operations) +- lrmadmin: fix -E option +- lrmadmin moved to the sbindir +- stonith: support for RHCS fence agents +- stonith: external/dracmc-telnet: stonith plugin for Dell + Drac/MC Blade Enclosure and Cyclades terminal server +- stonith: sbd plugin +- stonith: apcmastersnmp plugin (bnc#518689) +- stonith: bladehpi plugin (bnc#510299) +- stonith: WTS MPC: new SNMP based plugin +- stonith: meatclient: add -w option to wait until we can connect +- stonith: add -m option to stonith(8) to display metadata (lf#2279) +- stonith: external: log using ha_log.sh (lf#2294,1971) +- stonith: external: log output of plugins (bnc#548699,bnc#553340) +- stonith: external: log messages immediately on manage and status calls +- stonith: external: remove dependency on .ocf-shellfuncs (lf#2249) +- stonith: external/riloe: make sure that host is turned on after power + off/on reset (lf#2282) +- stonith: external/riloe: fix check for ilo_can_reset +- stonith: external/riloe: workaround for the iLO double close in RIBCL (bnc#553340) +- stonith: external/ipmi: add explanation on reset and power off (LF 2071) +- stonith: external/ibmrsa-telnet: add support for later RSA cards +- stonith: cyclades: fix for support for newer PM10 firmware (lf#1938) +- stonith: wti_nps: add support for internet power switch model (bnc#539912) +- stonith: wti_mpc: support for MIB versions 1 and 3 +- stonith: external/sbd: fix definition of sector_size for s390x (bnc#542827) +- stonith: external/sbd: make nodename comparison case insensitive (bnc#534445) +- stonith: external/sbd: describe "dump" command in help (bnc#529575) +- stonith: external/sbd: Accept -h (bnc#529574) +- stonith: external/xen0: add run_dump parameter to dump core before resetting a node +- hb_report: add man page hb_report.8 +- hb_report: add -V (version) option +- hb_report: add support for corosync +- hb_report: add -v option (debugging) +- hb_report: options -C and -D are obsoleted +- hb_report: combine log/events if there is no loghost +- hb_report: extract important events from the logs +- logd: add init script +- rpm spec: start logd by default +- doc: new README for wti_mpc +- doc: move stonith README files to the doc directory +- doc: convert man pages to xml +- build: /usr/share/heartbeat replaced by /usr/share/cluster-glue +- build: enable IPMI and hpi support +- build: include time.h in ipcsocket.c and proctrack.c (lf#2263) +- build: output documentation directory from configure (lf#2276) + +* Thu Oct 23 2008 Lars Marowsky-Bree and MANY others +- beta release 2.99.2 +- stonith: external/kdumpcheck: new plugin +- stonith: external/drac5: new plugin +- stonith: drac3: initialize curl properly and workaround xml parsing problem (lf#1730) +- stonith external/riloe: a new implementation for HP iLO devices + +* Tue Sep 23 2008 Lars Marowsky-Bree and MANY others +- beta release 2.99.1 +- stonith: bladehpi: fix a mix of a threaded library and not threaded stonithd (bnc#389344) +- stonith: external/riloe: fix check for ilo_can_reset + +* Tue Aug 19 2008 Andrew Beekhof and MANY others +- beta release 2.99.0 diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..a641d9c --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,47 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +-include Makefile + +PACKAGE ?= cluster-glue +TARFILE = $(PACKAGE).tar.bz2 + +RPM_ROOT = $(shell pwd) +RPM_OPTS = --define "_sourcedir $(RPM_ROOT)" \ + --define "_specdir $(RPM_ROOT)" \ + --define "_srcrpmdir $(RPM_ROOT)" + + +getdistro = $(shell test -e /etc/SuSE-release || echo fedora; test -e /etc/SuSE-release && echo suse) +DISTRO ?= $(call getdistro) +TAG ?= tip + +hgarchive: + rm -f $(TARFILE) + hg archive -t tbz2 -r $(TAG) $(TARFILE) + echo `date`: Rebuilt $(TARFILE) + +srpm: hgarchive + rm -f *.src.rpm + @echo To create custom builds, edit the flags and options in $(PACKAGE)-$(DISTRO).spec first + rpmbuild -bs --define "dist .$(DISTRO)" $(RPM_OPTS) $(PACKAGE)-$(DISTRO).spec + +rpm: srpm + rpmbuild $(RPM_OPTS) --rebuild $(RPM_ROOT)/*.src.rpm + + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..93dbaf6 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,39 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +DISTCHECK_CONFIGURE_FLAGS = \ + --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) + +MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure DRF/config-h.in \ + DRF/stamp-h.in libtool.m4 ltdl.m4 libltdl.tar + +SUBDIRS = include $(LIBLTDL_DIR) replace lib lrm logd \ + hb_report doc config + +install-exec-local: + $(INSTALL) -d $(DESTDIR)/$(HA_COREDIR) + -$(INSTALL) -d -m 700 -o root $(DESTDIR)/$(HA_COREDIR)/root + -$(INSTALL) -d -m 700 -o nobody $(DESTDIR)/$(HA_COREDIR)/nobody + $(INSTALL) -d -m 700 $(DESTDIR)/$(HA_COREDIR)/$(GLUE_DAEMON_USER) + -chown $(GLUE_DAEMON_USER) $(DESTDIR)/$(HA_COREDIR)/$(GLUE_DAEMON_USER) +# Use chown because $(GLUE_DAEMON_USER) may not exist yet + +dist-clean-local: + rm -f autoconf automake autoheader $(TARFILE) + +.PHONY: diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ + diff --git a/README b/README new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/README @@ -0,0 +1 @@ + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..7464c9d --- /dev/null +++ b/autogen.sh @@ -0,0 +1,193 @@ +#!/bin/sh +# +# License: GNU General Public License (GPL) +# Copyright 2001 horms +# (heavily mangled by alanr) +# +# bootstrap: set up the project and get it ready to make +# +# Basically, we run autoconf, automake and libtool in the +# right way to get things set up for this environment. +# +# We also look and see if those tools are installed, and +# tell you where to get them if they're not. +# +# Our goal is to not require dragging along anything +# more than we need. If this doesn't work on your system, +# (i.e., your /bin/sh is broken) send us a patch. +# +# This code loosely based on the corresponding named script in +# enlightenment, and also on the sort-of-standard autoconf +# bootstrap script. + +# Run this to generate all the initial makefiles, etc. + +testProgram() +{ + cmd=$1 + + if [ -z "$cmd" ]; then + return 1; + fi + + arch=`uname -s` + + # Make sure the which is in an if-block... on some platforms it throws exceptions + # + # The ERR trap is not executed if the failed command is part + # of an until or while loop, part of an if statement, part of a && + # or || list. + if + which $cmd /dev/null 2>&1 + then + : + else + return 1 + fi + + # The GNU standard is --version + if + $cmd --version /dev/null 2>&1 + then + return 0 + fi + + # Maybe it suppports -V instead + if + $cmd -V /dev/null 2>&1 + then + return 0 + fi + + # Nope, the program seems broken + return 1 +} + +gnu="ftp://ftp.gnu.org/pub/gnu" + +for command in autoconf213 autoconf253 autoconf259 autoconf +do + if + testProgram $command == 1 + then + autoconf=$command + autoheader=`echo "$autoconf" | sed -e 's/autoconf/autoheader/'` + autom4te=`echo "$autoconf" | sed -e 's/autoconf/autmo4te/'` + autoreconf=`echo "$autoconf" | sed -e 's/autoconf/autoreconf/'` + autoscan=`echo "$autoconf" | sed -e 's/autoconf/autoscan/'` + autoupdate=`echo "$autoconf" | sed -e 's/autoconf/autoupdate/'` + ifnames=`echo "$autoconf" | sed -e 's/autoconf/ifnames/'` + fi +done + +for command in automake14 automake-1.4 automake15 automake-1.5 automake17 automake-1.7 automake19 automake-1.9 automake-1.11 automake +do + if + testProgram $command + then + : OK $pkg is installed + automake=$command + aclocal=`echo "$automake" | sed -e 's/automake/aclocal/'` + fi +done + +for command in libtool14 libtool15 libtool glibtool +do + URL=$gnu/$pkg/ + if + testProgram $command + then + libtool=$command + libtoolize=`echo "$libtool" | sed -e 's/libtool/libtoolize/'` + fi +done + +if [ -z $autoconf ]; then + echo You must have autoconf installed to compile the cluster-glue package. + echo Download the appropriate package for your system, + echo or get the source tarball at: $gnu/autoconf/ + exit 1 + +elif [ -z $automake ]; then + echo You must have automake installed to compile the cluster-glue package. + echo Download the appropriate package for your system, + echo or get the source tarball at: $gnu/automake/ + exit 1 + +elif [ -z $libtool ]; then + echo You must have libtool installed to compile the cluster-glue package. + echo Download the appropriate package for your system, + echo or get the source tarball at: $gnu/libtool/ + exit 1 +fi + +oneline() { + read x; echo "$x" +} + +LT_version=`$libtool --version | oneline | sed -e 's%^[^0-9]*%%' -e s'% .*%%'` +LT_majvers=`echo "$LT_version" | sed -e 's%\..*%%'` +LT_minvers=`echo "$LT_version" | sed -e 's%^[^.]*\.%%' ` +LT_minnum=`echo "$LT_minvers" | sed -e 's%[^0-9].*%%'` + +if + [ $LT_majvers -lt 1 ] || [ $LT_majvers = 1 -a $LT_minnum -lt 4 ] +then + echo "Minimum version of libtool is 1.4. You have $LT_version installed." + exit 1 +fi + +# Create local copies so that the incremental updates will work. +rm -f ./autoconf ./automake ./autoheader ./libtool +ln -s `which $libtool` ./libtool +ln -s `which $autoconf` ./autoconf +ln -s `which $automake` ./automake +ln -s `which $autoheader` ./autoheader + +printf "$autoconf:\t" +$autoconf --version | head -n 1 + +printf "$automake:\t" +$automake --version | head -n 1 + +rm -rf libltdl libltdl.tar +echo $libtoolize --ltdl --force --copy +# Unset GREP_OPTIONS as any coloring can mess up the AC_CONFIG_AUX_DIR matching patterns +GREP_OPTIONS= $libtoolize --ltdl --force --copy + +arch=`uname -s` +# Disable the errors on FreeBSD until a fix can be found. +if [ ! "$arch" = "FreeBSD" ]; then +set -e +# +# All errors are fatal from here on out... +# The shell will complain and exit on any "uncaught" error code. +# +# +# And the trap will ensure sure some kind of error message comes out. +# +trap 'echo ""; echo "$0 exiting due to error (sorry!)." >&2' 0 +fi + +# Emulate the old --ltdl-tar option... +# If the libltdl directory is required we will unpack it later +tar -cf libltdl.tar libltdl +rm -rf libltdl + +echo $aclocal $ACLOCAL_FLAGS +$aclocal $ACLOCAL_FLAGS + +echo $autoheader +$autoheader + +echo $automake --add-missing --include-deps --copy +$automake --add-missing --include-deps --copy + +echo $autoconf +$autoconf + +test -f libtool.m4 || touch libtool.m4 +test -f ltdl.m4 || touch ltdl.m4 + +echo Now run ./configure +trap '' 0 diff --git a/cluster-glue-fedora.spec b/cluster-glue-fedora.spec new file mode 100644 index 0000000..b480ff5 --- /dev/null +++ b/cluster-glue-fedora.spec @@ -0,0 +1,249 @@ +# Keep around for when/if required +## define alphatag XXX + +%define gname haclient +%define uname hacluster +%define nogroup nobody + +# Directory where we install documentation +%global glue_docdir %{_defaultdocdir}/%{name}-%{version} + +# When downloading directly from Mercurial, it will automatically add this prefix +# Invoking 'hg archive' wont but you can add one with: hg archive -t tgz -p "Reusable-Cluster-Components-" -r $upstreamversion $upstreamversion.tar.gz +%global upstreamprefix Reusable-Cluster-Components- +%global upstreamversion d97b9dea436e + +Name: cluster-glue +Summary: Reusable cluster components +Version: 1.0.12 +Release: 1%{?dist} +License: GPLv2+ and LGPLv2+ +Url: http://www.linux-ha.org/wiki/Cluster_Glue +Group: System Environment/Base +Source0: cluster-glue.tar.bz2 +Requires: perl-TimeDate +Requires: cluster-glue-libs = %{version}-%{release} + +# Directives to allow upgrade from combined heartbeat packages in Fedora11 +Provides: heartbeat-stonith = 3.0.0-1 +Provides: heartbeat-pils = 3.0.0-1 +Obsoletes: heartbeat-stonith < 3.0.0-1 +Obsoletes: heartbeat-pils < 3.0.0-1 +Obsoletes: heartbeat-common + +## Setup/build bits + +BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) + +# Build dependencies +BuildRequires: automake autoconf libtool pkgconfig which +BuildRequires: bzip2-devel glib2-devel python-devel libxml2-devel libaio-devel +BuildRequires: OpenIPMI-devel openssl-devel +BuildRequires: libxslt docbook-dtds docbook-style-xsl +BuildRequires: help2man +BuildRequires: asciidoc + +%if 0%{?fedora} +BuildRequires: libcurl-devel libnet-devel +%endif + +%if 0%{?fedora} || 0%{?centos} > 4 || 0%{?rhel} > 4 +BuildRequires: libtool-ltdl-devel openhpi-devel +BuildRequires: net-snmp-devel >= 5.4 +%else +BuildRequires: gcc-c++ +%endif + +%if 0%{?fedora} > 11 || 0%{?centos} > 5 || 0%{?rhel} > 5 +BuildRequires: libuuid-devel +%else +BuildRequires: e2fsprogs-devel +%endif + +%if %{defined systemd_requires} +BuildRequires: systemd +%{?systemd_requires} +%endif + +%prep +%setup -q -n cluster-glue + +./autogen.sh + +# RHEL <= 5 does not support ./configure --docdir=, +# hence, use this ugly hack +%if 0%{?centos} <= 5 || 0%{?rhel} <= 5 +export docdir=%{glue_docdir} +%configure \ + --enable-fatal-warnings=yes \ + --with-daemon-group=%{gname} \ + --with-daemon-user=%{uname} \ + --localstatedir=%{_var} \ + --libdir=%{_libdir} +%else +%configure \ + --enable-fatal-warnings=yes \ + --with-daemon-group=%{gname} \ + --with-daemon-user=%{uname} \ + --localstatedir=%{_var} \ + --libdir=%{_libdir} \ +%if %{defined _unitdir} + --with-systemdsystemunitdir=%{_unitdir} \ +%endif + --docdir=%{glue_docdir} +%endif + +%build +make %{?jobs:-j%jobs} docdir=%{glue_docdir} + +%install +rm -rf %{buildroot} +make install DESTDIR=%{buildroot} docdir=%{glue_docdir} + + +## tree fix up +# Dont package static libs +find %{buildroot} -name '*.a' -exec rm {} \; +find %{buildroot} -name '*.la' -exec rm {} \; + +%clean +rm -rf %{buildroot} + +# cluster-glue + +%description +A collection of common tools that are useful for writing cluster managers +such as Pacemaker. +Provides a local resource manager that understands the OCF and LSB +standards, and an interface to common STONITH devices. + +%files +%defattr(-,root,root) +%dir %{_datadir}/%{name} +%if %{defined _unitdir} +%{_unitdir}/logd.service +%else +%{_sysconfdir}/init.d/logd +%endif +%{_datadir}/%{name}/ha_cf_support.sh +%{_datadir}/%{name}/openais_conf_support.sh +%{_datadir}/%{name}/utillib.sh +%{_datadir}/%{name}/ha_log.sh + +%{_sbindir}/ha_logger +%{_sbindir}/hb_report +%{_sbindir}/lrmadmin +%{_sbindir}/cibsecret +%{_sbindir}/meatclient +%{_sbindir}/stonith +%dir %{_libdir}/heartbeat +%dir %{_libdir}/heartbeat/plugins +%dir %{_libdir}/heartbeat/plugins/RAExec +%dir %{_libdir}/heartbeat/plugins/InterfaceMgr +%dir %{_libdir}/heartbeat/plugins/compress +%{_libdir}/heartbeat/lrmd +%{_libdir}/heartbeat/ha_logd +%{_libdir}/heartbeat/plugins/RAExec/*.so +%{_libdir}/heartbeat/plugins/InterfaceMgr/*.so +%{_libdir}/heartbeat/plugins/compress/*.so +%dir %{_libdir}/stonith +%dir %{_libdir}/stonith/plugins +%dir %{_libdir}/stonith/plugins/stonith2 +%{_libdir}/stonith/plugins/external +%{_libdir}/stonith/plugins/stonith2/*.so +%{_libdir}/stonith/plugins/stonith2/*.py* +%exclude %{_libdir}/stonith/plugins/external/ssh +%exclude %{_libdir}/stonith/plugins/stonith2/null.so +%exclude %{_libdir}/stonith/plugins/stonith2/ssh.so +%{_libdir}/stonith/plugins/xen0-ha-dom0-stonith-helper +%dir %{_var}/lib/heartbeat +%dir %{_var}/lib/heartbeat/cores +%dir %attr (0700, root, root) %{_var}/lib/heartbeat/cores/root +%dir %attr (0700, nobody, %{nogroup}) %{_var}/lib/heartbeat/cores/nobody +%dir %attr (0700, %{uname}, %{gname}) %{_var}/lib/heartbeat/cores/%{uname} +%{_mandir}/man1/* +%{_mandir}/man8/* +%doc doc/stonith/README* +%doc logd/logd.cf +%doc AUTHORS +%doc COPYING +%doc ChangeLog + +# cluster-glue-libs + +%package -n cluster-glue-libs +Summary: Reusable cluster libraries +Group: Development/Libraries +Obsoletes: libheartbeat2 + +%description -n cluster-glue-libs +A collection of libraries that are useful for writing cluster managers +such as Pacemaker. + +%pre +getent group %{gname} >/dev/null || groupadd -r %{gname} +getent passwd %{uname} >/dev/null || \ +useradd -r -g %{gname} -d %{_var}/lib/heartbeat/cores/hacluster -s /sbin/nologin \ +-c "cluster user" %{uname} +exit 0 + +%if %{defined _unitdir} +%post +%systemd_post logd.service + +%preun +%systemd_preun logd.service + +%postun +%systemd_postun_with_restart logd.service +%endif + +%post -n cluster-glue-libs -p /sbin/ldconfig + +%postun -n cluster-glue-libs -p /sbin/ldconfig + +%files -n cluster-glue-libs +%defattr(-,root,root) +%{_libdir}/lib*.so.* +%doc AUTHORS +%doc COPYING.LIB + +# cluster-glue-libs-devel + +%package -n cluster-glue-libs-devel +Summary: Headers and libraries for writing cluster managers +Group: Development/Libraries +Requires: cluster-glue-libs = %{version}-%{release} +Obsoletes: libheartbeat-devel + +%description -n cluster-glue-libs-devel +Headers and shared libraries for a useful for writing cluster managers +such as Pacemaker. + +%files -n cluster-glue-libs-devel +%defattr(-,root,root) +%dir %{_libdir}/heartbeat/plugins +%dir %{_libdir}/heartbeat/plugins/test +%dir %{_libdir}/heartbeat +%dir %{_datadir}/%{name} +%{_libdir}/lib*.so +%{_libdir}/heartbeat/ipctest +%{_libdir}/heartbeat/ipctransientclient +%{_libdir}/heartbeat/ipctransientserver +%{_libdir}/heartbeat/transient-test.sh +%{_libdir}/heartbeat/base64_md5_test +%{_libdir}/heartbeat/logtest +%{_includedir}/clplumbing +%{_includedir}/heartbeat +%{_includedir}/stonith +%{_includedir}/pils +%{_datadir}/%{name}/lrmtest +%{_libdir}/heartbeat/plugins/test/test.so +%{_libdir}/stonith/plugins/external/ssh +%{_libdir}/stonith/plugins/stonith2/null.so +%{_libdir}/stonith/plugins/stonith2/ssh.so +%doc AUTHORS +%doc COPYING +%doc COPYING.LIB + +%changelog diff --git a/cluster-glue-suse.spec b/cluster-glue-suse.spec new file mode 100644 index 0000000..e2ca7c7 --- /dev/null +++ b/cluster-glue-suse.spec @@ -0,0 +1,307 @@ +# +# Copyright (c) 2009 SUSE LINUX Products GmbH, Nuernberg, Germany. +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + +# norootforbuild + +# +# Since this spec file supports multiple distributions, ensure we +# use the correct group for each. +# + +%define uid 90 +%define gname haclient +%define uname hacluster + +# Directory where we install documentation +%global glue_docdir %{_defaultdocdir}/%{name} + +Name: cluster-glue +Summary: Reusable cluster components +Version: 1.0.12 +Release: 1%{?dist} +License: GPL v2 or later; LGPL v2.1 or later +Url: http://www.linux-ha.org/wiki/Cluster_Glue +Group: Productivity/Clustering/HA +Source: cluster-glue.tar.bz2 +BuildRoot: %{_tmppath}/%{name}-%{version}-build +AutoReqProv: on +BuildRequires: automake autoconf libtool e2fsprogs-devel glib2-devel pkgconfig python-devel libxml2-devel +BuildRequires: libnet net-snmp-devel OpenIPMI-devel openhpi-devel +BuildRequires: libxslt docbook_4 docbook-xsl-stylesheets +BuildRequires: help2man +BuildRequires: asciidoc +BuildRequires: libbz2-devel libaio-devel + +Obsoletes: heartbeat-common +Provides: heartbeat-common +Requires(pre): /usr/sbin/groupadd /usr/bin/getent /usr/sbin/useradd + +# SLES10 needs tcpd-devel but doesn't have libcurl +# in SLES10 docbook has no dependency on sgml-skel +%if 0%{?suse_version} < 1020 +BuildRequires: tcpd-devel +BuildRequires: sgml-skel +%else +BuildRequires: libcurl-devel +%endif + +%if %{defined systemd_requires} +BuildRequires: systemd +%{?systemd_requires} +%endif + +%description +A collection of common tools derived from the Heartbeat project that are +useful for writing cluster managers such as Pacemaker. +Provides a local resource manager that understands the OCF and LSB +standards, and an interface to common STONITH devices. + +%package -n libglue2 +License: GPL v2 only; GPL v2 or later; LGPL v2.1 or later +Summary: The Pacemaker scalable High-Availability cluster resource manager +Group: Productivity/Clustering/HA +Obsoletes: libheartbeat2 +Provides: libheartbeat2 +Requires: %{name} = %{version}-%{release} + +%description -n libglue2 +A collection of libraries that are useful for writing cluster managers +such as Pacemaker. + +%package -n libglue-devel +License: GPL v2 only; GPL v2 or later; LGPL v2.1 or later +Summary: The Pacemaker scalable High-Availability cluster resource manager +Group: Development/Libraries/C and C++ +Requires: %{name} = %{version}-%{release} +Requires: libglue2 = %{version}-%{release} +Obsoletes: libheartbeat-devel +Provides: libheartbeat-devel + +%description -n libglue-devel +Headers and shared libraries for a useful for writing cluster managers +such as Pacemaker. + +%prep +########################################################### +%setup -n cluster-glue -q +########################################################### + +%build +CFLAGS="${CFLAGS} ${RPM_OPT_FLAGS}" +export CFLAGS + +./autogen.sh +# SLES <= 10 does not support ./configure --docdir=, +# hence, use this ugly hack +%if 0%{?suse_version} < 1020 +export docdir=%{glue_docdir} +%configure \ + --enable-fatal-warnings=yes \ + --with-package-name=%{name} \ + --with-daemon-group=%{gname} \ + --with-daemon-user=%{uname} +%else +%configure \ + --enable-fatal-warnings=yes \ + --with-package-name=%{name} \ + --with-daemon-group=%{gname} \ + --with-daemon-user=%{uname} \ + --with-rundir=%{_rundir} \ +%if %{defined _unitdir} + --with-systemdsystemunitdir=%{_unitdir} \ +%endif + --docdir=%{glue_docdir} +%endif + +make %{?_smp_mflags} docdir=%{glue_docdir} +########################################################### + +%install +########################################################### +make DESTDIR=$RPM_BUILD_ROOT docdir=%{glue_docdir} install +# Dont package static libs or compiled python +find $RPM_BUILD_ROOT -name '*.a' -type f -print0 | xargs -0 rm -f +find $RPM_BUILD_ROOT -name '*.la' -type f -print0 | xargs -0 rm -f +find $RPM_BUILD_ROOT -name '*.pyc' -type f -print0 | xargs -0 rm -f +find $RPM_BUILD_ROOT -name '*.pyo' -type f -print0 | xargs -0 rm -f + +%if %{defined _unitdir} +ln -s /usr/sbin/service %{buildroot}%{_sbindir}/rclogd +%else +test -d $RPM_BUILD_ROOT/sbin || mkdir $RPM_BUILD_ROOT/sbin +( + cd $RPM_BUILD_ROOT/sbin + ln -s /etc/init.d/logd rclogd +) +%endif + +########################################################### + +%clean +########################################################### +if + [ -n "${RPM_BUILD_ROOT}" -a "${RPM_BUILD_ROOT}" != "/" ] +then + rm -rf $RPM_BUILD_ROOT +fi +rm -rf $RPM_BUILD_DIR/cluster-glue +########################################################### + +%pre +if + getent group %{gname} >/dev/null +then + : OK group haclient already present +else + /usr/sbin/groupadd -o -r -g %{uid} %{gname} 2>/dev/null || : +fi +if + getent passwd %{uname} >/dev/null +then + : OK hacluster user already present +else + /usr/sbin/useradd -r -g %{gname} -c "heartbeat processes" \ + -d %{_var}/lib/heartbeat/cores/%{uname} -o -u %{uid} \ + %{uname} 2>/dev/null || : +fi +%if %{defined _unitdir} + %service_add_pre logd.service +%endif + +%if %{defined _unitdir} +%post +%service_add_post logd.service + +%preun +%service_del_preun logd.service + +%postun +%service_del_postun logd.service +%else +%preun +%stop_on_removal logd + +%post +%{insserv_force_if_yast logd} + +%postun +%insserv_cleanup +%endif + +%post -n libglue2 +/sbin/ldconfig + +%postun -n libglue2 +/sbin/ldconfig + +%files +########################################################### +%defattr(-,root,root) + +%dir %{_libdir}/heartbeat +%dir %{_var}/lib/heartbeat +%dir %{_var}/lib/heartbeat/cores +%dir %attr (0700, root, root) %{_var}/lib/heartbeat/cores/root +%dir %attr (0700, nobody, nobody) %{_var}/lib/heartbeat/cores/nobody +%dir %attr (0700, %{uname}, %{gname}) %{_var}/lib/heartbeat/cores/%{uname} + +%dir %{_libdir}/heartbeat/plugins +%dir %{_libdir}/heartbeat/plugins/RAExec +%dir %{_libdir}/heartbeat/plugins/InterfaceMgr +%dir %{_libdir}/heartbeat/plugins/compress + +%dir %{_libdir}/stonith +%dir %{_libdir}/stonith/plugins +%dir %{_libdir}/stonith/plugins/stonith2 + +%dir %{_datadir}/%{name} +%{_datadir}/%{name}/ha_cf_support.sh +%{_datadir}/%{name}/openais_conf_support.sh +%{_datadir}/%{name}/utillib.sh +%{_datadir}/%{name}/ha_log.sh + +%{_sbindir}/ha_logger +%{_sbindir}/hb_report +%{_sbindir}/lrmadmin +%{_sbindir}/cibsecret +%{_sbindir}/meatclient +%{_sbindir}/stonith + +%if %{defined _unitdir} +%{_unitdir}/logd.service +%{_sbindir}/rclogd +%else +%{_sysconfdir}/init.d/logd +/sbin/rclogd +%endif + +%doc %{_mandir}/man1/* +%doc %{_mandir}/man8/* +%doc AUTHORS +%doc COPYING +%doc ChangeLog +%doc logd/logd.cf +%doc doc/stonith/README* + +%{_libdir}/heartbeat/lrmd +%{_libdir}/heartbeat/ha_logd + +%{_libdir}/heartbeat/plugins/RAExec/*.so +%{_libdir}/heartbeat/plugins/InterfaceMgr/*.so +%{_libdir}/heartbeat/plugins/compress/*.so + +%{_libdir}/stonith/plugins/external +%{_libdir}/stonith/plugins/stonith2/*.so +%{_libdir}/stonith/plugins/stonith2/*.py +%{_libdir}/stonith/plugins/xen0-ha-dom0-stonith-helper +%exclude %{_libdir}/stonith/plugins/external/ssh +%exclude %{_libdir}/stonith/plugins/stonith2/null.so +%exclude %{_libdir}/stonith/plugins/stonith2/ssh.so + +%files -n libglue2 +%defattr(-,root,root) +%{_libdir}/lib*.so.* +%doc AUTHORS +%doc COPYING.LIB + +%files -n libglue-devel +%defattr(-,root,root) + +%dir %{_libdir}/heartbeat +%dir %{_libdir}/heartbeat/plugins +%dir %{_libdir}/heartbeat/plugins/test +%dir %{_datadir}/%{name} + +%{_libdir}/lib*.so +%{_libdir}/heartbeat/ipctest +%{_libdir}/heartbeat/ipctransientclient +%{_libdir}/heartbeat/ipctransientserver +%{_libdir}/heartbeat/transient-test.sh +%{_libdir}/heartbeat/base64_md5_test +%{_libdir}/heartbeat/logtest +%{_includedir}/clplumbing +%{_includedir}/heartbeat +%{_includedir}/stonith +%{_includedir}/pils +%{_datadir}/%{name}/lrmtest +%{_libdir}/heartbeat/plugins/test/test.so +%{_libdir}/stonith/plugins/external/ssh +%{_libdir}/stonith/plugins/stonith2/null.so +%{_libdir}/stonith/plugins/stonith2/ssh.so +%doc AUTHORS +%doc COPYING +%doc COPYING.LIB + +%changelog diff --git a/config/Makefile.am b/config/Makefile.am new file mode 100644 index 0000000..fa41516 --- /dev/null +++ b/config/Makefile.am @@ -0,0 +1,19 @@ +# +# Copyright (C) 2005 Guochun Shi (gshi@ncsa.uiuc.edu) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +MAINTAINERCLEANFILES = Makefile.in +EXTRA_DIST = byteorder_test.c diff --git a/config/byteorder_test.c b/config/byteorder_test.c new file mode 100644 index 0000000..0583803 --- /dev/null +++ b/config/byteorder_test.c @@ -0,0 +1,15 @@ +#include + +int +main () +{ + unsigned int a = 0x1234; + + if ( (unsigned int) ( ((unsigned char *)&a)[0]) == 0x34 ) { + printf("little-endian\n"); + return 0; + } else { + printf("big-endian\n"); + return 1; + } +} diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..36bcf12 --- /dev/null +++ b/configure.ac @@ -0,0 +1,1439 @@ +dnl +dnl autoconf for Pacemaker +dnl +dnl License: GNU General Public License (GPL) + +dnl =============================================== +dnl Bootstrap +dnl =============================================== +AC_PREREQ(2.53) + +dnl Suggested structure: +dnl information on the package +dnl checks for programs +dnl checks for libraries +dnl checks for header files +dnl checks for types +dnl checks for structures +dnl checks for compiler characteristics +dnl checks for library functions +dnl checks for system services + +AC_INIT(cluster-glue, 1.0.12, linux-ha-dev@lists.linux-ha.org) + +FEATURES="" +HB_PKG=heartbeat + +AC_CONFIG_AUX_DIR(.) +AC_CANONICAL_HOST + +dnl Where #defines go (e.g. `AC_CHECK_HEADERS' below) +dnl +dnl Internal header: include/config.h +dnl - Contains ALL defines +dnl - include/config.h.in is generated automatically by autoheader +dnl - NOT to be included in any header files except lha_internal.h +dnl (which is also not to be included in any other header files) +dnl +dnl External header: include/crm_config.h +dnl - Contains a subset of defines checked here +dnl - Manually edit include/crm_config.h.in to have configure include +dnl new defines +dnl - Should not include HAVE_* defines +dnl - Safe to include anywhere +AM_CONFIG_HEADER(include/config.h include/glue_config.h) +ALL_LINGUAS="en fr" + +AC_ARG_WITH(version, + [ --with-version=version Override package version (if you're a packager needing to pretend) ], + [ PACKAGE_VERSION="$withval" ]) + +AC_ARG_WITH(pkg-name, + [ --with-pkg-name=name Override package name (if you're a packager needing to pretend) ], + [ PACKAGE_NAME="$withval" ]) + +PKG_PROG_PKG_CONFIG +AC_ARG_WITH([systemdsystemunitdir], + [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],, + [with_systemdsystemunitdir=auto]) +AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [ + def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) + + AS_IF([test "x$def_systemdsystemunitdir" = "x"], + [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], + [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) + with_systemdsystemunitdir=no], + [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) +AS_IF([test "x$with_systemdsystemunitdir" != "xno"], + [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) +AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) + +AM_INIT_AUTOMAKE($PACKAGE_NAME, $PACKAGE_VERSION) +AC_DEFINE_UNQUOTED(GLUE_VERSION, "$PACKAGE_VERSION", Current version of the glue library) + +CC_IN_CONFIGURE=yes +export CC_IN_CONFIGURE + +LDD=ldd + +dnl ======================================================================== +dnl Compiler characteristics +dnl ======================================================================== + +AC_PROG_CC dnl Can force other with environment variable "CC". +AM_PROG_CC_C_O +AC_PROG_CC_STDC + +AC_LIBTOOL_DLOPEN dnl Enable dlopen support... +AC_LIBLTDL_CONVENIENCE dnl make libltdl a convenience lib +AC_PROG_LIBTOOL + +AC_C_STRINGIZE +AC_TYPE_SIZE_T +AC_CHECK_SIZEOF(char) +AC_CHECK_SIZEOF(short) +AC_CHECK_SIZEOF(int) +AC_CHECK_SIZEOF(long) +AC_CHECK_SIZEOF(long long) +AC_CHECK_SIZEOF(clock_t, [], [#include ]) +AC_STRUCT_TIMEZONE + +dnl =============================================== +dnl Helpers +dnl =============================================== +cc_supports_flag() { + local CFLAGS="$@" + AC_MSG_CHECKING(whether $CC supports "$@") + AC_COMPILE_IFELSE([AC_LANG_SOURCE(int main(){return 0;})] ,[RC=0; AC_MSG_RESULT(yes)],[RC=1; AC_MSG_RESULT(no)]) + return $RC +} + +dnl =============================================== +dnl Configure Options +dnl =============================================== + +dnl Some systems, like Solaris require a custom package name +AC_ARG_WITH(pkgname, + [ --with-pkgname=name name for pkg (typically for Solaris) ], + [ PKGNAME="$withval" ], + [ PKGNAME="LXHAhb" ], + ) +AC_SUBST(PKGNAME) + +AC_ARG_ENABLE([ansi], +[ --enable-ansi force GCC to compile to ANSI/ANSI standard for older compilers. + [default=yes]]) + +AC_ARG_ENABLE([fatal-warnings], +[ --enable-fatal-warnings very pedantic and fatal warnings for gcc + [default=yes]]) + +AC_ARG_ENABLE([pretty], +[ --enable-pretty + Pretty-print compiler output unless there is an error + [default=no]]) + +AC_ARG_ENABLE([quiet], +[ --enable-quiet + Supress make output unless there is an error + [default=no]]) + +AC_ARG_ENABLE([thread-safe], +[ --enable-thread-safe Enable some client libraries to be thread safe. + [default=no]]) + +AC_ARG_ENABLE([bundled-ltdl], +[ --enable-bundled-ltdl Configure, build and install the standalone ltdl library bundled with ${PACKAGE} [default=no]]) +LTDL_LIBS="" + +AC_ARG_ENABLE([upstart], +AS_HELP_STRING([--enable-upstart], + [Enable Upstart support in lrmd. [default=no]])) + +INITDIR="" +AC_ARG_WITH(initdir, + [ --with-initdir=DIR directory for init (rc) scripts [${INITDIR}]], + [ INITDIR="$withval" ]) + +OCF_ROOT_DIR="/usr/lib/ocf" +AC_ARG_WITH(ocf-root, + [ --with-ocf-root=DIR directory for OCF scripts [${OCF_ROOT_DIR}]], + [ if test x"$withval" = xprefix; then OCF_ROOT_DIR=${prefix}; else + OCF_ROOT_DIR="$withval"; fi ]) + +AC_ARG_WITH( + daemon-group, + [ --with-daemon-group=GROUP_NAME + Group to run our programs as. [default=haclient] ], + [ GLUE_DAEMON_GROUP="$withval" ], + [ GLUE_DAEMON_GROUP="haclient" ], + ) + +AC_ARG_WITH( + daemon-user, + [ --with-daemon-user=USER_NAME + User to run privileged non-root things as. [default=hacluster] ], + [ GLUE_DAEMON_USER="$withval" ], + [ GLUE_DAEMON_USER="hacluster" ], + ) + + +AC_ARG_WITH( + rundir, + [ --with-rundir=DIR + directory to store state information [default=localstatedir/run] ], + [ GLUE_STATE_DIR="$withval" ], + [ GLUE_STATE_DIR="${localstatedir}/run" ], + ) + +dnl =============================================== +dnl General Processing +dnl =============================================== + +AC_SUBST(HB_PKG) + +INIT_EXT="" +echo Our Host OS: $host_os/$host + +if test "X$OCF_ROOT_DIR" = X; then + OCF_ROOT_DIR="/usr/lib/ocf" +fi + +AC_MSG_NOTICE(Sanitizing prefix: ${prefix}) +case $prefix in + NONE) prefix=/usr;; +esac + +AC_MSG_NOTICE(Sanitizing exec_prefix: ${exec_prefix}) +case $exec_prefix in + dnl For consistency with Heartbeat, map NONE->$prefix + NONE) exec_prefix=$prefix;; + prefix) exec_prefix=$prefix;; +esac + +AC_MSG_NOTICE(Sanitizing INITDIR: ${INITDIR}) +case $INITDIR in + prefix) INITDIR=$prefix;; + "") + AC_MSG_CHECKING(which init (rc) directory to use) + for initdir in /etc/init.d /etc/rc.d/init.d /sbin/init.d \ + /usr/local/etc/rc.d /etc/rc.d + do + if + test -d $initdir + then + INITDIR=$initdir + break + fi + done + AC_MSG_RESULT($INITDIR);; +esac +AC_SUBST(INITDIR) + +AC_MSG_NOTICE(Sanitizing libdir: ${libdir}) +case $libdir in + dnl For consistency with Heartbeat, map NONE->$prefix + *prefix*|NONE) + AC_MSG_CHECKING(which lib directory to use) + for aDir in lib64 lib + do + trydir="${exec_prefix}/${aDir}" + if + test -d ${trydir} + then + libdir=${trydir} + break + fi + done + AC_MSG_RESULT($libdir); + ;; +esac + +DLOPEN_FORCE_FLAGS="" +AC_SUBST(DLOPEN_FORCE_FLAGS) + + +dnl Expand autoconf variables so that we dont end up with '${prefix}' +dnl in #defines and python scripts +dnl NOTE: Autoconf deliberately leaves them unexpanded to allow +dnl make exec_prefix=/foo install +dnl No longer being able to do this seems like no great loss to me... + +eval prefix="`eval echo ${prefix}`" +eval exec_prefix="`eval echo ${exec_prefix}`" +eval bindir="`eval echo ${bindir}`" +eval sbindir="`eval echo ${sbindir}`" +eval libexecdir="`eval echo ${libexecdir}`" +eval datadir="`eval echo ${datadir}`" +eval sysconfdir="`eval echo ${sysconfdir}`" +eval sharedstatedir="`eval echo ${sharedstatedir}`" +eval localstatedir="`eval echo ${localstatedir}`" +eval libdir="`eval echo ${libdir}`" +eval includedir="`eval echo ${includedir}`" +eval oldincludedir="`eval echo ${oldincludedir}`" +eval infodir="`eval echo ${infodir}`" +eval mandir="`eval echo ${mandir}`" + +dnl docdir is a recent addition to autotools +eval docdir="`eval echo ${docdir}`" +if test "x$docdir" = "x"; then + docdir="`eval echo ${datadir}/doc`" +fi +AC_SUBST(docdir) + +AC_MSG_CHECKING(for the location of the lock directory) +for HA_VARLOCKDIR in ${localstatedir}/lock ${localstatedir}/spool/lock ${localstatedir}/spool/locks ${localstatedir}/lock +do + if + test -d "$HA_VARLOCKDIR" + then + AC_MSG_RESULT($HA_VARLOCKDIR) + break + fi +done + +AC_SUBST(HA_VARLOCKDIR) +AC_DEFINE_UNQUOTED(HA_VARLOCKDIR,"$HA_VARLOCKDIR", System lock directory) + +dnl Home-grown variables +eval INITDIR="${INITDIR}" + +for j in prefix exec_prefix bindir sbindir libexecdir datadir sysconfdir \ + sharedstatedir localstatedir libdir includedir oldincludedir infodir \ + mandir INITDIR docdir HA_VARLOCKDIR +do + dirname=`eval echo '${'${j}'}'` + if + test ! -d "$dirname" + then + AC_MSG_WARN([$j directory ($dirname) does not exist!]) + fi +done + +dnl This OS-based decision-making is poor autotools practice; +dnl feature-based mechanisms are strongly preferred. +dnl +dnl So keep this section to a bare minimum; regard as a "necessary evil". + +ON_LINUX=0 +REBOOT_OPTIONS="-f" +POWEROFF_OPTIONS="-f" + +case "$host_os" in +*bsd*) LIBS="-L/usr/local/lib" + CPPFLAGS="$CPPFLAGS -I/usr/local/include" + INIT_EXT=".sh" + ;; +*solaris*) + REBOOT_OPTIONS="-n" + POWEROFF_OPTIONS="-n" + ;; +*linux*) + ON_LINUX=1 + REBOOT_OPTIONS="-nf" + POWEROFF_OPTIONS="-nf" + AC_DEFINE_UNQUOTED(ON_LINUX, $ON_LINUX, Compiling for Linux platform) + ;; +darwin*) + AC_DEFINE_UNQUOTED(ON_DARWIN, 1, Compiling for Darwin platform) + LIBS="$LIBS -L${prefix}/lib" + CFLAGS="$CFLAGS -I${prefix}/include" + ;; +esac + +AM_CONDITIONAL(ON_LINUX, test $ON_LINUX = 1) + +dnl Eventually remove this +dnl CFLAGS="$CFLAGS -I${prefix}/include/heartbeat" + +AC_SUBST(INIT_EXT) +AC_DEFINE_UNQUOTED(HA_LOG_FACILITY, LOG_DAEMON, Default logging facility) + +AC_MSG_NOTICE(Host CPU: $host_cpu) + +case "$host_cpu" in + ppc64|powerpc64) + case $CFLAGS in + *powerpc64*) ;; + *) if test "$GCC" = yes; then + CFLAGS="$CFLAGS -m64" + fi ;; + esac +esac + +AC_MSG_CHECKING(which format is needed to print uint64_t) +case "$host_cpu" in + s390x)U64T="%lu";; + *64*) U64T="%lu";; + *) U64T="%llu";; +esac +AC_MSG_RESULT($U64T) +AC_DEFINE_UNQUOTED(U64T, "$U64T", Correct printf format for logging uint64_t) + +dnl Variables needed for substitution +AC_DEFINE_UNQUOTED(GLUE_DAEMON_USER,"$GLUE_DAEMON_USER", User to run daemons as) +AC_SUBST(GLUE_DAEMON_USER) + +AC_DEFINE_UNQUOTED(GLUE_DAEMON_GROUP,"$GLUE_DAEMON_GROUP", Group to run daemons as) +AC_SUBST(GLUE_DAEMON_GROUP) + +dnl Eventually move out of the heartbeat dir tree and create symlinks when needed +GLUE_DAEMON_DIR=$libdir/heartbeat +AC_DEFINE_UNQUOTED(GLUE_DAEMON_DIR,"$GLUE_DAEMON_DIR", Location for daemons) +AC_SUBST(GLUE_DAEMON_DIR) + +GLUE_STATE_DIR=${localstatedir}/run +AC_DEFINE_UNQUOTED(GLUE_STATE_DIR,"$GLUE_STATE_DIR", Where to keep state files and sockets) +AC_SUBST(GLUE_STATE_DIR) + +GLUE_SHARED_DIR=${datadir}/"$PACKAGE_NAME" +AC_DEFINE_UNQUOTED(GLUE_SHARED_DIR,"$GLUE_SHARED_DIR", Location for scripts) +AC_SUBST(GLUE_SHARED_DIR) + +AC_DEFINE_UNQUOTED(HA_VARRUNDIR,"$GLUE_STATE_DIR", Where Heartbeat keeps state files and sockets - old name) +AC_SUBST(HA_VARRUNDIR) + +HA_VARLIBHBDIR=${localstatedir}/lib/heartbeat +AC_DEFINE_UNQUOTED(HA_VARLIBHBDIR,"$HA_VARLIBHBDIR", Whatever this used to mean) +AC_SUBST(HA_VARLIBHBDIR) +AC_DEFINE_UNQUOTED(HA_VARLIBDIR,"$HA_VARLIBHBDIR", Whatever this used to mean) +AC_SUBST(HA_VARLIBDIR) + +AC_DEFINE_UNQUOTED(OCF_ROOT_DIR,"$OCF_ROOT_DIR", OCF root directory - specified by the OCF standard) +AC_SUBST(OCF_ROOT_DIR) + +OCF_RA_DIR="${OCF_ROOT_DIR}/resource.d/" +AC_DEFINE_UNQUOTED(OCF_RA_DIR,"$OCF_RA_DIR", Location for OCF RAs) +AC_SUBST(OCF_RA_DIR) + +HA_LOGDAEMON_IPC="${localstatedir}/lib/heartbeat/log_daemon" +AC_DEFINE_UNQUOTED(HA_LOGDAEMON_IPC, "$HA_LOGDAEMON_IPC", Logging Daemon IPC socket name) +AC_SUBST(HA_LOGDAEMON_IPC) + +HA_URLBASE="http://linux-ha.org/wiki/" +AC_DEFINE_UNQUOTED(HA_URLBASE, "$HA_URLBASE", Web site base URL) +AC_SUBST(HA_URLBASE) + +HA_COREDIR="${localstatedir}/lib/heartbeat/cores" +AC_DEFINE_UNQUOTED(HA_COREDIR,"$HA_COREDIR", top directory of area to drop core files in) +AC_SUBST(HA_COREDIR) + +LRM_VARLIBDIR="${localstatedir}/lib/heartbeat/lrm" +AC_DEFINE_UNQUOTED(LRM_VARLIBDIR,"$LRM_VARLIBDIR", LRM directory) +AC_SUBST(LRM_VARLIBDIR) + +LRM_CIBSECRETS="${localstatedir}/lib/heartbeat/lrm/secrets" +AC_DEFINE_UNQUOTED(LRM_CIBSECRETS,"$LRM_CIBSECRETS", CIB secrets location) +AC_SUBST(LRM_CIBSECRETS) + +AC_DEFINE_UNQUOTED(PILS_BASE_PLUGINDIR,"$libdir/heartbeat/plugins", Default plugin search path) +AC_DEFINE_UNQUOTED(HA_PLUGIN_DIR,"$libdir/heartbeat/plugins", Where to find plugins) +AC_DEFINE_UNQUOTED(LRM_PLUGIN_DIR,"$libdir/heartbeat/plugins/RAExec", Where to find LRM plugins) + +AC_DEFINE_UNQUOTED(LSB_RA_DIR,"$INITDIR", Location for LSB RAs) +LSB_RA_DIR=$INITDIR +AC_SUBST(LSB_RA_DIR) + +AC_DEFINE_UNQUOTED(HA_SYSCONFDIR, "$sysconfdir", Location of system configuration files) + +HA_HBCONF_DIR=${sysconfdir}/ha.d/ +AC_DEFINE_UNQUOTED(HA_HBCONF_DIR,"$HA_HBCONF_DIR", Location for v1 Heartbeat configuration) +AC_SUBST(HA_HBCONF_DIR) + +HB_RA_DIR=${sysconfdir}/ha.d/resource.d/ +AC_DEFINE_UNQUOTED(HB_RA_DIR,"$HB_RA_DIR", Location for v1 Heartbeat RAs) +AC_SUBST(HB_RA_DIR) + +stonith_plugindir="${libdir}/stonith/plugins" +stonith_ext_plugindir="${stonith_plugindir}/external" +stonith_rhcs_plugindir="${stonith_plugindir}/rhcs" +AC_DEFINE_UNQUOTED(ST_TEXTDOMAIN, "stonith", Stonith plugin domain) +AC_DEFINE_UNQUOTED(STONITH_MODULES, "$stonith_plugindir", Location of stonith plugins) +AC_DEFINE_UNQUOTED(STONITH_EXT_PLUGINDIR, "$stonith_ext_plugindir", Location of non-plugin stonith scripts) +AC_DEFINE_UNQUOTED(STONITH_RHCS_PLUGINDIR, "$stonith_rhcs_plugindir", Location of RHCS fence scripts) +AC_SUBST(stonith_plugindir) +AC_SUBST(stonith_ext_plugindir) +AC_SUBST(stonith_rhcs_plugindir) + +dnl Old names for new things +AC_DEFINE_UNQUOTED(HA_CCMUSER, "$GLUE_DAEMON_USER", User to run daemons as) +AC_DEFINE_UNQUOTED(HA_APIGROUP, "$GLUE_DAEMON_GROUP", Group to run daemons as) +AC_DEFINE_UNQUOTED(HA_LIBHBDIR, "$GLUE_DAEMON_DIR", Location for daemons) + +LRM_DIR=lrm +AC_SUBST(LRM_DIR) + +AC_PATH_PROGS(HG, hg false) +AC_MSG_CHECKING(build version) +GLUE_BUILD_VERSION=unknown +if test -f $srcdir/.hg_archival.txt; then + GLUE_BUILD_VERSION=`cat $srcdir/.hg_archival.txt | awk '/node:/ { print $2 }'` +elif test -x $HG -a -d .hg; then + GLUE_BUILD_VERSION=`$HG id -itb` + if test $? != 0; then + GLUE_BUILD_VERSION=unknown + fi +fi + +AC_DEFINE_UNQUOTED(GLUE_BUILD_VERSION, "$GLUE_BUILD_VERSION", Build version) +AC_MSG_RESULT($GLUE_BUILD_VERSION) +AC_SUBST(GLUE_BUILD_VERSION) + +dnl check byte order +AC_MSG_CHECKING(for byteorder) +AC_C_BIGENDIAN( +[AC_MSG_RESULT(big-endian); AC_DEFINE(CONFIG_BIG_ENDIAN, 1, [big-endian])], +[AC_MSG_RESULT(little-endian); AC_DEFINE(CONFIG_LITTLE_ENDIAN, 1, [little-endian])], +) + + +dnl =============================================== +dnl Program Paths +dnl =============================================== + +PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin" +export PATH + + +dnl Replacing AC_PROG_LIBTOOL with AC_CHECK_PROG because LIBTOOL +dnl was NOT being expanded all the time thus causing things to fail. +AC_CHECK_PROGS(LIBTOOL, glibtool libtool libtool15 libtool13) + +AM_PATH_PYTHON +AC_CHECK_PROGS(MAKE, gmake make) +AC_PATH_PROGS(HTML2TXT, lynx w3m) +AC_PATH_PROGS(HELP2MAN, help2man) +AC_PATH_PROGS(POD2MAN, pod2man, pod2man) +AC_PATH_PROGS(SSH, ssh, /usr/bin/ssh) +AC_PATH_PROGS(SCP, scp, /usr/bin/scp) +AC_PATH_PROGS(HG, hg, /bin/false) +AC_PATH_PROGS(TAR, tar) +AC_PATH_PROGS(MD5, md5) +AC_PATH_PROGS(RPM, rpm) +AC_PATH_PROGS(TEST, test) +AC_PATH_PROGS(PING, ping, /bin/ping) +AC_PATH_PROGS(IFCONFIG, ifconfig, /sbin/ifconfig) +AC_PATH_PROGS(MAILCMD, mailx mail) +AC_PATH_PROGS(EGREP, egrep) +AC_PATH_PROGS(PKGCONFIG, pkg-config) +AC_PATH_PROGS(XML2CONFIG, xml2-config) + +AC_ARG_ENABLE([doc], + AS_HELP_STRING([--enable-doc], [build documentation (default is yes)]), + [], [enable_doc=yes]) +if test "x$enable_doc" != "xno"; then + AC_PATH_PROGS(XSLTPROC, xsltproc) + if test "x$XSLTPROC" = "x"; then + AC_MSG_WARN([xsltproc not installed, unable to (re-)build manual pages]) + fi + AC_PATH_PROGS(ASCIIDOC, asciidoc) + if test "x$ASCIIDOC" = "x"; then + AC_MSG_WARN([asciidoc not installed, unable to (re-)build manual pages]) + fi +fi +AM_CONDITIONAL(BUILD_DOC, test "x$XSLTPROC" != "x" ) + +AC_PATH_PROGS(VALGRIND_BIN, valgrind, /usr/bin/valgrind) +AC_DEFINE_UNQUOTED(VALGRIND_BIN, "$VALGRIND_BIN", Valgrind command) + +AC_SUBST(MAILCMD) +AC_SUBST(EGREP) +AC_SUBST(SHELL) +AC_SUBST(PING) +AC_SUBST(TEST) +AC_SUBST(RPM) +AC_SUBST(XSLTPROC) + +AC_MSG_CHECKING(ifconfig option to list interfaces) +for IFCONFIG_A_OPT in "-A" "-a" "" +do + $IFCONFIG $IFCONFIG_A_OPT > /dev/null 2>&1 + if + test "$?" = 0 + then + AC_DEFINE_UNQUOTED(IFCONFIG_A_OPT, "$IFCONFIG_A_OPT", option for ifconfig command) + AC_MSG_RESULT($IFCONFIG_A_OPT) + break + fi +done + +AC_SUBST(IFCONFIG_A_OPT) + +if test x"${LIBTOOL}" = x""; then + AC_MSG_ERROR(You need (g)libtool installed in order to build ${PACKAGE}) +fi +if test x"${MAKE}" = x""; then + AC_MSG_ERROR(You need (g)make installed in order to build ${PACKAGE}) +fi + +AM_CONDITIONAL(BUILD_HELP, test x"${HELP2MAN}" != x"") +if test x"${HELP2MAN}" != x""; then + FEATURES="$FEATURES manpages" +fi + +dnl =============================================== +dnl Libraries +dnl =============================================== +AC_CHECK_LIB(socket, socket) +AC_CHECK_LIB(c, dlopen) dnl if dlopen is in libc... +AC_CHECK_LIB(dl, dlopen) dnl for Linux +AC_CHECK_LIB(rt, sched_getscheduler) dnl for Tru64 +AC_CHECK_LIB(gnugetopt, getopt_long) dnl if available +AC_CHECK_LIB(uuid, uuid_parse) dnl e2fsprogs +AC_CHECK_LIB(uuid, uuid_create) dnl ossp +AC_CHECK_LIB(posix4, sched_getscheduler) + +if test x"${PKGCONFIG}" = x""; then + AC_MSG_ERROR(You need pkgconfig installed in order to build ${PACKAGE}) +fi + +dnl +dnl On many systems libcrypto is needed when linking against libsnmp. +dnl Check to see if it exists, and if so use it. +dnl +AC_CHECK_LIB(crypto, CRYPTO_free, CRYPTOLIB="-lcrypto",) +AC_SUBST(CRYPTOLIB) + +if test "x${enable_thread_safe}" = "xyes"; then + GPKGNAME="gthread-2.0" +else + GPKGNAME="glib-2.0" +fi + +if + $PKGCONFIG --exists $GPKGNAME +then + GLIBCONFIG="$PKGCONFIG $GPKGNAME" +else + set -x + echo PKG_CONFIG_PATH=$PKG_CONFIG_PATH + $PKGCONFIG --exists $GPKGNAME; echo $? + $PKGCONFIG --cflags $GPKGNAME; echo $? + $PKGCONFIG $GPKGNAME; echo $? + set +x + + AC_MSG_ERROR(You need glib2-devel installed in order to build ${PACKAGE}) +fi +AC_MSG_RESULT(using $GLIBCONFIG) + +# +# Where is dlopen? +# +if test "$ac_cv_lib_c_dlopen" = yes; then + LIBADD_DL="" +elif test "$ac_cv_lib_dl_dlopen" = yes; then + LIBADD_DL=-ldl +else + LIBADD_DL=${lt_cv_dlopen_libs} +fi +dnl +dnl Check for location of gettext +dnl +dnl On at least Solaris 2.x, where it is in libc, specifying lintl causes +dnl grief. Ensure minimal result, not the sum of all possibilities. +dnl And do libc first. +dnl Known examples: +dnl c: Linux, Solaris 2.6+ +dnl intl: BSD, AIX + +AC_CHECK_LIB(c, gettext) +if test x$ac_cv_lib_c_gettext != xyes; then + AC_CHECK_LIB(intl, gettext) +fi + +if test x$ac_cv_lib_c_gettext != xyes -a x$ac_cv_lib_intl_gettext != xyes; then + AC_MSG_ERROR(You need gettext installed in order to build ${PACKAGE}) +fi + +if test "X$GLIBCONFIG" != X; then + AC_MSG_CHECKING(for special glib includes: ) + GLIBHEAD=`$GLIBCONFIG --cflags` + AC_MSG_RESULT($GLIBHEAD) + CPPFLAGS="$CPPFLAGS $GLIBHEAD" + + AC_MSG_CHECKING(for glib library flags) + GLIBLIB=`$GLIBCONFIG --libs` + AC_MSG_RESULT($GLIBLIB) + LIBS="$LIBS $GLIBLIB" +fi + +dnl ======================================================================== +dnl Headers +dnl ======================================================================== + +AC_HEADER_STDC +AC_CHECK_HEADERS(arpa/inet.h) +AC_CHECK_HEADERS(asm/types.h) +AC_CHECK_HEADERS(assert.h) +AC_CHECK_HEADERS(auth-client.h) +AC_CHECK_HEADERS(ctype.h) +AC_CHECK_HEADERS(dirent.h) +AC_CHECK_HEADERS(errno.h) +AC_CHECK_HEADERS(fcntl.h) +AC_CHECK_HEADERS(getopt.h) +AC_CHECK_HEADERS(glib.h) +AC_CHECK_HEADERS(grp.h) +AC_CHECK_HEADERS(limits.h) +AC_CHECK_HEADERS(linux/errqueue.h,,, + [#ifdef HAVE_LINUX_TYPES_H + # include + #endif + ]) +AC_CHECK_HEADERS(malloc.h) +AC_CHECK_HEADERS(netdb.h) +AC_CHECK_HEADERS(netinet/in.h) +AC_CHECK_HEADERS(netinet/ip.h) +AC_CHECK_HEADERS(pthread.h) +AC_CHECK_HEADERS(pwd.h) +AC_CHECK_HEADERS(sgtty.h) +AC_CHECK_HEADERS(signal.h) +AC_CHECK_HEADERS(stdarg.h) +AC_CHECK_HEADERS(stddef.h) +AC_CHECK_HEADERS(stdio.h) +AC_CHECK_HEADERS(stdlib.h) +AC_CHECK_HEADERS(string.h) +AC_CHECK_HEADERS(strings.h) +AC_CHECK_HEADERS(sys/dir.h) +AC_CHECK_HEADERS(sys/ioctl.h) +AC_CHECK_HEADERS(sys/param.h) +AC_CHECK_HEADERS(sys/poll.h) +AC_CHECK_HEADERS(sys/reboot.h) +AC_CHECK_HEADERS(sys/resource.h) +AC_CHECK_HEADERS(sys/select.h) +AC_CHECK_HEADERS(sys/socket.h) +AC_CHECK_HEADERS(sys/sockio.h) +AC_CHECK_HEADERS(sys/stat.h) +AC_CHECK_HEADERS(sys/time.h) +AC_CHECK_HEADERS(sys/timeb.h) +AC_CHECK_HEADERS(sys/types.h) +AC_CHECK_HEADERS(sys/uio.h) +AC_CHECK_HEADERS(sys/un.h) +AC_CHECK_HEADERS(sys/utsname.h) +AC_CHECK_HEADERS(sys/wait.h) +AC_CHECK_HEADERS(time.h) +AC_CHECK_HEADERS(unistd.h) +AC_CHECK_HEADERS(winsock.h) +AC_CHECK_HEADERS(sys/termios.h) +AC_CHECK_HEADERS(termios.h) + +dnl These headers need prerequisits before the tests will pass +dnl AC_CHECK_HEADERS(net/if.h) +dnl AC_CHECK_HEADERS(netinet/icmp6.h) +dnl AC_CHECK_HEADERS(netinet/ip6.h) +dnl AC_CHECK_HEADERS(netinet/ip_icmp.h) + +AC_MSG_CHECKING(for special libxml2 includes) +if test "x$XML2CONFIG" = "x"; then + AC_MSG_ERROR(libxml2 config not found) +else + XML2HEAD="`$XML2CONFIG --cflags`" + AC_MSG_RESULT($XML2HEAD) + AC_CHECK_LIB(xml2, xmlReadMemory) +fi + +CPPFLAGS="$CPPFLAGS $XML2HEAD" + +AC_CHECK_HEADERS(libxml/xpath.h) +if test "$ac_cv_header_libxml_xpath_h" != "yes"; then + AC_MSG_ERROR(The libxml developement headers were not found) +fi + +dnl Check syslog.h for 'facilitynames' table +dnl +AC_CACHE_CHECK([for facilitynames in syslog.h],ac_cv_HAVE_SYSLOG_FACILITYNAMES,[ +AC_TRY_COMPILE([ +#define SYSLOG_NAMES +#include +#include +], +[ void *fnames; fnames = facilitynames; ], +ac_cv_HAVE_SYSLOG_FACILITYNAMES=yes,ac_cv_HAVE_SYSLOG_FACILITYNAMES=no,ac_cv_HAVE_SYSLOG_FACILITYNAMES=cross)]) +if test x"$ac_cv_HAVE_SYSLOG_FACILITYNAMES" = x"yes"; then + AC_DEFINE(HAVE_SYSLOG_FACILITYNAMES,1,[ ]) +fi + +dnl Check for POSIX signals +dnl +AC_CACHE_CHECK([have POSIX signals],ac_cv_HAVE_POSIX_SIGNALS,[ +AC_TRY_COMPILE([ +#include +], +[ struct sigaction act, oact; sigaction(0, &act, &oact); return 0;], +ac_cv_HAVE_POSIX_SIGNALS=yes,ac_cv_HAVE_POSIX_SIGNALS=no,ac_cv_HAVE_POSIX_SIGNALS=cross)]) +if test x"$ac_cv_HAVE_POSIX_SIGNALS" = x"yes"; then + AC_DEFINE(HAVE_POSIX_SIGNALS,1,[ ]) +fi + +dnl 'reboot()' system call: one argument (e.g. Linux) or two (e.g. Solaris)? +dnl +AC_CACHE_CHECK([number of arguments in reboot system call], + ac_cv_REBOOT_ARGS,[ + AC_TRY_COMPILE( + [#include ], + [(void)reboot(0);], + ac_cv_REBOOT_ARGS=1, + [AC_TRY_COMPILE( + [#include ], + [(void)reboot(0,(void *)0);], + ac_cv_REBOOT_ARGS=2, + ac_cv_REBOOT_ARGS=0 + )], + ac_cv_REBOOT_ARGS=0 + ) + ] +) +dnl Argument count of 0 suggests no known 'reboot()' call. +if test "$ac_cv_REBOOT_ARGS" -ge "1"; then + AC_DEFINE_UNQUOTED(REBOOT_ARGS,$ac_cv_REBOOT_ARGS,[number of arguments for reboot system call]) +fi + +AC_PATH_PROGS(REBOOT, reboot, /sbin/reboot) +AC_SUBST(REBOOT) +AC_SUBST(REBOOT_OPTIONS) +AC_DEFINE_UNQUOTED(REBOOT, "$REBOOT", path to the reboot command) +AC_DEFINE_UNQUOTED(REBOOT_OPTIONS, "$REBOOT_OPTIONS", reboot options) + +AC_PATH_PROGS(POWEROFF_CMD, poweroff, /sbin/poweroff) +AC_SUBST(POWEROFF_CMD) +AC_SUBST(POWEROFF_OPTIONS) +AC_DEFINE_UNQUOTED(POWEROFF_CMD, "$POWEROFF_CMD", path to the poweroff command) +AC_DEFINE_UNQUOTED(POWEROFF_OPTIONS, "$POWEROFF_OPTIONS", poweroff options) + +dnl Sockets are our preferred and supported comms mechanism. But the +dnl implementation needs to be able to convey credentials: some don't. +dnl So on a few OSes, credentials-carrying streams might be a better choice. +dnl +dnl Solaris releases up to and including "9" fall into this category +dnl (its sockets don't carry credentials; streams do). +dnl +dnl At Solaris 10, "getpeerucred()" is available, for both sockets and +dnl streams, so it should probably use (preferred) socket mechanism. + +AC_CHECK_HEADERS(stropts.h) dnl streams available (fallback option) + +AC_CHECK_HEADERS(ucred.h) dnl e.g. Solaris 10 decl. of "getpeerucred()" +AC_CHECK_FUNCS(getpeerucred) + +dnl ************************************************************************ +dnl checks for headers needed by clplumbing On BSD +AC_CHECK_HEADERS(sys/syslimits.h) +if test "$ac_cv_header_sys_param_h" = no; then + AC_CHECK_HEADERS(sys/ucred.h) +else + AC_CHECK_HEADERS(sys/ucred.h,[],[],[#include ]) +fi + +dnl ************************************************************************ +dnl checks for headers needed by clplumbing On Solaris +AC_CHECK_HEADERS(sys/cred.h xti.h) + +dnl ************************************************************************ +dnl checks for headers needed by clplumbing On FreeBSD/Solaris +AC_CHECK_HEADERS(sys/filio.h) + +dnl ======================================================================== +dnl Structures +dnl ======================================================================== + +AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,[[#include ]]) +AC_CHECK_TYPES([nfds_t],,,[[#include ]]) + +AC_MSG_CHECKING(if clock_t is long enough) +if test $ac_cv_sizeof_clock_t -ge 8; then + AC_MSG_RESULT(yes) + AC_DEFINE(CLOCK_T_IS_LONG_ENOUGH, 1, [Set if CLOCK_T is adequate by itself for the "indefinite future" (>= 100 years)]) +else + AC_MSG_RESULT(no) +fi + +dnl ======================================================================== +dnl Functions +dnl ======================================================================== + +AC_CHECK_FUNCS(g_log_set_default_handler) +AC_CHECK_FUNCS(getopt, AC_DEFINE(HAVE_DECL_GETOPT, 1, [Have getopt function])) +AC_CHECK_FUNCS(getpeereid) + +dnl ********************************************************************** +dnl Check for various argv[] replacing functions on various OSs +dnl +dnl Borrowed from Proftpd +dnl Proftpd is Licenced under the terms of the GNU General Public Licence +dnl and is available from http://www.proftpd.org/ +dnl + +AC_CHECK_FUNCS(setproctitle) +AC_CHECK_HEADERS(libutil.h) +AC_CHECK_LIB(util, setproctitle, + [AC_DEFINE(HAVE_SETPROCTITLE,1,[ ]) + ac_cv_func_setproctitle="yes" ; LIBS="$LIBS -lutil"]) + +if test "$ac_cv_func_setproctitle" = "yes"; then + pf_argv_set="PF_ARGV_NONE" +fi + +if test "$pf_argv_set" = ""; then + AC_CHECK_HEADERS(sys/pstat.h) + if test "$ac_cv_header_pstat_h" = "yes"; then + AC_CHECK_FUNCS(pstat) + + if test "$ac_cv_func_pstat" = "yes"; then + pf_argv_set="PF_ARGV_PSTAT" + else + pf_argv_set="PF_ARGV_WRITEABLE" + fi + fi + + if test "$pf_argv_set" = ""; then + AC_EGREP_HEADER([#define.*PS_STRINGS.*],sys/exec.h, + have_psstrings="yes",have_psstrings="no") + if test "$have_psstrings" = "yes"; then + pf_argv_set="PF_ARGV_PSSTRINGS" + fi + fi + + if test "$pf_argv_set" = ""; then + AC_CACHE_CHECK(whether __progname and __progname_full are available, + pf_cv_var_progname, + AC_TRY_LINK([extern char *__progname, *__progname_full;], + [__progname = "foo"; __progname_full = "foo bar";], + pf_cv_var_progname="yes", pf_cv_var_progname="no")) + + if test "$pf_cv_var_progname" = "yes"; then + AC_DEFINE(HAVE___PROGNAME,1,[ ]) + fi + + AC_CACHE_CHECK(which argv replacement method to use, + pf_cv_argv_type, + AC_EGREP_CPP(yes,[ +#if defined(__GNU_HURD__) + yes +#endif + ],pf_cv_argv_type="new", pf_cv_argv_type="writeable")) + + if test "$pf_cv_argv_type" = "new"; then + pf_argv_set="PF_ARGV_NEW" + fi + + if test "$pf_argv_set" = ""; then + pf_argv_set="PF_ARGV_WRITEABLE" + fi + fi +fi +AC_DEFINE_UNQUOTED(PF_ARGV_TYPE, $pf_argv_set, + mechanism to pretty-print ps output: setproctitle-equivalent) + +dnl End of tests borrowed from Proftpd + +dnl ======================================================================== +dnl ltdl +dnl ======================================================================== + +AC_CHECK_LIB(ltdl, lt_dlopen, [LTDL_foo=1]) +if test "x${enable_bundled_ltdl}" = "xyes"; then + if test $ac_cv_lib_ltdl_lt_dlopen = yes; then + AC_MSG_NOTICE([Disabling usage of installed ltdl]) + fi + ac_cv_lib_ltdl_lt_dlopen=no +fi + +LIBLTDL_DIR="" +if test $ac_cv_lib_ltdl_lt_dlopen != yes ; then + AC_MSG_NOTICE([Installing local ltdl]) + LIBLTDL_DIR=libltdl + ( cd $srcdir ; $TAR -xvf libltdl.tar ) + if test "$?" -ne 0; then + AC_MSG_ERROR([$TAR of libltdl.tar in $srcdir failed]) + fi + AC_CONFIG_SUBDIRS(libltdl) +else + LIBS="$LIBS -lltdl" + AC_MSG_NOTICE([Using installed ltdl]) + INCLTDL="" + LIBLTDL="" +fi + +AC_SUBST(INCLTDL) +AC_SUBST(LIBLTDL) +AC_SUBST(LIBLTDL_DIR) + +dnl ======================================================================== +dnl libnet +dnl ======================================================================== + +AC_ARG_ENABLE([libnet], + [ --enable-libnet Use libnet for ARP based funcationality, [default=try]], + [], [enable_libnet=try]) + +libnet="" +libnet_version="none" +LIBNETLIBS="" +LIBNETDEFINES="" + +AC_MSG_CHECKING(if libnet is required) +libnet_fatal=$enable_libnet +case $enable_libnet in + no) ;; + yes|libnet10|libnet11|10|11) libnet_fatal=yes;; + try) + case $host_os in + *Linux*|*linux*) libnet_fatal=no;; + *) libnet_fatal=yes;; dnl legacy behavior + esac + ;; + *) libnet_fatal=yes; enable_libnet=try;; +esac +AC_MSG_RESULT($libnet_fatal) + +if test "x$enable_libnet" != "xno"; then + AC_PATH_PROGS(LIBNETCONFIG, libnet-config) + + AC_CHECK_LIB(nsl, t_open) dnl -lnsl + AC_CHECK_LIB(socket, socket) dnl -lsocket + AC_CHECK_LIB(net, libnet_get_hwaddr, LIBNETLIBS=" -lnet", []) + fi + +AC_MSG_CHECKING(for libnet) +if test "x$LIBNETLIBS" != "x" -o "x$enable_libnet" = "xlibnet11"; then + LIBNETDEFINES="" + if test "$ac_cv_lib_nsl_t_open" = yes; then + LIBNETLIBS="-lnsl $LIBNETLIBS" + fi + if test "$ac_cv_lib_socket_socket" = yes; then + LIBNETLIBS="-lsocket $LIBNETLIBS" + fi + + libnet=net + libnet_version="libnet1.1" +fi + +if test "x$enable_libnet" = "xtry" -o "x$enable_libnet" = "xlibnet10"; then + if test "x$LIBNETLIBS" = x -a "x${LIBNETCONFIG}" != "x" ; then + LIBNETDEFINES="`$LIBNETCONFIG --defines` `$LIBNETCONFIG --cflags`"; + LIBNETLIBS="`$LIBNETCONFIG --libs`"; + libnet_version="libnet1.0 (old)" + case $LIBNETLIBS in + *-l*) libnet=`echo $LIBNETLIBS | sed 's%.*-l%%'`;; + *) libnet_version=none;; + esac + + CPPFLAGS="$CPPFLAGS $LIBNETDEFINES" + + AC_CHECK_HEADERS(libnet.h) + if test "$ac_cv_header_libnet_h" = no; then + libnet_version=none + fi + fi +fi +AC_MSG_RESULT(found $libnet_version) + +if test "$libnet_version" = none; then + LIBNETLIBS="" + LIBNETDEFINES="" + if test $libnet_fatal = yes; then + AC_MSG_ERROR(libnet not found) + fi + +else + AC_CHECK_LIB($libnet,libnet_init, + [new_libnet=yes; AC_DEFINE(HAVE_LIBNET_1_1_API, 1, Libnet 1.1 API)], + [new_libnet=no; AC_DEFINE(HAVE_LIBNET_1_0_API, 1, Libnet 1.0 API)],$LIBNETLIBS) +fi + +dnl ************************************************************************ +dnl * Check for netinet/icmp6.h to enable the IPv6addr resource agent +AC_CHECK_HEADERS(netinet/icmp6.h,[],[],[#include ]) +AM_CONDITIONAL(USE_IPV6ADDR, test "$ac_cv_header_netinet_icmp6_h" = yes -a "$new_libnet" = yes ) + + +dnl ======================================================================== +dnl SNMP +dnl ======================================================================== + +SNMPLIB="" +SNMPCONFIG="" + +ENABLE_SNMP="yes" +if test "x${enable_snmp}" = "xno"; then + ENABLE_SNMP="no" +fi + +AC_CHECK_HEADERS(ucd-snmp/snmp.h,[],[],[#include +#include ]) +AC_CHECK_HEADERS(net-snmp/net-snmp-config.h) + +if test "x${ENABLE_SNMP}" = "xno"; then + # nothing + : +elif test "x${ac_cv_header_net_snmp_net_snmp_config_h}" = "xyes"; then + AC_PATH_PROGS(SNMPCONFIG, net-snmp-config) + if test "X${SNMPCONFIG}" = "X"; then + AC_MSG_RESULT(You need the net_snmp development package to continue.) + ENABLE_SNMP="no" + else + AC_MSG_CHECKING(for special snmp libraries) + SNMPLIB=`${SNMPCONFIG} --libs` + AC_MSG_RESULT($SNMPLIB) + fi +elif test "x${ac_cv_header_ucd_snmp_snmp_h}" = "xyes"; then + # UCD SNMP + # ucd-snmp-config does not seem to exist, so just + # rely on people having their LDFLAGS set to the path where + AC_CHECK_LIB(snmp, init_snmp, SNMPLIB="-lsnmp") + if test "X${SNMPLIB}" = "X"; then + AC_CHECK_LIB(ucdsnmp, init_snmp, SNMPLIB="-lucdsnmp") + fi + if test "X${SNMPLIB}" = "X"; then + ENABLE_SNMP="no" + AC_MSG_RESULT("Could not find ucdsnmp libary." + "Please make sure that libsnmp or libucdsnmp" + "are in your library path. Or the path to LDFLAGS") + fi +else + ENABLE_SNMP="no" +fi + +AC_SUBST(SNMPLIB) + +dnl ======================================================================== +dnl Stonith Devices +dnl ======================================================================== + +if test "x${enable_ipmilan}" = "x"; then + enable_ipmilan="yes" +fi +if test "x${enable_ipmilan}" = "xyes" -o "x${enable_ipmilan}" = "xtry"; then + AC_MSG_CHECKING(For libOpenIPMI version 1.4 or greater) + AC_TRY_COMPILE([#include ], + [ #if (OPENIPMI_VERSION_MAJOR == 1) && (OPENIPMI_VERSION_MINOR < 4) + #error "Too Old" + #endif ], + AC_MSG_RESULT("yes"); enable_ipmilan="yes", + AC_MSG_RESULT("no"); enable_ipmilan="no") +else + enable_ipmilan="no" +fi + +AC_CHECK_HEADERS(curl/curl.h) +AC_CHECK_HEADERS(openhpi/SaHpi.h) +AC_CHECK_HEADERS(vacmclient_api.h) + +AM_CONDITIONAL(USE_APC_SNMP, test "$ENABLE_SNMP" = "yes") +AM_CONDITIONAL(USE_VACM, test "$ac_cv_header_vacmclient_api_h" = yes) +AM_CONDITIONAL(USE_DRAC3, test "$ac_cv_header_curl_curl_h" = yes -a "$ac_cv_header_libxml_xpath_h" = yes) +AM_CONDITIONAL(USE_OPENHPI, test "$ac_cv_header_openhpi_SaHpi_h" = yes && pkg-config --atleast-version 2.6 openhpi) +AM_CONDITIONAL(IPMILAN_BUILD, test "X$enable_ipmilan" = "Xyes") + +dnl ======================================================================== +dnl ZLIB and BZ2 +dnl ======================================================================== + +dnl check if header file and lib are there for zlib +zlib_installed="yes" +AC_CHECK_HEADERS(zlib.h, , [zlib_installed="no"],) +AC_CHECK_LIB(z, compress , , [zlib_installed="no"]) +AM_CONDITIONAL(BUILD_ZLIB_COMPRESS_MODULE, test "x${zlib_installed}" = "xyes") +if test "x${zlib_installed}" = "xno"; then + FatalMissingThing "zlib" \ + "The zlib library is missing" +fi + +bz2_installed="yes" +AC_CHECK_HEADERS(bzlib.h, , [bz2_installed="no"],) +AC_CHECK_LIB(bz2, BZ2_bzBuffToBuffCompress , , [bz2_installed="no"]) +AM_CONDITIONAL(BUILD_BZ2_COMPRESS_MODULE, test "x${bz2_installed}" = "xyes") + +#if test x$ac_cv_lib_bz2_BZ2_bzBuffToBuffCompress != xyes ; then +# AC_MSG_ERROR(BZ2 libraries not found) +#fi + +if test x$ac_cv_header_bzlib_h != xyes; then + AC_MSG_ERROR(BZ2 Development headers not found) +fi + +dnl ======================================================================== +dnl Upstart via DBus +dnl ======================================================================== + +if test x$enable_upstart = xyes; then + PKG_CHECK_MODULES(DBUS, [dbus-1, dbus-glib-1]) + AC_SUBST(DBUS_CFLAGS) + AC_SUBST(DBUS_LIBS) + AC_PATH_PROGS(DBUS_BINDING_TOOL, dbus-binding-tool) +fi +AM_CONDITIONAL(UPSTART, test x$enable_upstart = xyes) + + +dnl ======================================================================== +dnl checks for library functions to replace them +dnl +dnl NoSuchFunctionName: +dnl is a dummy function which no system supplies. It is here to make +dnl the system compile semi-correctly on OpenBSD which doesn't know +dnl how to create an empty archive +dnl +dnl scandir: Only on BSD. +dnl System-V systems may have it, but hidden and/or deprecated. +dnl A replacement function is supplied for it. +dnl +dnl setenv: is some bsdish function that should also be avoided (use +dnl putenv instead) +dnl On the other hand, putenv doesn't provide the right API for the +dnl code and has memory leaks designed in (sigh...) Fortunately this +dnl A replacement function is supplied for it. +dnl +dnl strerror: returns a string that corresponds to an errno. +dnl A replacement function is supplied for it. +dnl +dnl unsetenv: is some bsdish function that should also be avoided (No +dnl replacement) +dnl A replacement function is supplied for it. +dnl +dnl strnlen: is a gnu function similar to strlen, but safer. +dnl We wrote a tolearably-fast replacement function for it. +dnl +dnl strndup: is a gnu function similar to strdup, but safer. +dnl We wrote a tolearably-fast replacement function for it. +dnl +dnl daemon: is a GNU function. The daemon() function is for programs wishing to +dnl detach themselves from the controlling terminal and run in the +dnl background as system daemon +dnl A replacement function is supplied for it. + +AC_REPLACE_FUNCS(alphasort inet_pton NoSuchFunctionName scandir setenv strerror unsetenv strnlen strndup daemon strlcpy strlcat) + +dnl ======================================================================== +dnl Compiler flags +dnl ======================================================================== + +dnl Make sure that CFLAGS is not exported. If the user did +dnl not have CFLAGS in their environment then this should have +dnl no effect. However if CFLAGS was exported from the user's +dnl environment, then the new CFLAGS will also be exported +dnl to sub processes. + +CC_ERRORS="" +CC_EXTRAS="" + +if export | fgrep " CFLAGS=" > /dev/null; then + SAVED_CFLAGS="$CFLAGS" + unset CFLAGS + CFLAGS="$SAVED_CFLAGS" + unset SAVED_CFLAGS +fi + +if test "$GCC" != yes; then + CFLAGS="$CFLAGS -g" + enable_fatal_warnings=no +else + CFLAGS="$CFLAGS -ggdb" + + # We had to eliminate -Wnested-externs because of libtool changes + EXTRA_FLAGS="-fgnu89-inline + -fstack-protector-all + -Wall + -Waggregate-return + -Wbad-function-cast + -Wcast-qual + -Wcast-align + -Wdeclaration-after-statement + -Wendif-labels + -Wfloat-equal + -Wformat=2 + -Wformat-security + -Wformat-nonliteral + -Winline + -Wmissing-prototypes + -Wmissing-declarations + -Wmissing-format-attribute + -Wnested-externs + -Wno-long-long + -Wno-strict-aliasing + -Wpointer-arith + -Wstrict-prototypes + -Wunsigned-char + -Wwrite-strings" + +# Additional warnings it might be nice to enable one day +# -Wshadow +# -Wunreachable-code + + for j in $EXTRA_FLAGS + do + if + cc_supports_flag $j + then + CC_EXTRAS="$CC_EXTRAS $j" + fi + done + +dnl In lib/ais/Makefile.am there's a gcc option available as of v4.x + + GCC_MAJOR=`gcc -v 2>&1 | awk 'END{print $3}' | sed 's/[.].*//'` + AM_CONDITIONAL(GCC_4, test "${GCC_MAJOR}" = 4) + +dnl System specific options + + case "$host_os" in + *linux*|*bsd*) + if test "${enable_fatal_warnings}" = "unknown"; then + enable_fatal_warnings=yes + fi + ;; + esac + + if test "x${enable_fatal_warnings}" != xno && cc_supports_flag -Werror ; then + enable_fatal_warnings=yes + else + enable_fatal_warnings=no + fi + + if test "x${enable_ansi}" != xno && cc_supports_flag -std=iso9899:199409 ; then + AC_MSG_NOTICE(Enabling ANSI Compatibility) + CC_EXTRAS="$CC_EXTRAS -ansi -D_GNU_SOURCE -DANSI_ONLY" + fi + + AC_MSG_NOTICE(Activated additional gcc flags: ${CC_EXTRAS}) +fi + +CFLAGS="$CFLAGS $CC_EXTRAS" + +NON_FATAL_CFLAGS="$CFLAGS" +AC_SUBST(NON_FATAL_CFLAGS) + +dnl +dnl We reset CFLAGS to include our warnings *after* all function +dnl checking goes on, so that our warning flags don't keep the +dnl AC_*FUNCS() calls above from working. In particular, -Werror will +dnl *always* cause us troubles if we set it before here. +dnl +dnl +if test "x${enable_fatal_warnings}" = xyes ; then + AC_MSG_NOTICE(Enabling Fatal Warnings) + CFLAGS="$CFLAGS -Werror" +fi +AC_SUBST(CFLAGS) + +dnl This is useful for use in Makefiles that need to remove one specific flag +CFLAGS_COPY="$CFLAGS" +AC_SUBST(CFLAGS_COPY) + +AC_SUBST(LIBADD_DL) dnl extra flags for dynamic linking libraries +AC_SUBST(LIBADD_INTL) dnl extra flags for GNU gettext stuff... + +AC_SUBST(LOCALE) + +dnl Options for cleaning up the compiler output +PRETTY_CC="" +QUIET_LIBTOOL_OPTS="" +QUIET_MAKE_OPTS="" +if test x"${enable_pretty}" = "xyes"; then + enable_quiet="yes" + echo "install_sh: ${install_sh}" + PRETTY_CC="`pwd`/tools/ccdv" + dnl It would be nice if this was rebuilt when needed too... + mkdir `pwd`/tools/ 2>/dev/null + ${CC} $CFLAGS -o `pwd`/tools/ccdv ${srcdir}/tools/ccdv.c + CC="\$(PRETTY_CC) ${CC}" +fi +if test "x${enable_quiet}" = "xyes"; then + QUIET_LIBTOOL_OPTS="--quiet" + QUIET_MAKE_OPTS="--quiet" +fi + +AC_MSG_RESULT(Supress make details: ${enable_quiet}) +AC_MSG_RESULT(Pretty print compiler output: ${enable_pretty}) + +dnl Put the above variables to use +LIBTOOL="${LIBTOOL} --tag=CC \$(QUIET_LIBTOOL_OPTS)" +MAKE="${MAKE} \$(QUIET_MAKE_OPTS)" + +AC_SUBST(CC) +AC_SUBST(MAKE) +AC_SUBST(LIBTOOL) +AC_SUBST(PRETTY_CC) +AC_SUBST(QUIET_MAKE_OPTS) +AC_SUBST(QUIET_LIBTOOL_OPTS) + +dnl The Makefiles and shell scripts we output +AC_CONFIG_FILES(Makefile \ +config/Makefile \ +include/Makefile \ + include/pils/Makefile \ + include/pils/plugin.h \ + include/clplumbing/Makefile \ + include/lrm/Makefile \ + include/stonith/Makefile \ +lib/Makefile \ + lib/pils/Makefile \ + lib/clplumbing/Makefile \ + lib/stonith/Makefile \ + lib/lrm/Makefile \ + lib/plugins/Makefile \ + lib/plugins/InterfaceMgr/Makefile \ + lib/plugins/compress/Makefile \ + lib/plugins/lrm/Makefile \ + lib/plugins/lrm/dbus/Makefile \ + lib/plugins/stonith/Makefile \ + lib/plugins/stonith/ribcl.py \ + lib/plugins/stonith/external/Makefile \ + lib/plugins/stonith/external/drac5 \ + lib/plugins/stonith/external/kdumpcheck \ + lib/plugins/stonith/external/ssh \ + lib/plugins/stonith/external/ippower9258 \ + lib/plugins/stonith/external/xen0-ha \ +lrm/Makefile \ + lrm/lrmd/Makefile \ + lrm/admin/Makefile \ + lrm/admin/cibsecret \ + lrm/test/Makefile \ + lrm/test/regression.sh \ + lrm/test/lrmregtest \ + lrm/test/LRMBasicSanityCheck \ + lrm/test/testcases/Makefile \ +logd/Makefile \ +logd/logd \ +logd/logd.service \ +replace/Makefile \ +hb_report/Makefile \ + hb_report/hb_report \ +doc/Makefile \ + doc/ha_logd.xml \ + doc/ha_logger.xml \ + doc/stonith.xml \ + doc/meatclient.xml \ + doc/stonith/Makefile +) + +dnl Now process the entire list of files added by previous +dnl calls to AC_CONFIG_FILES() +AC_OUTPUT() + +dnl ***************** +dnl Configure summary +dnl ***************** + +AC_MSG_RESULT([]) +AC_MSG_RESULT([$PACKAGE configuration:]) +AC_MSG_RESULT([ Version = ${VERSION} (Build: $GLUE_BUILD_VERSION)]) +AC_MSG_RESULT([ Features =${FEATURES}]) +AC_MSG_RESULT([]) +AC_MSG_RESULT([ Prefix = ${prefix}]) +AC_MSG_RESULT([ Executables = ${sbindir}]) +AC_MSG_RESULT([ Man pages = ${mandir}]) +AC_MSG_RESULT([ Libraries = ${libdir}]) +AC_MSG_RESULT([ Header files = ${includedir}]) +AC_MSG_RESULT([ Arch-independent files = ${datadir}]) +AC_MSG_RESULT([ Documentation = ${docdir}]) +AC_MSG_RESULT([ State information = ${localstatedir}]) +AC_MSG_RESULT([ System configuration = ${sysconfdir}]) +AC_MSG_RESULT([]) +AC_MSG_RESULT([ Use system LTDL = ${ac_cv_lib_ltdl_lt_dlopen}]) +AC_MSG_RESULT([]) +AC_MSG_RESULT([ HA group name = ${GLUE_DAEMON_GROUP}]) +AC_MSG_RESULT([ HA user name = ${GLUE_DAEMON_USER}]) +AC_MSG_RESULT([]) +AC_MSG_RESULT([ CFLAGS = ${CFLAGS}]) +AC_MSG_RESULT([ Libraries = ${LIBS}]) +AC_MSG_RESULT([ Stack Libraries = ${CLUSTERLIBS}]) + diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..c8d67a8 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,53 @@ +# +# heartbeat: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in hb_report.xml ha_logd.xml ha_logger.xml stonith.xml meatclient.xml + +CLEANFILES = $(man_MANS) + +SUBDIRS = stonith + +hanoarchdir = $(datadir)/heartbeat + +man_MANS = + +if BUILD_DOC +man_MANS += hb_report.8 ha_logd.8 ha_logger.1 stonith.8 meatclient.8 + +EXTRA_DIST = $(man_MANS) + +STYLESHEET_PREFIX ?= http://docbook.sourceforge.net/release/xsl/current +MANPAGES_STYLESHEET ?= $(STYLESHEET_PREFIX)/manpages/docbook.xsl +HTML_STYLESHEET ?= $(STYLESHEET_PREFIX)/xhtml/docbook.xsl +FO_STYLESHEET ?= $(STYLESHEET_PREFIX)/fo/docbook.xsl + +XSLTPROC_OPTIONS ?= --xinclude +XSLTPROC_MANPAGES_OPTIONS ?= $(XSLTPROC_OPTIONS) +XSLTPROC_HTML_OPTIONS ?= $(XSLTPROC_OPTIONS) +XSLTPROC_FO_OPTIONS ?= $(XSLTPROC_OPTIONS) + +%.5 %.8 %.1: %.xml + $(XSLTPROC) \ + $(XSLTPROC_MANPAGES_OPTIONS) \ + $(MANPAGES_STYLESHEET) $< + +hb_report.8: hb_report.8.txt + a2x -f manpage $< + +endif diff --git a/doc/ha_logd.xml.in b/doc/ha_logd.xml.in new file mode 100644 index 0000000..368f06d --- /dev/null +++ b/doc/ha_logd.xml.in @@ -0,0 +1,134 @@ + + + + + December 8, 2009 + @PACKAGE_NAME@ + @VERSION@ + + + Alan + Robertson + ha_logd + alanr@unix.sh + + + Shi + Guochun + ha_logd + gshi@ncsa.uiuc.edu + + + Lars + Marowsky-Bree + ha_logd + lmb@suse.de + + + Florian + Haas + man page + florian.haas@linbit.com + + + + + ha_logd + 8 + System administration utilities + + + ha_logd + Logging Daemon for High-Availability Linux + + + + ha_logd + + + + + file + + + + Description + ha_logd is a logging daemon for + Linux-HA. It receives messages from a local domain socket + @HA_LOGDAEMON_IPC@, and writes them to + appropriate files and syslog if enabled. The reason for utilizing + this logging daemon is that occasionally Heartbeat suffers from + disk I/O delays. By sending log messages to a logging daemon, + heartbeat can avoid such I/O delays. + + + Options + The following options are supported: + + + + + + + Show ha_logd status (either + running or stopped) + + + + + + + + Stop (kill) the daemon + + + + + + + + Daemonize (without this option, + ha_logd will run in the + foreground) + + + + + + + + Show a brief usage message + + + + + file + + + Configuration file. You may configure a regular log + file, debug log file, log facility, and entity. For details, + see the example ha_logd.cf file found + in the documentation. + + + + + + Files + + + @GLUE_STATE_DIR@/ha_logd.pid – PID file + + + ha_logd.cf – example configuration file + + + + + See also + + heartbeat8, + ha_logger1 + + + diff --git a/doc/ha_logger.xml.in b/doc/ha_logger.xml.in new file mode 100644 index 0000000..dce7fe2 --- /dev/null +++ b/doc/ha_logger.xml.in @@ -0,0 +1,110 @@ + + + + + December 8, 2009 + @PACKAGE_NAME@ + @VERSION@ + + + Alan + Robertson + ha_logd + alanr@unix.sh + + + Shi + Guochun + ha_logd + gshi@ncsa.uiuc.edu + + + Lars + Marowsky-Bree + ha_logd + lmb@suse.de + + + Florian + Haas + man page + florian.haas@linbit.com + + + + + ha_logger + 1 + User commands + + + ha_logger + Log a message to files and/or syslog through the HA + Logging Daemon + + + + ha_logger + + + + ha-log + ha-debug + + + + + tag + + + message + + + + + Description + ha_logger is used to log a message to + files/syslog through the HA Logging Daemon. + + + Options + The following options are supported: + + + + ha-log|ha-debug + + + Log the message to different + files. ha-log will log the message to the log + file and the debug file, while ha-debug will + log the message to the debug file only. + + + + + tag + + + Mark every line in the log with the specified + tag. + + + + + message + + + The message that should be logged. + + + + + + See also + + heartbeat8, + ha_logd8 + + + diff --git a/doc/hb_report.8.txt b/doc/hb_report.8.txt new file mode 100644 index 0000000..5efbc32 --- /dev/null +++ b/doc/hb_report.8.txt @@ -0,0 +1,478 @@ +:man source: hb_report +:man version: 1.2 +:man manual: Pacemaker documentation + +hb_report(8) +============ + + +NAME +---- +hb_report - create report for CRM based clusters (Pacemaker) + + +SYNOPSIS +-------- +*hb_report* -f {time|"cts:"testnum} [-t time] [-u user] [-l file] + [-n nodes] [-E files] [-p patt] [-L patt] [-e prog] + [-MSDCZAQVsvhd] [dest] + + +DESCRIPTION +----------- +The hb_report(1) is a utility to collect all information (logs, +configuration files, system information, etc) relevant to +Pacemaker (CRM) over the given period of time. + + +OPTIONS +------- +dest:: + The report name. It can also contain a path where to put the + report tarball. If left out, the tarball is created in the + current directory named "hb_report-current_date", for instance + hb_report-Wed-03-Mar-2010. + +*-d*:: + Don't create the compressed tar, but leave the result in a + directory. + +*-f* { time | "cts:"testnum }:: + The start time from which to collect logs. The time is in the + format as used by the Date::Parse perl module. For cts tests, + specify the "cts:" string followed by the test number. This + option is required. + +*-t* time:: + The end time to which to collect logs. Defaults to now. + +*-n* nodes:: + A list of space separated hostnames (cluster members). + hb_report may try to find out the set of nodes by itself, but + if it runs on the loghost which, as it is usually the case, + does not belong to the cluster, that may be difficult. Also, + OpenAIS doesn't contain a list of nodes and if Pacemaker is + not running, there is no way to find it out automatically. + This option is cumulative (i.e. use -n "a b" or -n a -n b). + +*-l* file:: + Log file location. If, for whatever reason, hb_report cannot + find the log files, you can specify its absolute path. + +*-E* files:: + Extra log files to collect. This option is cumulative. By + default, /var/log/messages are collected along with the + cluster logs. + +*-M*:: + Don't collect extra log files, but only the file containing + messages from the cluster subsystems. + +*-L* patt:: + A list of regular expressions to match in log files for + analysis. This option is additive (default: "CRIT: ERROR:"). + +*-p* patt:: + Additional patterns to match parameter name which contain + sensitive information. This option is additive (default: "passw.*"). + +*-Q*:: + Quick run. Gathering some system information can be expensive. + With this option, such operations are skipped and thus + information collecting sped up. The operations considered + I/O or CPU intensive: verifying installed packages content, + sanitizing files for sensitive information, and producing dot + files from PE inputs. + +*-A*:: + This is an OpenAIS cluster. hb_report has some heuristics to + find the cluster stack, but that is not always reliable. + By default, hb_report assumes that it is run on a Heartbeat + cluster. + +*-u* user:: + The ssh user. hb_report will try to login to other nodes + without specifying a user, then as "root", and finally as + "hacluster". If you have another user for administration over + ssh, please use this option. + +*-X* ssh-options:: + Extra ssh options. These will be added to every ssh + invocation. Alternatively, use `$HOME/.ssh/config` to setup + desired ssh connection options. + +*-S*:: + Single node operation. Run hb_report only on this node and + don't try to start slave collectors on other members of the + cluster. Under normal circumstances this option is not + needed. Use if ssh(1) does not work to other nodes. + +*-Z*:: + If the destination directory exist, remove it instead of + exiting (this is default for CTS). + +*-V*:: + Print the version including the last repository changeset. + +*-v*:: + Increase verbosity. Normally used to debug unexpected + behaviour. + +*-h*:: + Show usage and some examples. + +*-D* (obsolete):: + Don't invoke editor to fill the description text file. + +*-e* prog (obsolete):: + Your favourite text editor. Defaults to $EDITOR, vim, vi, + emacs, or nano, whichever is found first. + +*-C* (obsolete):: + Remove the destination directory once the report has been put + in a tarball. + +EXAMPLES +-------- +Last night during the backup there were several warnings +encountered (logserver is the log host): + + logserver# hb_report -f 3:00 -t 4:00 -n "node1 node2" report + +collects everything from all nodes from 3am to 4am last night. +The files are compressed to a tarball report.tar.bz2. + +Just found a problem during testing: + + # note the current time + node1# date + Fri Sep 11 18:51:40 CEST 2009 + node1# /etc/init.d/heartbeat start + node1# nasty-command-that-breaks-things + node1# sleep 120 #wait for the cluster to settle + node1# hb_report -f 18:51 hb1 + + # if hb_report can't figure out that this is corosync + node1# hb_report -f 18:51 -A hb1 + + # if hb_report can't figure out the cluster members + node1# hb_report -f 18:51 -n "node1 node2" hb1 + +The files are compressed to a tarball hb1.tar.bz2. + +INTERPRETING RESULTS +-------------------- +The compressed tar archive is the final product of hb_report. +This is one example of its content, for a CTS test case on a +three node OpenAIS cluster: + + $ ls -RF 001-Restart + + 001-Restart: + analysis.txt events.txt logd.cf s390vm13/ s390vm16/ + description.txt ha-log.txt openais.conf s390vm14/ + + 001-Restart/s390vm13: + STOPPED crm_verify.txt hb_uuid.txt openais.conf@ sysinfo.txt + cib.txt dlm_dump.txt logd.cf@ pengine/ sysstats.txt + cib.xml events.txt messages permissions.txt + + 001-Restart/s390vm13/pengine: + pe-input-738.bz2 pe-input-740.bz2 pe-warn-450.bz2 + pe-input-739.bz2 pe-warn-449.bz2 pe-warn-451.bz2 + + 001-Restart/s390vm14: + STOPPED crm_verify.txt hb_uuid.txt openais.conf@ sysstats.txt + cib.txt dlm_dump.txt logd.cf@ permissions.txt + cib.xml events.txt messages sysinfo.txt + + 001-Restart/s390vm16: + STOPPED crm_verify.txt hb_uuid.txt messages sysinfo.txt + cib.txt dlm_dump.txt hostcache openais.conf@ sysstats.txt + cib.xml events.txt logd.cf@ permissions.txt + +The top directory contains information which pertains to the +cluster or event as a whole. Files with exactly the same content +on all nodes will also be at the top, with per-node links created +(as it is in this example the case with openais.conf and logd.cf). + +The cluster log files are named ha-log.txt regardless of the +actual log file name on the system. If it is found on the +loghost, then it is placed in the top directory. If not, the top +directory ha-log.txt contains all nodes logs merged and sorted by +time. Files named messages are excerpts of /var/log/messages from +nodes. + +Most files are copied verbatim or they contain output of a +command. For instance, cib.xml is a copy of the CIB found in +/var/lib/heartbeat/crm/cib.xml. crm_verify.txt is output of the +crm_verify(8) program. + +Some files are result of a more involved processing: + + *analysis.txt*:: + A set of log messages matching user defined patterns (may be + provided with the -L option). + + *events.txt*:: + A set of log messages matching event patterns. It should + provide information about major cluster motions without + unnecessary details. These patterns are devised by the + cluster experts. Currently, the patterns cover membership + and quorum changes, resource starts and stops, fencing + (stonith) actions, and cluster starts and stops. events.txt + is always generated for each node. In case the central + cluster log was found, also combined for all nodes. + + *permissions.txt*:: + One of the more common problem causes are file and directory + permissions. hb_report looks for a set of predefined + directories and checks their permissions. Any issues are + reported here. + + *backtraces.txt*:: + gdb generated backtrace information for cores dumped + within the specified period. + + *sysinfo.txt*:: + Various release information about the platform, kernel, + operating system, packages, and anything else deemed to be + relevant. The static part of the system. + + *sysstats.txt*:: + Output of various system commands such as ps(1), uptime(1), + netstat(8), and ifconfig(8). The dynamic part of the system. + +description.txt should contain a user supplied description of the +problem, but since it is very seldom used, it will be dropped +from the future releases. + +PREREQUISITES +------------- + +ssh:: + It is not strictly required, but you won't regret having a + password-less ssh. It is not too difficult to setup and will save + you a lot of time. If you can't have it, for example because your + security policy does not allow such a thing, or you just prefer + menial work, then you will have to resort to the semi-manual + semi-automated report generation. See below for instructions. + + + If you need to supply a password for your passphrase/login, then + always use the `-u` option. + + + For extra ssh(1) options, if you're too lazy to setup + $HOME/.ssh/config, use the `-X` option. Do not forget to put + the options in quotes. + +sudo:: + If the ssh user (as specified with the `-u` option) is other + than `root`, then `hb_report` uses `sudo` to collect the + information which is readable only by the `root` user. In that + case it is required to setup the `sudoers` file properly. The + user (or group to which the user belongs) should have the + following line: + + + ALL = NOPASSWD: /usr/sbin/hb_report + + + See the `sudoers(5)` man page for more details. + +Times:: + In order to find files and messages in the given period and to + parse the `-f` and `-t` options, `hb_report` uses perl and one of the + `Date::Parse` or `Date::Manip` perl modules. Note that you need + only one of these. Furthermore, on nodes which have no logs and + where you don't run `hb_report` directly, no date parsing is + necessary. In other words, if you run this on a loghost then you + don't need these perl modules on the cluster nodes. + + + On rpm based distributions, you can find `Date::Parse` in + `perl-TimeDate` and on Debian and its derivatives in + `libtimedate-perl`. + +Core dumps:: + To backtrace core dumps gdb is needed and the packages with + the debugging info. The debug info packages may be installed + at the time the report is created. Let's hope that you will + need this really seldom. + +TIMES +----- + +Specifying times can at times be a nuisance. That is why we have +chosen to use one of the perl modules--they do allow certain +freedom when talking dates. You can either read the instructions +at the +http://search.cpan.org/dist/TimeDate/lib/Date/Parse.pm#EXAMPLE_DATES[Date::Parse +examples page]. +or just rely on common sense and try stuff like: + + 3:00 (today at 3am) + 15:00 (today at 3pm) + 2007/9/1 2pm (September 1st at 2pm) + Tue Sep 15 20:46:27 CEST 2009 (September 15th etc) + +`hb_report` will (probably) complain if it can't figure out what do +you mean. + +Try to delimit the event as close as possible in order to reduce +the size of the report, but still leaving a minute or two around +for good measure. + +`-f` is not optional. And don't forget to quote dates when they +contain spaces. + + +Should I send all this to the rest of Internet? +----------------------------------------------- + +By default, the sensitive data in CIB and PE files is not mangled +by `hb_report` because that makes PE input files mostly useless. +If you still have no other option but to send the report to a +public mailing list and do not want the sensitive data to be +included, use the `-s` option. Without this option, `hb_report` +will issue a warning if it finds information which should not be +exposed. By default, parameters matching 'passw.*' are considered +sensitive. Use the `-p` option to specify additional regular +expressions to match variable names which may contain information +you don't want to leak. For example: + + # hb_report -f 18:00 -p "user.*" -p "secret.*" /var/tmp/report + +Heartbeat's ha.cf is always sanitized. Logs and other files are +not filtered. + +LOGS +---- + +It may be tricky to find syslog logs. The scheme used is to log a +unique message on all nodes and then look it up in the usual +syslog locations. This procedure is not foolproof, in particular +if the syslog files are in a non-standard directory. We look in +/var/log /var/logs /var/syslog /var/adm /var/log/ha +/var/log/cluster. In case we can't find the logs, please supply +their location: + + # hb_report -f 5pm -l /var/log/cluster1/ha-log -S /tmp/report_node1 + +If you have different log locations on different nodes, well, +perhaps you'd like to make them the same and make life easier for +everybody. + +Files starting with "ha-" are preferred. In case syslog sends +messages to more than one file, if one of them is named ha-log or +ha-debug those will be favoured over syslog or messages. + +hb_report supports also archived logs in case the period +specified extends that far in the past. The archives must reside +in the same directory as the current log and their names must +be prefixed with the name of the current log (syslog-1.gz or +messages-20090105.bz2). + +If there is no separate log for the cluster, possibly unrelated +messages from other programs are included. We don't filter logs, +but just pick a segment for the period you specified. + +MANUAL REPORT COLLECTION +------------------------ + +So, your ssh doesn't work. In that case, you will have to run +this procedure on all nodes. Use `-S` so that `hb_report` doesn't +bother with ssh: + + # hb_report -f 5:20pm -t 5:30pm -S /tmp/report_node1 + +If you also have a log host which is not in the cluster, then +you'll have to copy the log to one of the nodes and tell us where +it is: + + # hb_report -f 5:20pm -t 5:30pm -l /var/tmp/ha-log -S /tmp/report_node1 + +OPERATION +--------- +hb_report collects files and other information in a fairly +straightforward way. The most complex tasks are discovering the +log file locations (if syslog is used which is the most common +case) and coordinating the operation on multiple nodes. + +The instance of hb_report running on the host where it was +invoked is the master instance. Instances running on other nodes +are slave instances. The master instance communicates with slave +instances by ssh. There are multiple ssh invocations per run, so +it is essential that the ssh works without password, i.e. with +the public key authentication and authorized_keys. + +The operation consists of three phases. Each phase must finish +on all nodes before the next one can commence. The first phase +consists of logging unique messages through syslog on all nodes. +This is the shortest of all phases. + +The second phase is the most involved. During this phase all +local information is collected, which includes: + +- logs (both current and archived if the start time is far in the past) +- various configuration files (corosync, heartbeat, logd) +- the CIB (both as xml and as represented by the crm shell) +- pengine inputs (if this node was the DC at any point in + time over the given period) +- system information and status +- package information and status +- dlm lock information +- backtraces (if there were core dumps) + +The third phase is collecting information from all nodes and +analyzing it. The analyzis consists of the following tasks: + +- identify files equal on all nodes which may then be moved to + the top directory +- save log messages matching user defined patterns + (defaults to ERRORs and CRITical conditions) +- report if there were coredumps and by whom +- report crm_verify(8) results +- save log messages matching major events to events.txt +- in case logging is configured without loghost, node logs and + events files are combined using a perl utility + + +BUGS +---- +Finding logs may at times be extremely difficult, depending on +how weird the syslog configuration. It would be nice to ask +syslog-ng developers to provide a way to find out the log +destination based on facility and priority. + +If you think you found a bug, please rerun with the -v option and +attach the output to bugzilla. + +hb_report can function in a satisfactory way only if ssh works to +all nodes using authorized_keys (without password). + +There are way too many options. + + +AUTHOR +------ +Written by Dejan Muhamedagic, + + +RESOURCES +--------- +Pacemaker: + +Heartbeat and other Linux HA resources: + +OpenAIS: + +Corosync: + + +SEE ALSO +-------- +Date::Parse(3) + + +COPYING +------- +Copyright \(C) 2007-2009 Dejan Muhamedagic. Free use of this +software is granted under the terms of the GNU General Public License (GPL). + diff --git a/doc/meatclient.xml.in b/doc/meatclient.xml.in new file mode 100644 index 0000000..778a57c --- /dev/null +++ b/doc/meatclient.xml.in @@ -0,0 +1,77 @@ + + + + + December 4, 2009 + Cluster Glue + @VERSION@ + + + Gregor + Binder + meatclient + gbinder@sysfive.com + + + Michael + Mörz + man page + mimem@debian.org + + + Simon + Horman + man page + horms@vergenet.net + + + Florian + Haas + man page + florian.haas@linbit.com + + + + + meatclient + 8 + System administration utilities + + + meatclient + Manually confirm that a node has been removed from the + cluster + + + meatclient nodename + + + Description + meatclient confirms that a node has been + manually removed from the cluster. It instructs the cluster + manager, via the meatware STONITH plugin, that it is safe to + continue cluster operations. + + + Options + The following options are supported: + + + + nodename + + + nodename is the name of the + cluster node that has been fenced. + + + + + + See also + + heartbeat8, + stonith8 + + + diff --git a/doc/stonith.xml.in b/doc/stonith.xml.in new file mode 100644 index 0000000..575c339 --- /dev/null +++ b/doc/stonith.xml.in @@ -0,0 +1,315 @@ + + + + + December 7, 2009 + @PACKAGE_NAME@ + @VERSION@ + + + Alan + Robertson + stonith + alanr@unix.sh + + + Simon + Horman + man page + horms@vergenet.net + + + Florian + Haas + man page + florian.haas@linbit.com + + + + + stonith + 8 + System administration utilities + + + stonith + extensible interface for remotely powering down a node + in the cluster + + + + stonith + + + + stonith + + + + + + stonith + + + stonith-device-type + + + + stonith + + + stonith-device-type + + + name=value + + stonith-device-parameters + stonith-device-parameters-file + + count + + + + + stonith + + + stonith-device-type + + + name=value + + stonith-device-parameters + stonith-device-parameters-file + + count + + + reset + on + off + + + nodename + + + + Description + The STONITH module provides an extensible interface for + remotely powering down a node in the cluster (STONITH = Shoot The + Other Node In The Head). The idea is quite simple: when the + software running on one machine wants to make sure another machine + in the cluster is not using a resource, pull the plug on the other + machine. It's simple and reliable, albeit admittedly + brutal. + + + Options + The following options are supported: + + + + count + + + Perform any actions identified by the + , and + options count + times. + + + + + stonith-device-parameters-file + + + Path of file specifying parameters for a stonith + device. To determine the syntax of the parameters file for a + given device type run: + # stonith -t stonith-device-type -n + All of the listed parameters need to appear in order + on a single line in the parameters file and be delimited by + whitespace. + + + + + + + + Display detailed information about a stonith device + including description, configuration information, parameters + and any other related information. When specified without a + stonith-device-type, detailed information on all stonith + devices is displayed. + If you don't yet own a stonith device and want to know + more about the ones we support, this information is likely + to be helpful. + + + + + + + + List the valid stonith device types, suitable for + passing as an argument to the + option. + + + + + + + + List the hosts controlled by the stonith device. + + + + + + + + Output the parameter names of the stonith device. + + + + + name=value + + + Parameter, in the form of a name/value pair, to pass + directly to the stonith device. To determine the syntax of + the parameters for a given device type run: + # stonith -t stonith-device-type -n + All of the listed parameter names need to be passed + with their corresponding values. + + + + + stonith-device-parameters + + + Parameters to pass directly to the stonith device. To + determine the syntax of the parameters for a given device + type run: + # stonith -t stonith-device-type -n + All of the listed parameter names need to appear in + order and be delimited by whitespace. + + + + + + + + Show the status of the stonith device. + + + + + + + + Silent operation. Suppress logging of error messages to standard error. + + + + + action + + + The stonith action to perform on the node identified + by nodename. Chosen from reset, + on, and off. + + If a nodename is specified without the + option, the stonith action defaults to + reset. + + + + + + stonith-device-type + + + The type of the stonith device to be used to effect + stonith. A list of supported devices for an installation may + be obtained using the option. + + + + + + + + Ignored. + + + + + + Examples + To determine which stonith devices are available on your installation, use the option: + # stonith -L + All of the supported devices will be displayed one per line. + Choose one from this list that is best for your environment - + let's use wti_nps for the rest of this example. To get detailed + information about this device, use the option: + # stonith -t wti_nps -h + Included in the output is the list of valid parameter names + for wti_nps. To get just the + list of valid parameter names, use the option + instead: + # stonith -t wti_nps -n + All of the required parameter names will be displayed one + per line. For wti_nps the output is: + ipaddr +password + There are three ways to pass these parameters to the device. + The first (and preferred) way is by passing name/value pairs on + the stonith command line: + # stonith -t wti_nps ipaddr=my-dev-ip password=my-dev-pw ... + The second way, which is maintained only for backward + compatibility with legacy clusters, is passing the values + in order on the stonith + command line with the option: + # stonith -t wti_nps -p "my-dev-ip my-dev-pw" ... + The third way, which is also maintained only for backward + compatibility with legacy clusters, is placing the values in order + on a single line in a config file: + my-dev-ip my-dev-pw + ... and passing the name of the file on the stonith command + line with the option: + # stonith -t wti_nps -F ~/my-wtinps-config ... + To make sure you have the configuration set up correctly and + that the device is available for stonith operations, use the + option: + # stonith -t wti_nps ipaddr=my-dev-ip password=my-dev-pw -S + If all is well at this point, you should see something similar to: + stonith: wti_nps device OK. + If you don't, some debugging may be necessary to determine + if the config info is correct, the device is powered on, etc. The + option can come in handy here - you can add it + to any stonith command to cause it to generate + debug output. + To get the list of hosts controlled by the device, use the + option: + # stonith -t wti_nps ipaddr=my-dev-ip password=my-dev-pw -l + All of the hosts controlled by the device will be displayed one per line. For wti_nps the output could be: + node1 + node2 + node3 + To power off one of these hosts, use the option: + # stonith -t wti_nps ipaddr=my-dev-ip password=my-dev-pw -T off node + + + See also + + heartbeat8, + meatclient8 + + + diff --git a/doc/stonith/Makefile.am b/doc/stonith/Makefile.am new file mode 100644 index 0000000..4c9b76f --- /dev/null +++ b/doc/stonith/Makefile.am @@ -0,0 +1,37 @@ +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +stdocdir = $(docdir)/stonith + +stdoc_DATA = README.bladehpi \ + README.cyclades \ + README.drac3 \ + README.dracmc \ + README.external \ + README.ibmrsa \ + README.ibmrsa-telnet \ + README.meatware \ + README.rackpdu \ + README.rcd_serial \ + README.riloe \ + README.vacm \ + README.wti_mpc \ + README_kdumpcheck.txt \ + README.vcenter + +if IPMILAN_BUILD +stdoc_DATA += README.ipmilan +endif diff --git a/doc/stonith/README.bladehpi b/doc/stonith/README.bladehpi new file mode 100644 index 0000000..3119ef7 --- /dev/null +++ b/doc/stonith/README.bladehpi @@ -0,0 +1,101 @@ + +STONITH module for IBM BladeCenter via OpenHPI +---------------------------------------------- + +Requirements: + Linux-HA bladehpi STONITH plugin requires OpenHPI 2.6+ + OpenHPI requires Net-SNMP 5.0+ + OpenHPI requires BladeCenter Management Module 1.08+ + +This STONITH module talks to IBM BladeCenters via SNMP through use of +the OpenHPI BladeCenter plugin (snmp_bc). For more information about +installing OpenHPI, setting up the BladeCenter SNMP agent, etc. please +visit http://www.openhpi.org/. Once OpenHPI is installed properly, +the STONITH plugin will automatically be built the next time Linux-HA +is built. + +Use the OpenHPI configuration file (i.e. /etc/openhpi/openhpi.conf) +to configure the BladeCenters of interest to STONITH. For example, +the following excerpt: + + plugin libsnmp_bc + + handler libsnmp_bc { + entity_root = "{SYSTEM_CHASSIS,1}" # Required + host = "9.254.253.252" # Required + community = "community" # Version 1 Required. + version = "3" # Required. SNMP protocol version (1|3) + security_name = "userid" # Version 3 Required. + passphrase = "userpass" # Version 3. Required if security_level is authNoPriv or authPriv. + auth_type = "MD5" # Version 3. Passphrase encoding (MD5|SHA) + security_level = "authNoPriv" # Version 3. (noAuthNoPriv|authNoPriv|authPriv) + } + +defines how to access the BladeCenter at 9.254.253.252 using SNMPV3 +with an ID/password of userid/userpass. The entity_root must be +passed to the STONITH bladehpi plugin as its single required parameter. +For example, to query the list of blades present in the BladeCenter +configured above, run: + + stonith -t bladehpi -p "{SYSTEM_CHASSIS,1}" -l + +which is the same as: + + stonith -t bladehpi "entity_root={SYSTEM_CHASSIS,1}" -l + +Use the BladeCenter Management Module web interface to set the Blade +Information to match "uname -n" for each blade in the cluster. For +example, with the BladeCeter configured above use a brower to access +http://9.254.253.252, login with userid/userpass, and then go to +Blade Tasks -> Configuration -> Blade Information, enter the proper +names, and select Save. Be aware that heartbeat must be restarted +before these changes take effect or, if using the OpenHPI daemon, +the daemon must be restarted. + +More than one BladeCenter can be placed in the OpenHPI configuration +file by using different numbers with the entity_root. For example, + + plugin libsnmp_bc + + handler libsnmp_bc { + entity_root = "{SYSTEM_CHASSIS,1}" # Required + host = "9.254.253.252" # Required + : + } + handler libsnmp_bc { + entity_root = "{SYSTEM_CHASSIS,2}" # Required + host = "9.254.253.251" # Required + : + } + +There is an optional parameter, soft_reset, that is true|1 if bladehpi +should use soft reset (power cycle) to reset nodes or false|0 if it +should use hard reset (power off, wait, power on); the default is +false. As an example, to override the default value the above stonith +command would become: + + stonith -t bladehpi -p "{SYSTEM_CHASSIS,1} true" -l + +which is the same as: + + stonith -t bladehpi "entity_root={SYSTEM_CHASSIS,1} soft_reset=true" -l + +The difference between the two is that a soft reset is much quicker +but may return before the node has been reset because bladehpi relies +on BladeCenter firmware to cycle the node's power, while a hard reset +is slower but guaranteed not to return until the node is dead because +bladehpi powers off the node, waits until it is off, then powers it +on again. + +NOTE: Set the OPENHPI_CONF environment variable to contain the +fully-qualified path of the OpenHPI configuration file, for example: + + export OPENHPI_CONF=/etc/openhpi/openhpi.conf + +NOTE: If OpenHPI is not configured with --disable-daemon before being +built and installed, make sure that the OpenHPI daemon is running +before using the bladehpi plugin. + +NOTE: If debugging of the environment is needed, configure OpenHPI +with --enable-debuggable and rebuild/reinstall, export +OPENHPI_DEBUG=YES, and run stonith commands with the -d option. diff --git a/doc/stonith/README.cyclades b/doc/stonith/README.cyclades new file mode 100644 index 0000000..3ccf9db --- /dev/null +++ b/doc/stonith/README.cyclades @@ -0,0 +1,61 @@ +STONITH module for Cyclades AlterPath PM +---------------------------------------- + +This STONITH module talks to Cyclades AlterPath PM series of power managers +via TS, ACS or KVM equipment. + +Access to the frontend device (TS, ACS or KVM) is done via root user with +passwordless ssh. + +For that, it is necessary to create a public/private keypar with _empty_ +passphrase on _each_ machine which is part of the cluster. + +Small HOWTO follows: + +# ssh-keygen -t rsa +Generating public/private rsa key pair. +Enter file in which to save the key (/root/.ssh/id_rsa): +Created directory '/home/root/.ssh'. +Enter passphrase (empty for no passphrase): +Enter same passphrase again: +Your identification has been saved in /root/.ssh/id_rsa. +Your public key has been saved in /root/.ssh/id_rsa.pub. +The key fingerprint is: +dc:e0:71:55:fd:2a:b0:19:d6:3c:48:e5:45:db:b4:be root@hostname.network + +Next step is to append the public key (/root/.ssh/id_rsa.pub) +to the authorized_keys file on the TS/ACS/KVM box. The authorized +keys file location is set at the SSH daemon configuration file. +The default location is /etc/ssh/authorized_keys, so: + +[root@clusterhost]# scp /root/.ssh/id_rsa.pub root@alterpath:/tmp + +login to the TS/ACS/KVM box normally and append the public key. + +# ssh root@alterpath +Password: .... + +[root@CAS root]# cat /tmp/id_rsa.pub >> /etc/ssh/authorized_keys + +The following entries must be present on /etc/ssh/sshd_config for the +passwordless scheme to work properly: + +RSAAuthentication yes +PubkeyAuthentication yes +AuthorizedKeysFile /etc/ssh/authorized_keys + +Next step is to test if the configuration has been done successfully: + +[root@clusterhost root]# ssh root@alterpath +[root@CAS root]# + +If it logins automatically without asking for a password, then everything +has been done correctly! + +Note that such configuration procedure (including generation of the key pair) +has to be done for each machine in the cluster which intends to use the +AlterPath PM as a STONITH device. + +------ +Any questions please contact Cyclades support at +or diff --git a/doc/stonith/README.drac3 b/doc/stonith/README.drac3 new file mode 100644 index 0000000..e3c071b --- /dev/null +++ b/doc/stonith/README.drac3 @@ -0,0 +1,18 @@ +Stonith module for Dell DRACIII remote access card +-------------------------------------------------- + +This module uses the Dell DRACIII PCI card as a stonith device. +It sends the XML commands over HTTPS to the DRACIII web server. + +The card firmware must be version 2.0 at least, with support for SSL based +service and many bug fixes over 1.x versions. + +This module uses libcurl, libxml2 (gnome xml libs) and libssl. + +Any hints, bug reports, improvements, etc. will be apreciated. + +--- +Roberto Moreda http://www.alfa21.com +Alfa21 A Coruña (Spain) +UNIX, Linux & TCP/IP Services - High Availability Solutions + diff --git a/doc/stonith/README.dracmc b/doc/stonith/README.dracmc new file mode 100644 index 0000000..761f5ad --- /dev/null +++ b/doc/stonith/README.dracmc @@ -0,0 +1,87 @@ +dracmc-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) + Connects to Dell Drac/MC Blade Enclosure via a Cyclades + terminal server with telnet and switches power of named + blade servers appropriatelly. + +Description: + Dell offers the Drac/MC in their blade enclosures. The +Drac/MC can be accessed in different ways. One way to interface to it +is to connect the blade enclosure's Drac/MC serial port to a Cyclades +terminal server. You can then access the Drac/MC via telnet via the +Cyclades. Once logged in, you can use 'help' to show all available +commands. With the 'serveraction' command, you can control both +hard and soft resets as well as power to a particular blade. The +blades are named 'Server-X', where 'X' is a number which corresponds +to the blade number in the enclosure. This plugin allows using the +Drac/MC with stonith. It uses python's standards 'telnetlib' library +to log in and issue commands. The code is very similar to the original +ibmrsa-telnet plugin released by Andreas and was quite easy to +modify for this application. + One complication is that the Cyclades only allows one active +connection. If someone or something has a connection active, the +terminal server closes the new attempted connection. Since this +situation can be common, for example if trying to stonith two blades +or when the plugin is started by multiple cluster nodes, there is a +built in retry mechanism for login. On 10 retries, the code gives up +and throws. + When running this resource, it is best to not run it as a clone, +rather as a normal, single-instance resource. Make sure to create a +location constraint that excludes the node that is to be fenced. + +Required parameters: + nodename: The name of the server you want to touch on your network + cyclades_ip: The IP address of the cyclades terminal server + cyclades_port: The port for telnet to access on the cyclades (i.e. 7032) + servername: The DRAC/MC server name of the blade (i.e. Server-7) + username: The login user name for the DRAC/MC + password: The login password for the DRAC/MC + +Example configuration + +These are examples: you should adjust parameters, scores and +timeout values to fit your environment. + +crm shell: + + primitive fence_node1 stonith:external/dracmc-telnet \ + nodename=node1 cyclades_ip=10.0.0.1 cyclades_port=7001 \ + servername=Server-1 username=USERID password=PASSWORD \ + op monitor interval="200m" timeout="60s" + location loc-fence_node1 fence_node1 -inf: node1 + +XML: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/stonith/README.external b/doc/stonith/README.external new file mode 100644 index 0000000..a70ccde --- /dev/null +++ b/doc/stonith/README.external @@ -0,0 +1,90 @@ +EXTERNAL module for Linux-HA STONITH + + +This stonith plugin runs an external command written in your favorite +language to shutdown the given host. The external command should return +a zero exit status after a successful shutdown, or non-zero exit status +for a shutdown failure. Failures notifications will be sent to syslog. + +To create your own external plugin, write a script that supports the +following actions: + + reset + on (optional) + off (optional) + gethosts + status + getconfignames + getinfo-devid + getinfo-devname + getinfo-devdescr + getinfo-devurl + getinfo-xml + +and place it in the /usr/lib/stonith/plugins/external directory - the +script must be a regular executable file that is NOT writable by group +or others in order to be recognized as an external plugin. If the +action requires information to be returned, such as the list of hosts +or config names or any of the getinfo calls, simply write the +information to stdout. When complete, return zero to indicate the +action succeeded or non-zero to indicate the action failed. You can +use the ssh (sh) and riloe (pyhton) scripts already in that directory +as working examples. + +To make sure that your external plugin is recognized, run "stonith -L" +and look for its name in the output, something along the lines of: + + external/yourplugin + +To configure the plugin on an R1 (legacy) cluster, add a line similar +to the following to /etc/ha.d/ha.cf: + + stonith external/yourplugin /etc/ha.d/yourplugin.cfg + +where /etc/ha.d/yourplugin.cfg contains a single line with all of your +plugin's parameters: + + parm1-value parm2-value ... + +Another way to configure the plugin on a legacy cluster is to add a line +similiar to the following to /etc/ha.d/ha.cf instead: + + stonith_host * external/yourplugin parm1-value parm2-value ... + +where all of your plugin's parameters are placed at the end of the line. + +Please note that all parameters come in to the plugin in name/value +(environment variable) form, but in R1 configurations, they appear as a +list of parameters. They are ordered in the config file or on the +stonith_host line according to the ordering specified in the output of +the getconfignames operation. + +To configure the plugin on an R2 cluster, place lines similar to the +following into the section of your CIB, which is contained +in /var/lib/heartbeat/crm/cib.xml: + + + + + + + + + + + + + + + + + + + +Whatever parameters specified in the section of +the CIB are passed to the script as environment variables. For the +example above, the parameters are passed as parm1-name=parm1-value, +parm2-name=parm2-value and so on. + +Additional information can be found at +http://linux-ha.org/wiki/ExternalStonithPlugins. diff --git a/doc/stonith/README.ibmrsa b/doc/stonith/README.ibmrsa new file mode 100644 index 0000000..b34031b --- /dev/null +++ b/doc/stonith/README.ibmrsa @@ -0,0 +1,9 @@ +See + +ftp://ftp.software.ibm.com/systems/support/system_x_pdf/d3basmst.pdf +ftp://ftp.software.ibm.com/systems/support/system_x_pdf/88p9248.pdf +http://www.redbooks.ibm.com/abstracts/sg246495.html + +for documentation about IBM management processors and the +IBMmpcli utility. + diff --git a/doc/stonith/README.ibmrsa-telnet b/doc/stonith/README.ibmrsa-telnet new file mode 100644 index 0000000..109bdd9 --- /dev/null +++ b/doc/stonith/README.ibmrsa-telnet @@ -0,0 +1,55 @@ +ibmrsa-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) + Connects to IBM RSA Board via telnet and switches power + of server appropriately. + +Description: + + IBM offers Remote Supervisor Adapters II for several + servers. These RSA boards can be accessed in different ways. + One of that is via telnet. Once logged in you can use 'help' to + show all available commands. With 'power' you can reset, power on and + off the controlled server. This command is used in combination + with python's standard library 'telnetlib' to do it automatically. + +Code snippet for cib + + It's useful to give a location preference so that the stonith agent + is run on the/an other node. This is not necessary as one node can kill + itself via RSA Board. But: If this node becomes crazy my experiences + showed that the node is not able to shoot itself anymore properly. + + You have to adjust parameters, scores and timeout values to fit your + HA environment. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/stonith/README.ipmilan b/doc/stonith/README.ipmilan new file mode 100644 index 0000000..eef86cf --- /dev/null +++ b/doc/stonith/README.ipmilan @@ -0,0 +1,131 @@ + IPMILAN STONITH Module + Copyright (c) 2003 Intel Corp. + yixiong.zou@intel.com + +1. Intro + +IPMILAN STONITH module works by sending a node an IPMI message, in particular, +a 'chassis control' command. Currently the message is sent over the LAN. + +2. Hardware Requirement + +In order to use this module, the node has to be IPMI v1.5 compliant and +also supports IPMI over LAN. For example, the Intel Langley platform. + +Note: IPMI over LAN is an optional feature defined by IPMI v1.5 spec. +So even if a system is IPMI compliant/compatible, it might still not +support IPMI over LAN. If you are sure this is your case and you still +want to try this plugin, read section 6, IPMI v1.5 without IPMI over +LAN Support. + +3. Software Requirement + +This module needs OpenIPMI (http://openipmi.sf.net) to compile. +Version 1.4.x or 2.0.x is supported. + +4. Hardware Configuration + +How to configure the node so it accepts IPMI lan packets is beyond the +scope of this document. Consult your product manual for this. + +5. STONITH Configuration + +Each node in the cluster has to be configured individually. So normally there +would be at least two entries, unless you want to use a different STONITH +device for the other nodes in the cluster. ;) + +The configuration file syntax looks like this: + + + + ... + + node: the hostname. + + ip: the IP address of the node. If a node has more than one IP addresses, + this is the IP address of the interface which accepts IPMI messages. :) + + port: the port number to send the IPMI message to. The default is 623. + But it could be different or even configurable. + + auth: the authorization type of the IPMI session. Valid choices are + "none", "straight", "md2", and "md5". + + priv: the privilege level of the user. Valid choices are "operator" + or "admin". These are the privilege levels required to run the + 'chassis control' command. + + user: the username. use "" if it is empty. Cannot exceed 16 characters. + + pass: the password. use "" if it is empty. Cannot exceed 16 characters. + + reset_method: (optional) which IPMI chassis control to send + to reset the host. Possible values are power_cycle (default) + and hard_reset. + +Each line is white-space delimited and lines begins with '#' are ignored. + +6. IPMI v1.5 without IPMI over LAN Support + +If somehow your computer have a BMC but without LAN support, you might +still be able to use this module. + + 0) Make sure OpenIPMI is installed. OpenIPMI 1.0.3 should work. + + 1) Create a /etc/ipmi_lan.conf file. + + Here's a sample of how this file should look like + + addr 172.16.1.249 999 + PEF_alerting on + per_msg_auth off + priv_limit admin + allowed_auths_admin none md2 md5 + user 20 on "" "" admin 5 md2 md5 none + + If you do not understand what each line means, do a man on ipmilan. + + 2) run ipmilan as root. + + 3) Try send youself an IPMI packet over the network using ipmicmd see + if it works. + + ipmicmd -k "0f 00 06 01" lan 172.16.1.249 999 none admin "" "" + + The result should be something like: + + Connection 0 to the BMC is up0f 07 00 01 00 01 80 01 19 01 8f 77 00 00 4b 02 + + 4) Configure your system so everytime it boots up, the ipmi device + drivers are all loaded and ipmilan is run. This is all OS dependent + so I can't tell you what to do. + + The major draw back of this is that you will not be able to power it up + once it's power down, which for a real IPMI, you could. + + +7. Bugs + +Some IPMI device does not return 0x0, success, to the host who issued the reset +command. A timeout, 0xc3, could be returned instead. So I am counting that +also as a "successful reset". + +Note: This behavior is not fully IPMI v1.5 compliant. Based on the IPMI v1.5 +spec, the IPMI device should return the appropriate return code. And it is +even allowed to return the appropriate return code before performing the +action. + + +8. TODO + +1) Right now the timeout on each host is hard coded to be 10 seconds. It will + be nice to be able to set this value for individual host. + +2) A better way of detecting the success of the reset operation will be good. A + lot of times the host which carried out the reset does not return a success. + +3) The os_handler should be contributed back to the OpenIPMI project so that + we do not need to maintain it here. It does not make sense for every little + app like this to write its own os_handler. A generic one like in this + program should be sufficient. + diff --git a/doc/stonith/README.ippower9258 b/doc/stonith/README.ippower9258 new file mode 100644 index 0000000..6873efd --- /dev/null +++ b/doc/stonith/README.ippower9258 @@ -0,0 +1,68 @@ +IP Power 9258 as external stonith device. +========================================= + +Device Information +================== + + Warning: + ======== + + Aviosys provides different types and versions of IP Power 9258. + The device is currently available with four or eight power outlets. + This script was tested with firmware version: V1.55 2009/12/22 + + Especially "IP Power 9258 HP" uses a different http command interface. + ====================================================================== + + Resources for device documentation: + + Manufacturer URL: http://www.aviosys.com/ippower9258.htm + Manual URL: http://www.aviosys.com/manual.htm + Manual current version URL: + http://www.aviosys.com/images/9258_manual_20081104.pdf + +The documentation of the http command interface defines three +supported commands: + + GetPower - useful for testing status of the device and of each port + SetPower - used to control status of each power outlet + SetSchedule+Power - useless for stonith + +Common documented structure of these three commands is + + http://username:password@a.b.c.d/Set.cmd?CMD=command[+param=value...] + param is one or more of P60 to P67 and value is 0 or 1 + expected response for GetPower is of the format + P60=1,P61=0,P62=1,P63=1,P64=0,P65=0,P66=0,P67=0 + SetPower does respond with the same format but restricts the list + to the modified ports. + P60 to P67 represent the status of the power outlet 1 to 8: 0 <=> + power off; 1 <=> power on. + +IP Power 9258 allows to assign port names (pw1Name to pw8Name) to each +port. These names can be used with the web interface (web form with +post-method). + +Script specific notes +===================== + +There is no documented http command to retrieve port names via the +http command interface. We try to get the hostlist via the web +interface. + +This script assumes a one to one mapping between names of hostlist and +port attributes of power outlet: + + 1st hostname in hostlist connected to 1st power outlet with port + status P60 and port name pw1Name. + ... + 8th hostname in hostlist connected to 8th power outlet with port + status P67 and port name pw8Name. + +If the hostlist parameter is not defined, then all assigned outlets +are inserted into the hostlist. Unused outlets should have empty +names. The node names obviously have to match the corresponding outlet +names. A reserved hostname is "*not-defined*". This is a +sript-internal placeholder for unused outlets. It does not appear in +the hostlist. + diff --git a/doc/stonith/README.meatware b/doc/stonith/README.meatware new file mode 100644 index 0000000..0b9b15d --- /dev/null +++ b/doc/stonith/README.meatware @@ -0,0 +1,26 @@ + +MEATWARE Module for Linux-HA STONITH + +ABOUT: + + This is a port of the "meatware" stomith method found in the GFS + distribution (see http://globalfilesystem.org/) to the Linux-HA + project. It notifies operators if a node needs to be reset and + waits for confirmation. + +USAGE: + + The module can be used like any other stonith module. It will + syslog a message at CRIT level if it needs an operator to power-cycle + a node on its behalf. + To confirm that a manual reset has been done, execute + + "meatclient -c ". + + If you abort the confirmation, the module will report that the reset + has failed. + +AUTHOR: + + Gregor Binder + diff --git a/doc/stonith/README.rackpdu b/doc/stonith/README.rackpdu new file mode 100644 index 0000000..69a0f44 --- /dev/null +++ b/doc/stonith/README.rackpdu @@ -0,0 +1,21 @@ +APC Rack PDU + +The product information pages: + +http://www.apcc.com/products/family/index.cfm?id=70 + +The User's Guide: + +http://www.apcmedia.com/salestools/ASTE-6Z6KAV_R1_EN.pdf + +Apparently, an existing http or telnet session will make the +plugin fail. + +In case your nodes are equipped with multiple power supplies, the +PDU supports synchronous operation on multiple outlets on up to +four Switched Rack PDUs. See the User's Guide for more +information on how to setup outlet groups. + +NB: There has been a report by one user that in case a link +between two PDUs in the chain is broken, the PDU returns success +even though it failed. This needs to be verified. diff --git a/doc/stonith/README.rcd_serial b/doc/stonith/README.rcd_serial new file mode 100644 index 0000000..8b4abb4 --- /dev/null +++ b/doc/stonith/README.rcd_serial @@ -0,0 +1,186 @@ +rcd_serial - RC Delayed Serial +------------------------------ + +This stonith plugin uses one (or both) of the control lines of a serial +device (on the stonith host) to reboot another host (the stonith'ed host) +by closing its reset switch. A simple idea with one major problem - any +glitch which occurs on the serial line of the stonith host can potentially +cause a reset of the stonith'ed host. Such "glitches" can occur when the +stonith host is powered up or reset, during BIOS detection of the serial +ports, when the kernel loads up the serial port driver, etc. + +To fix this, you need to introduce a delay between the assertion of the +control signal on the serial port and the closing of the reset switch. +Then any glitches will be dissipated. When you really want to do the +business, you hold the control signal high for a "long time" rather than +just tickling it "glitch-fashion" by, e.g., using the rcd_serial plugin. + +As the name of the plugin suggests, one way to achieve the required delay is +to use a simple RC circuit and an npn transistor: + + + . . + RTS . . ----------- +5V + or ---------- . | + DTR . | . Rl reset + . | T1 . | |\logic + . Rt | ------RWL--------| -------> + . | b| /c . |/ + . |---Rb---|/ . + . | |\ . (m/b wiring typical + . C | \e . only - YMMV!) + . | | . + . | | . + SG ---------------------------RWG----------- 0V + . . + . . stonith'ed host + stonith host --->.<----- RC circuit ----->.<---- RWL = reset wire live + (serial port) . . RWG = reset wire ground + + +The characteristic delay (in seconds) is given by the product of Rt (in ohms) +and C (in Farads). Suitable values for the 4 components of the RC circuit +above are: + +Rt = 20k +C = 47uF +Rb = 360k +T1 = BC108 + +which gives a delay of 20 x 10e3 x 47 x 10e-6 = 0.94s. In practice the +actual delay achieved will depend on the pull-up load resistor Rl if Rl is +small: for Rl greater than 3k there is no significant dependence but lower +than this and the delay will increase - to about 1.4s at 1k and 1.9s at 0.5k. + +This circuit will work but it is a bit dangerous for the following reasons: + +1) If by mistake you open the serial port with minicom (or virtually any +other piece of software) you will cause a stonith reset ;-(. This is +because opening the port will by default cause the assertion of both DTR +and RTS, and a program like minicom will hold them high thenceforth (unless +and until a receive buffer overflow pulls RTS down). + +2) Some motherboards have the property that when held in the reset state, +all serial outputs are driven high. Thus, if you have the circuit above +attached to a serial port on such a motherboard, if you were to press the +(manual) reset switch and hold it in for more than a second or so, you will +cause a stonith reset of the attached system ;-(. + +This problem can be solved by adding a second npn transistor to act as a +shorting switch across the capacitor, driven by the other serial output: + + + . . + . . ----------- +5V + RTS ----------------- . | + . | . Rl reset + . | T1 . | |\logic + . Rt | ------RWL--------| -------> + . | b| /c . |/ + . T2 --|---Rb---|/ . + . | / | |\ . (m/b wiring typical + . b| /c | | \e . only - YMMV!) + DTR ------Rb--|/ C | . + . |\ | | . + . | \e | | . + . | | | . + SG ----------------------------------RWG------------- 0V + . . + . . stonith'ed host +stonith->.<--------- RC circuit ------->.<---- RWL = reset wire live + host . . RWG = reset wire ground + + +Now when RTS goes high it can only charge up C and cause a reset if DTR is +simultaneously kept low - if DTR goes high, T2 will switch on and discharge +the capacitor. Only a very unusual piece of software e.g. the rcd_serial +plugin, is going to achieve this (rather bizarre) combination of signals +(the "meaning" of which is something along the lines of "you are clear to +send but I'm not ready"!). T2 can be another BC108 and with Rb the same. + +RS232 signal levels are typically +-8V to +-12V so a 16V rating or greater +for the capacitor is sufficient BUT NOTE that a _polarised_ electrolytic should +not be used because the voltage switches around as the capacitor charges. +Nitai make a range of non-polar aluminium electrolytic capacitors. A 16V 47uF +radial capacitor measures 6mm diameter by 11mm long and along with the 3 +resistors (1/8W are fine) and the transistors, the whole circuit can be built +in the back of a DB9 serial "plug" so that all that emerges from the plug are +the 2 reset wires to go to the stonith'ed host's m/b reset pins. + +NOTE that with these circuits the reset wires are now POLARISED and hence +they are labelled RWG and RWL above. You cannot connect to the reset pins +either way round as you can when connecting a manual reset switch! You'll +soon enough know if you've got it the wrong way round because your machine +will be in permanent reset state ;-( + + +How to find out if your motherboard can be reset by these circuits +------------------------------------------------------------------ + +You can either build it first and then suck it and see, or, you need a +multimeter. The 0V rail of your system is available in either +of the 2 black wires in the middle of a spare power connector (one of +those horrible 4-way plugs which you push with difficulty into the back +of hard disks, etc. Curse IBM for ever specifying such a monstrosity!). +Likewise, the +5V rail is the red wire. (The yellow one is +12V, ignore +this.) + +First, with the system powered down and the meter set to read ohms: + + check that one of the reset pins is connected to 0V - this then + is the RWG pin; + + check that the other pin (RWL) has a high resistance wrt 0V + (probably > 2M) and has a small resistance wrt to +5V - between + 0.5k and 10k (or higher, doesn't really matter) will be fine. + +Second, with the system powered up and the meter set to read Volts: + + check that RWG is indeed that i.e. there should be 0V between it + and the 0V rail; + + check that RWL is around +5V wrt the 0V rail. + +If all this checks out, you are _probably_ OK. However, I've got one +system which checks out fine but actually won't work. The reason is that +when you short the reset pins, the actual current drain is much higher than +one would expect. Why, I don't know, but there is a final test you can do +to detect this kind of system. + +With the system powered up and the meter set to read milliamps: + + short the reset pins with the meter i.e. reset the system, and + note how much current is actually drained when the system is in + the reset state. + +Mostly you will find that the reset current is 1mA or less and this is +fine. On the system I mention above, it is 80mA! If the current is +greater than 20mA or so, you have probably had it with the simple circuits +above, although reducing the base bias resistor will get you a bit further. +Otherwise, you have to use an analog switch (like the 4066 - I had to use 4 +of these in parallel to reset my 80mA system) which is tedious because then +you need a +5V supply rail to the circuit so you can no longer just build it +in the back of a serial plug. Mail me if you want the details. + +With the circuit built and the rcd_serial plugin compiled, you can use: + +stonith -t rcd_serial -p "testhost /dev/ttyS0 rts XXX" testhost + +to test it. XXX is the duration in millisecs so just keep increasing this +until you get a reset - but wait a few secs between each attempt because +the capacitor takes time to discharge. Once you've found the minimum value +required to cause a reset, add say 200ms for safety and use this value +henceforth. + +Finally, of course, all the usual disclaimers apply. If you follow my +advice and destroy your system, sorry. But it's highly unlikely: serial +port outputs are internally protected against short circuits, and reset pins +are designed to be short circuited! The only circumstance in which I can +see a possibility of damaging something by incorrect wiring would be if the +2 systems concerned were not at the same earth potential. Provided both +systems are plugged into the same mains system (i.e. are not miles apart +and connected only by a very long reset wire ;-) this shouldn't arise. + +John Sutton +john@scl.co.uk +October 2002 diff --git a/doc/stonith/README.riloe b/doc/stonith/README.riloe new file mode 100644 index 0000000..4befe95 --- /dev/null +++ b/doc/stonith/README.riloe @@ -0,0 +1,36 @@ +Note for iLO 3 users + +This plugin doesn't support the iLO version 3. Please use ipmilan +or external/ipmi, iLO3 should support IPMI. + +Alain St-Denis wrote the riloe plugin. Here is short usage: + +primitive st0 stonith:external/riloe \ + hostlist=target-node \ + ilo_hostname=ilo-ip-address \ + ilo_user=admin ilo_password=secret ilo_protocol=2.0 + +The following additional parameters are available: + +ilo_can_reset: + Set to "1" if the ilo is capable of rebooting the host. + Defaults to '0'. + +ilo_protocol: + Defaults to 1.2. Set to the protocol version ilo supports. + +ilo_powerdown_method: + "button" or "power", the former simulates pressing the + button, the latter pulling the power plug. Defaults to + "power". The "button" method is easier on the host, but + requires ACPI. "power" should be more reliable, but not to + be used excessively for testing. + +ilo_proxyhost (string): Proxy hostname + proxy hostname if required to access ILO board + +ilo_proxyport (string, [3128]): Proxy port + proxy port if required to access ILO board + parameter will be ignored if proxy hostname is not set + + diff --git a/doc/stonith/README.vacm b/doc/stonith/README.vacm new file mode 100644 index 0000000..c9083ee --- /dev/null +++ b/doc/stonith/README.vacm @@ -0,0 +1,40 @@ +20 December 2000 + +I (rather poorly) integrated this contributed stonith driver into the +linux-ha-stonith release. There is a problem that needs to be +resolved by autoconf in that the driver will not compile unless +libvacmclient is installed on the system. + +For now, what I've done is included a line in stonith/Makefile that you can +uncomment if you want to compile the vacm stonith module. Look in the +Makefile in this directory for the following lines and do like it says + + +# If you want the VA Linux Cluster stonith module installed, +# uncomment the following line. You must have the vacmclient library +#VACM_STONITH = vacm_stonith.so + +Please direct questions about the operation of the stonith module to +Mike Tilstra (see the announcement to the linux-ha-dev mailing list +attached below.) + + +-Eric. +eric.ayers@compgen.com + +------------------------------------------------------------------------------ + +From: Mike Tilstra +Sender: linux-ha-dev-admin@lists.tummy.com +To: linux-ha-dev@lists.tummy.com +Subject: [Linux-ha-dev] stonith module for VACM +Date: Tue, 19 Dec 2000 16:41:38 -0600 + +This was in need for some testing I'm doing, so I hacked this up quick. It +works for me, but I'm willing to bet there's atleast one bug in it. + +Figured others might like it. + +... +-- +Mike Tilstra conrad@sistina.com \ No newline at end of file diff --git a/doc/stonith/README.vcenter b/doc/stonith/README.vcenter new file mode 100644 index 0000000..e6cc9a5 --- /dev/null +++ b/doc/stonith/README.vcenter @@ -0,0 +1,90 @@ +VMware vCenter/ESX STONITH Module +================================= + +1. Intro +-------- + +VMware vCenter/ESX STONITH Module is intended to provide STONITH support to +clusters in VMware Virtual Infrastructures. It is able to deal with virtual +machines running on physically different HostSystems (e.g. ESX/ESXi) by using +VMware vSphere Web Services SDK http://www.vmware.com/support/developer/vc-sdk/ +and connecting directly on each HostSystem or through a VMware vCenter: in this +last case the module locates the specified virtual machine in the Virtual +Infrastructure and performs actions required by cluster policies. + +2. Software requirements +------------------------ + +VMware vSphere CLI, which includes both CLI tools and Perl SDK +http://www.vmware.com/support/developer/vcli/ . The plugin has been tested with +version 4.1 http://www.vmware.com/download/download.do?downloadGroup=VCLI41 + + +3. vCenter/ESX authentication settings +-------------------------------------- + +Create the credentials file with credstore_admin.pl: + +/usr/lib/vmware-vcli/apps/general/credstore_admin.pl \ + -s 10.1.1.1 -u myuser -p mypass + +This should create $HOME/.vmware/credstore/vicredentials.xml +Copy it to a system folder, e.g. /etc + +cp -p $HOME/.vmware/credstore/vicredentials.xml /etc + + +4. Testing +---------- + +The plugin can be invoked directly to perform a very first connection test +(replace all the provided sample values): + +VI_SERVER=10.1.1.1 \ + VI_CREDSTORE=/etc/vicredentials.xml \ + HOSTLIST="hostname1=vmname1;hostname2=vmname2" \ + RESETPOWERON=0 \ + /usr/lib/stonith/plugins/external/vcenter gethosts + +If everything works correctly you should get: + +hostname1 +hostname2 + +When invoked in this way, the plugin connects to VI_SERVER, authenticates with +credentials stored in VI_CREDSTORE and tries to retrieve the list of virtual +machines (case insensitive) matching vmname1 and vmname2 (and any other listed). +When finished, it reports the list back by mapping virtual machine names to +hostnames as provided in HOSTLIST. If you see the full list of hostnames as a +result, then everything is going well. If otherwise you are having a partial or +empty list, you have to check parameters. + +You can even test "reset", "off" and "on" commands, to test (carefully!) the +full chain. E.g. + +VI_SERVER=10.1.1.1 \ + VI_CREDSTORE=/etc/vicredentials.xml \ + HOSTLIST="hostname1=vmname1;hostname2=vmname2" \ + RESETPOWERON=0 \ + /usr/lib/stonith/plugins/external/vcenter reset hostname2 + +In the above examples the referring infrastructure is a vCenter with several +ESXi nodes. Server IP and credentials are referred to vCenter. + +5. CRM configuration +-------------------- + +The following is a sample procedure to setup STONITH for an HA 2-node cluster +(replace all the provided sample values): + +crm configure primitive vfencing stonith::external/vcenter params \ + VI_SERVER="10.1.1.1" VI_CREDSTORE="/etc/vicredentials.xml" \ + HOSTLIST="hostname1=vmname1;hostname2=vmname2" RESETPOWERON="0" \ + op monitor interval="60s" + +crm configure clone Fencing vfencing + +crm configure property stonith-enabled="true" + + + diff --git a/doc/stonith/README.wti_mpc b/doc/stonith/README.wti_mpc new file mode 100644 index 0000000..050953d --- /dev/null +++ b/doc/stonith/README.wti_mpc @@ -0,0 +1,85 @@ +STONITH module for WTI MPC +-------------------------- + + +****Introduction. + +wti_mpc module uses snmp for controlling the MPC power distribution unit. It has +been tested with MPC-8H and MPC-18H and should be compatible with the whole +MPC series: + * MPC-20* + * MPC-16* + * MPC-18* + * MPC-8* + +****Unit configuration. + +wti_mpc STONITH modules uses SNMP v1, therefore it should be configured on the +device side. To do so, you should login to device, go to "Network +configuration" (/N), select "SNMP access" (25) and turn it on (enable/1). At the +SNMP access screen set "Version" (2) to "V1/V2 Only", set "Read only" (3) to +"No and set any "Community" (10) you want. You may also set other options as +you need. You may check your setup by issuing the following command: + + snmpwalk -v1 -c .1.3.6.1.2.1.1.1.0 + +and result should be something like this: + + SNMPv2-MIB::sysDescr.0 = STRING: Linux 85.195.135.236 2.4.18_mvl30-cllf #1991 Sun Mar 16 14:39:29 PST 2008 ppc + + +****Plugin configuration. + + Plugin declares the following configuration variables: + + *ipaddr - ip address or hostname of a MPC unit. + *port - ip port, should be 161, as MPC listens for incoming SNMP + packets on that port. It is made for future use actually. + *community - Community that you've specified on previous step. + *mib_version - Should be 3 for MPC devices with firmware version 1.62 + and later. 1 is for firmware version 1.44 and below. + 2 is unused right now, if you have device, with mib V2 + feel free to contact me and I'll add it. + +****MIB version issue + + WTI guys have several time changed OIDs, used by MPC devices. I own two +types of the devices: + *With firmware v 1.44 which is compatible with MIB version 1 + *With firmware v 1.62 which is compatible with MIB version 3 + +I suppose there are exist MIB v2, but i cannot find it and I'd not able +to test it. +Anyway, this plugin supports both V1 and V3 versions, and the correct version +is selected by the "mib-version" configuration parameter. Default value is "1", +so if you do not specify this parameter or assign a unsupported value to it, +it will fall back to mib version 1. + +****Outlets and groups + + MPC devices forces unique names of the outlets. This is a big problem +for STONITH plugin, cause it uses nodes unames as outlet names, so in case +you have a node with several power plugs, you should have set the node uname +as name of all the plugs. The MPC device simply doesn't allows this. + So, this plugin works with a GROUPS instead of a PLUGS. You may give +any unique names for your physical outlets on the MPC, but you MUST create +a plug group, name it using node's uname and include plugs, corresponding to +that particular node to this group. It should be done even for node with +single power supply. Some example: + + Let's pretend you have a node "atest", with two power cords, connected +to plugs A1 and B1. You have to create a group ("Plug grouping parameters" (/G) +-> Add Plug Group to directory (2)), name it "atest" ("Plug Group Name (1)) and +assign plugs A1 and B1 to that group ("Plug access" (2)). Now save your +configuration and try to retrieve host list: + + stonith -t wti_mpc ipaddr= port=161 community= mib-version= -l + +result should be: + + atest + + +------------------ +(C) Denis Chapligin , SatGate, 2009 + diff --git a/doc/stonith/README_kdumpcheck.txt b/doc/stonith/README_kdumpcheck.txt new file mode 100644 index 0000000..cc8787c --- /dev/null +++ b/doc/stonith/README_kdumpcheck.txt @@ -0,0 +1,151 @@ + Kdump check STONITH plugin "kdumpcheck" +1. Introduction + This plugin's purpose is to avoid STONITH for a node which is doing kdump. + It confirms whether the node is doing kdump or not when STONITH reset or + off operation is executed. + If the target node is doing kdump, this plugin considers that STONITH + succeeded. If not, it considers that STONITH failed. + + NOTE: This plugin has no ability to shutdown or startup a node. + So it has to be used with other STONITH plugin. + Then, when this plugin failed, the next plugin which can kill a node + is executed. + NOTE: This plugin works only on Linux. + +2. The way to check + When STONITH reset or off is executed, kdumpcheck connects to the target + node, and checks the size of /proc/vmcore. + It judges that the target node is _not_ doing kdump when the size of + /proc/vmcore on the node is zero, or the file doesn't exist. + Then kdumpcheck returns "STONITH failed" to stonithd, and the next plugin + is executed. + +3. Expanding mkdumprd + This plugin requires non-root user and ssh connection even on 2nd kernel. + So, you need to apply mkdumprd_for_kdumpcheck.patch to /sbin/mkdumprd. + This patch is tested with mkdumprd version 5.0.39. + The patch adds the following functions: + i) Start udevd with specified .rules files. + ii) Bring the specified network interface up. + iii) Start sshd. + iv) Add the specified user to the 2nd kernel. + The user is to check whether the node is doing kdump or not. + v) Execute sync command after dumping. + + NOTE: i) to iv) expandings are only for the case that filesystem partition + is specified as the location where the vmcore should be dumped. + +4. Parameters + kdumpcheck's parameters are the following. + hostlist : The list of hosts that the STONITH device controls. + delimiter is "," or " ". + indispensable setting. (default:none) + identity_file: a full-path of the private key file for the user + who checks doing kdump. + (default: $HOME/.ssh/id_rsa, $HOME/.ssh/id_dsa and + $HOME/.ssh/identity) + + NOTE: To execute this plugin first, set the highest priority to this plugin + in all STONITH resources. + +5. How to Use + To use this tool, do the following steps at all nodes in the cluster. + 1) Add an user to check doing kdump. + ex.) + # useradd kdumpchecker + # passwd kdumpchecker + 2) Allow passwordless login from the node which will do STONITH to all + target nodes for the user added at step 1). + ex.) + $ cd + $ mkdir .ssh + $ chmod 700 .ssh + $ cd .ssh + $ ssh-keygen (generate authentication keys with empty passphrase) + $ scp id_rsa.pub kdumpchecker@target_node:"~/.ssh/." + $ ssh kdumpchecker@target_node + $ cd ~/.ssh + $ cat id_rsa.pub >> authorized_keys + $ chmod 600 autorized_keys + $ rm id_rsa.pub + 3) Limit the command that the user can execute. + Describe the following commands in a line at the head of the user's + public key in target node's authorized_keys file. + [command="test -s /proc/vmcore"] + And describe some options (like no-pty, no-port-forwarding and so on) + according to your security policy. + ex.) + $ vi ~/.ssh/authorized_keys + command="test -s /proc/vmcore",no-port-forwarding,no-X11-forwarding, + no-agent-forwarding,no-pty ssh-rsa AAA..snip..== kdumpchecker@node1 + 4) Add settings in /etc/kdump.conf. + network_device : network interface name to check doing kdump. + indispensable setting. (default: none) + kdump_check_user : user name to check doing kdump. + specify non-root user. + (default: "kdumpchecker") + udev_rules : .rules files' names. + specify if you use udev for mapping devices. + specified files have to be in /etc/udev/rules.d/. + you can specify two or more files. + delimiter is "," or " ". (default: none) + ex.) + # vi /etc/kdump.conf + ext3 /dev/sda1 + network_device eth0 + kdump_check_user kdumpchecker + udev_rules 10-if.rules + 5) Apply the patch to /sbin/mkdumprd. + # cd /sbin + # patch -p 1 < mkdumprd_for_kdumpcheck.patch + 6) Restart kdump service. + # service kdump restart + 7) Describe cib.xml to set STONITH plugin. + (See "2. Parameters" and "6. Appendix") + +6. Appendix + A sample cib.xml. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hb_report/Makefile.am b/hb_report/Makefile.am new file mode 100644 index 0000000..cd4ad65 --- /dev/null +++ b/hb_report/Makefile.am @@ -0,0 +1,26 @@ +# +# heartbeat: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +hanoarchdir = $(datadir)/$(PACKAGE_NAME) + +hanoarch_DATA = utillib.sh ha_cf_support.sh openais_conf_support.sh +sbin_SCRIPTS = hb_report + diff --git a/hb_report/ha_cf_support.sh b/hb_report/ha_cf_support.sh new file mode 100644 index 0000000..7b35c98 --- /dev/null +++ b/hb_report/ha_cf_support.sh @@ -0,0 +1,83 @@ + # Copyright (C) 2007 Dejan Muhamedagic + # + # This program is free software; you can redistribute it and/or + # modify it under the terms of the GNU General Public + # License as published by the Free Software Foundation; either + # version 2.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +# +# Stack specific part (heartbeat) +# ha.cf/logd.cf parsing +# +getcfvar() { + [ -f "$CONF" ] || return + sed 's/#.*//' < $CONF | + grep -w "^$1" | + sed 's/^[^[:space:]]*[[:space:]]*//' +} +iscfvarset() { + test "`getcfvar $1`" +} +iscfvartrue() { + getcfvar "$1" | + egrep -qsi "^(true|y|yes|on|1)" +} +uselogd() { + iscfvartrue use_logd && + return 0 # if use_logd true + iscfvarset logfacility || + iscfvarset logfile || + iscfvarset debugfile || + return 0 # or none of the log options set + false +} +get_hb_logvars() { + # unless logfacility is set to none, heartbeat/ha_logd are + # going to log through syslog + HA_LOGFACILITY=`getcfvar logfacility` + [ "" = "$HA_LOGFACILITY" ] && HA_LOGFACILITY=$DEFAULT_HA_LOGFACILITY + [ none = "$HA_LOGFACILITY" ] && HA_LOGFACILITY="" + HA_LOGFILE=`getcfvar logfile` + HA_DEBUGFILE=`getcfvar debugfile` +} +getlogvars() { + HA_LOGFACILITY=${HA_LOGFACILITY:-$DEFAULT_HA_LOGFACILITY} + HA_LOGLEVEL="info" + cfdebug=`getcfvar debug` # prefer debug level if set + isnumber $cfdebug || cfdebug="" + [ "$cfdebug" ] && [ $cfdebug -gt 0 ] && + HA_LOGLEVEL="debug" + if uselogd; then + [ -f "$LOGD_CF" ] || { + debug "logd used but logd.cf not found: using defaults" + return # no configuration: use defaults + } + debug "reading log settings from $LOGD_CF" + get_logd_logvars + else + debug "reading log settings from $CONF" + get_hb_logvars + fi +} +cluster_info() { + echo "heartbeat version: `$HA_BIN/heartbeat -V`" +} +essential_files() { + cat< + # + # This program is free software; you can redistribute it and/or + # modify it under the terms of the GNU General Public + # License as published by the Free Software Foundation; either + # version 2.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +. @OCF_ROOT_DIR@/lib/heartbeat/ocf-shellfuncs + +HA_NOARCHBIN=@datadir@/@PACKAGE_NAME@ + +. $HA_NOARCHBIN/utillib.sh + +unset LANG +export LC_ALL=POSIX + +PROG=`basename $0` + +# the default syslog facility is not (yet) exported by heartbeat +# to shell scripts +# +DEFAULT_HA_LOGFACILITY="daemon" +export DEFAULT_HA_LOGFACILITY +LOGD_CF=`findlogdcf @sysconfdir@ $HA_DIR` +export LOGD_CF + +SSH_PASSWORD_NODES="" +: ${SSH_OPTS="-o StrictHostKeyChecking=no -o EscapeChar=none"} +LOG_PATTERNS="CRIT: ERROR:" +# PEINPUTS_PATT="peng.*PEngine Input stored" + +# Important events +# +# Patterns format: +# title extended_regexp +# NB: don't use spaces in titles or regular expressions! +EVENT_PATTERNS=" +membership crmd.*ccm_event.*(NEW|LOST)|pcmk_peer_update.*(lost|memb): +quorum crmd.*crm_update_quorum:.Updating.quorum.status|crmd.*ais.disp.*quorum.(lost|ac?quir) +pause Process.pause.detected +resources lrmd.*rsc:(start|stop) +stonith crmd.*te_fence_node.*Exec|stonith-ng.*log_oper.*reboot|stonithd.*(requests|(Succeeded|Failed).to.STONITH|result=) +start_stop Configuration.validated..Starting.heartbeat|Corosync.Cluster.Engine|Executive.Service.RELEASE|crm_shutdown:.Requesting.shutdown|pcmk_shutdown:.Shutdown.complete +" + +init_tmpfiles + +# +# the instance where user runs hb_report is the master +# the others are slaves +# +if [ x"$1" = x__slave ]; then + SLAVE=1 +fi + +usage() { + cat</dev/null ls -rt `2>/dev/null find /var/lib -name pengine -type d | + sed 's,$,/*.last,'` | tail -1) + if [ -f "$lastf" ]; then + PE_STATE_DIR=`dirname $lastf` + else + for p in pacemaker/pengine pengine heartbeat/pengine; do + if [ -d $localstatedir/$p ]; then + debug "setting PE_STATE_DIR to $localstatedir/$p" + PE_STATE_DIR=$localstatedir/$p + break + fi + done + fi +} +get_cib_dir2() { + # CIB + # HA_VARLIB is normally set to {localstatedir}/heartbeat + local localstatedir + localstatedir=`dirname $HA_VARLIB` + for p in pacemaker/cib heartbeat/crm; do + if [ -f $localstatedir/$p/cib.xml ]; then + debug "setting CIB_DIR to $localstatedir/$p" + CIB_DIR=$localstatedir/$p + break + fi + done +} +get_crm_daemon_dir() { + # CRM_DAEMON_DIR + local libdir p + libdir=`dirname $HA_BIN` + for p in pacemaker heartbeat; do + if [ -x $libdir/$p/crmd ]; then + debug "setting CRM_DAEMON_DIR to $libdir/$p" + CRM_DAEMON_DIR=$libdir/$p + return 0 + fi + done + return 1 +} +get_crm_daemon_dir2() { + # CRM_DAEMON_DIR again (brute force) + local p d d2 + for p in /usr /usr/local /opt; do + for d in libexec lib64 lib; do + for d2 in pacemaker heartbeat; do + if [ -x $p/$d/$d2/crmd ]; then + debug "setting CRM_DAEMON_DIR to $p/$d/$d2" + CRM_DAEMON_DIR=$p/$d/$d2 + break + fi + done + done + done +} +compatibility_pcmk() { + get_crm_daemon_dir || get_crm_daemon_dir2 + if [ ! -d "$CRM_DAEMON_DIR" ]; then + fatal "cannot find pacemaker daemon directory!" + fi + get_pe_state_dir || get_pe_state_dir2 + get_cib_dir || get_cib_dir2 + debug "setting PCMK_LIB to `dirname $CIB_DIR`" + PCMK_LIB=`dirname $CIB_DIR` + # PTEST + PTEST=`echo_ptest_tool` + export PE_STATE_DIR CIB_DIR CRM_DAEMON_DIR PCMK_LIB PTEST +} + +# +# find log files +# +logmark() { + logger -p $* + debug "run: logger -p $*" +} +# +# first try syslog files, if none found then use the +# logfile/debugfile settings +# +findlog() { + local logf="" + if [ "$HA_LOGFACILITY" ]; then + logf=`findmsg $UNIQUE_MSG | awk '{print $1}'` + fi + if [ -f "$logf" ]; then + echo $logf + else + echo ${HA_DEBUGFILE:-$HA_LOGFILE} + [ "${HA_DEBUGFILE:-$HA_LOGFILE}" ] && + debug "will try with ${HA_DEBUGFILE:-$HA_LOGFILE}" + fi +} + +# +# find log slices +# + +find_decompressor() { + if echo $1 | grep -qs 'xz$'; then + echo "xz -dc" + elif echo $1 | grep -qs 'bz2$'; then + echo "bzip2 -dc" + elif echo $1 | grep -qs 'gz$'; then + echo "gzip -dc" + else + echo "cat" + fi +} +# +# check if the log contains a piece of our segment +# +is_our_log() { + local logf=$1 + local from_time=$2 + local to_time=$3 + + local cat=`find_decompressor $logf` + local first_time="`$cat $logf | head -10 | find_first_ts`" + local last_time="`$cat $logf | tail -10 | tac | find_first_ts`" + if [ x = "x$first_time" -o x = "x$last_time" ]; then + return 0 # skip (empty log?) + fi + if [ $from_time -gt $last_time ]; then + # we shouldn't get here anyway if the logs are in order + return 2 # we're past good logs; exit + fi + if [ $from_time -ge $first_time ]; then + return 3 # this is the last good log + fi + # have to go further back + if [ $to_time -eq 0 -o $to_time -ge $first_time ]; then + return 1 # include this log + else + return 0 # don't include this log + fi +} +# +# go through archived logs (timewise backwards) and see if there +# are lines belonging to us +# (we rely on untouched log files, i.e. that modify time +# hasn't been changed) +# +arch_logs() { + local next_log + local logf=$1 + local from_time=$2 + local to_time=$3 + + # look for files such as: ha-log-20090308 or + # ha-log-20090308.gz (.bz2) or ha-log.0, etc + ls -t $logf $logf*[0-9z] 2>/dev/null | + while read next_log; do + is_our_log $next_log $from_time $to_time + case $? in + 0) ;; # noop, continue + 1) echo $next_log # include log and continue + debug "found log $next_log" + ;; + 2) break;; # don't go through older logs! + 3) echo $next_log # include log and continue + debug "found log $next_log" + break + ;; # don't go through older logs! + esac + done +} +# +# print part of the log +# +print_log() { + local cat=`find_decompressor $1` + $cat $1 +} +print_logseg() { + if test -x $HA_NOARCHBIN/print_logseg; then + $HA_NOARCHBIN/print_logseg "$1" "$2" "$3" + return + fi + + local logf=$1 + local from_time=$2 + local to_time=$3 + local tmp sourcef + + # uncompress to a temp file (if necessary) + local cat=`find_decompressor $logf` + if [ "$cat" != "cat" ]; then + tmp=`mktemp` || + fatal "disk full" + add_tmpfiles $tmp + $cat $logf > $tmp || + fatal "disk full" + sourcef=$tmp + else + sourcef=$logf + tmp="" + fi + + if [ "$from_time" = 0 ]; then + FROM_LINE=1 + else + FROM_LINE=`findln_by_time $sourcef $from_time` + fi + if [ -z "$FROM_LINE" ]; then + warning "couldn't find line for time $from_time; corrupt log file?" + return + fi + + TO_LINE="" + if [ "$to_time" != 0 ]; then + TO_LINE=`findln_by_time $sourcef $to_time` + if [ -z "$TO_LINE" ]; then + warning "couldn't find line for time $to_time; corrupt log file?" + return + fi + fi + dumplog $sourcef $FROM_LINE $TO_LINE + debug "including segment [$FROM_LINE-$TO_LINE] from $logf" +} +# +# print some log info (important for crm history) +# +loginfo() { + local logf=$1 + local fake=$2 + local nextpos=`python -c "f=open('$logf');f.seek(0,2);print f.tell()+1"` + if [ "$fake" ]; then + echo "synthetic:$logf $nextpos" + else + echo "$logf $nextpos" + fi +} +# +# find log/set of logs which are interesting for us +# +dumplogset() { + local logf=$1 + local from_time=$2 + local to_time=$3 + + local logf_set=`arch_logs $logf $from_time $to_time` + if [ x = "x$logf_set" ]; then + return + fi + + local num_logs=`echo "$logf_set" | wc -l` + local oldest=`echo $logf_set | awk '{print $NF}'` + local newest=`echo $logf_set | awk '{print $1}'` + local mid_logfiles=`echo $logf_set | awk '{for(i=NF-1; i>1; i--) print $i}'` + + # the first logfile: from $from_time to $to_time (or end) + # logfiles in the middle: all + # the last logfile: from beginning to $to_time (or end) + case $num_logs in + 1) print_logseg $newest $from_time $to_time;; + *) + print_logseg $oldest $from_time 0 + for f in $mid_logfiles; do + print_log $f + debug "including complete $f logfile" + done + print_logseg $newest 0 $to_time + ;; + esac +} + +# +# cts_findlogseg finds lines for the CTS test run (FROM_LINE and +# TO_LINE) and then extracts the timestamps to get FROM_TIME and +# TO_TIME +# +cts_findlogseg() { + local testnum=$1 + local logf=$2 + if [ "x$logf" = "x" ]; then + logf=`findmsg "Running test.*\[ *$testnum\]" | awk '{print $1}'` + fi + getstampproc=`find_getstampproc < $logf` + export getstampproc # used by linetime + + FROM_LINE=`grep -n "Running test.*\[ *$testnum\]" $logf | tail -1 | sed 's/:.*//'` + if [ -z "$FROM_LINE" ]; then + warning "couldn't find line for CTS test $testnum; corrupt log file?" + exit 1 + else + FROM_TIME=`linetime $logf $FROM_LINE` + fi + TO_LINE=`grep -n "Running test.*\[ *$(($testnum+1))\]" $logf | tail -1 | sed 's/:.*//'` + [ "$TO_LINE" -a $FROM_LINE -lt $TO_LINE ] || + TO_LINE=`wc -l < $logf` + TO_TIME=`linetime $logf $TO_LINE` + debug "including segment [$FROM_LINE-$TO_LINE] from $logf" + dumplog $logf $FROM_LINE $TO_LINE +} + +# +# this is how we pass environment to other hosts +# +dumpenv() { + cat</dev/null +} +findsshuser() { + local n u rc + local ssh_s ssh_user="__undef" try_user_list + if [ -z "$SSH_USER" ]; then + try_user_list="__default $TRY_SSH" + else + try_user_list="$SSH_USER" + fi + for n in $NODES; do + rc=1 + [ "$n" = "$WE" ] && continue + for u in $try_user_list; do + if [ "$u" != '__default' ]; then + ssh_s=$u@$n + else + ssh_s=$n + fi + if testsshconn $ssh_s; then + debug "ssh $ssh_s OK" + ssh_user="$u" + try_user_list="$u" # we support just one user + rc=0 + break + else + debug "ssh $ssh_s failed" + fi + done + [ $rc = 1 ] && + SSH_PASSWORD_NODES="$SSH_PASSWORD_NODES $n" + done + if [ -n "$SSH_PASSWORD_NODES" ]; then + warning "passwordless ssh to node(s) $SSH_PASSWORD_NODES does not work" + fi + + if [ "$ssh_user" = "__undef" ]; then + return 1 + fi + if [ "$ssh_user" != "__default" ]; then + SSH_USER=$ssh_user + fi + return 0 +} +node_needs_pwd() { + local n + for n in $SSH_PASSWORD_NODES; do + [ "$n" = "$1" ] && return 0 + done + return 1 +} +say_ssh_user() { + if [ -n "$SSH_USER" ]; then + echo $SSH_USER + else + echo your user + fi +} + +# +# the usual stuff +# +getbacktraces() { + local f bf flist + flist=$( + for f in `find_files "$CORES_DIRS" $1 $2`; do + bf=`basename $f` + test `expr match $bf core` -gt 0 && + echo $f + done) + [ "$flist" ] && { + getbt $flist > $3 + debug "found backtraces: $flist" + } +} +pe2dot() { + local pef=`basename $1` + local dotf=`basename $pef .bz2`.dot + test -z "$PTEST" && return + ( + cd `dirname $1` + $PTEST -D $dotf -x $pef >/dev/null 2>&1 + ) +} +getpeinputs() { + local pe_dir flist + local f + pe_dir=$PE_STATE_DIR + debug "looking for PE files in $pe_dir" + flist=$( + find_files $pe_dir $1 $2 | grep -v '[.]last$' + ) + [ "$flist" ] && { + mkdir $3/`basename $pe_dir` + ( + cd $3/`basename $pe_dir` + for f in $flist; do + ln -s $f + done + ) + debug "found `echo $flist | wc -w` pengine input files in $pe_dir" + } + if [ `echo $flist | wc -w` -le 20 ]; then + for f in $flist; do + skip_lvl 1 || pe2dot $3/`basename $pe_dir`/`basename $f` + done + else + debug "too many PE inputs to create dot files" + fi +} +getratraces() { + local trace_dir flist + local f + trace_dir=$HA_VARLIB/trace_ra + test -d "$trace_dir" || return 0 + debug "looking for RA trace files in $trace_dir" + flist=$(find_files $trace_dir $1 $2 | sed "s,`dirname $trace_dir`/,,g") + [ "$flist" ] && { + tar -cf - -C `dirname $trace_dir` $flist | tar -xf - -C $3 + debug "found `echo $flist | wc -w` RA trace files in $trace_dir" + } +} +touch_DC_if_dc() { + local dc + dc=`crmadmin -D 2>/dev/null | awk '{print $NF}'` + if [ "$WE" = "$dc" ]; then + touch $1/DC + fi +} +corosync_blackbox() { + local from_time=$1 + local to_time=$2 + local outf=$3 + local inpf + inpf=`find_files /var/lib/corosync $from_time $to_time | grep -w fdata` + if [ -f "$inpf" ]; then + corosync-blackbox > $outf + touch -r $inpf $outf + fi +} +getconfigurations() { + local conf + local dest=$1 + for conf in $CONFIGURATIONS; do + if [ -f $conf ]; then + cp -p $conf $dest + elif [ -d $conf ]; then + ( + cd `dirname $conf` && + tar cf - `basename $conf` | (cd $dest && tar xf -) + ) + fi + done +} + + +# +# some basic system info and stats +# +sys_info() { + cluster_info + hb_report -V # our info + echo "resource-agents: `grep 'Build version:' @OCF_ROOT_DIR@/lib/heartbeat/ocf-shellfuncs`" + crm_info + pkg_versions $PACKAGES + skip_lvl 1 || verify_packages $PACKAGES + echo "Platform: `uname`" + echo "Kernel release: `uname -r`" + echo "Architecture: `uname -m`" + [ `uname` = Linux ] && + echo "Distribution: `distro`" +} +sys_stats() { + set -x + uname -n + uptime + ps axf + ps auxw + top -b -n 1 + ifconfig -a + ip addr list + netstat -i + arp -an + test -d /proc && { + cat /proc/cpuinfo + } + lsscsi + lspci + mount + # df can block, run in background, allow for 5 seconds (!) + local maxcnt=5 + df & + while kill -0 $! >/dev/null 2>&1; do + sleep 1 + if [ $maxcnt -le 0 ]; then + warning "df appears to be hanging, continuing without it" + break + fi + maxcnt=$((maxcnt-1)) + done + set +x +} +time_status() { + date + ntpdc -pn +} +dlm_dump() { + if which dlm_tool >/dev/null 2>&1 ; then + echo NOTICE - Lockspace overview: + dlm_tool ls + dlm_tool ls | grep name | + while read X N ; do + echo NOTICE - Lockspace $N: + dlm_tool lockdump $N + done + echo NOTICE - Lockspace history: + dlm_tool dump + fi +} + + +# +# replace sensitive info with '****' +# +sanitize() { + local f rc + for f in $1/$B_CONF; do + [ -f "$f" ] && sanitize_one $f + done + rc=0 + for f in $1/$CIB_F $1/pengine/*; do + if [ -f "$f" ]; then + if [ "$DO_SANITIZE" ]; then + sanitize_one $f + else + test_sensitive_one $f && rc=1 + fi + fi + done + [ $rc -ne 0 ] && { + warning "some PE or CIB files contain possibly sensitive data" + warning "you may not want to send this report to a public mailing list" + } +} + +# +# remove duplicates if files are same, make links instead +# +consolidate() { + for n in $NODES; do + if [ -f $1/$2 ]; then + rm $1/$n/$2 + else + mv $1/$n/$2 $1 + fi + ln -s ../$2 $1/$n + done +} + +# +# some basic analysis of the report +# +checkcrmvfy() { + for n in $NODES; do + if [ -s $1/$n/$CRM_VERIFY_F ]; then + echo "WARN: crm_verify reported warnings at $n:" + cat $1/$n/$CRM_VERIFY_F + fi + done +} +checkbacktraces() { + for n in $NODES; do + [ -s $1/$n/$BT_F ] && { + echo "WARN: coredumps found at $n:" + egrep 'Core was generated|Program terminated' \ + $1/$n/$BT_F | + sed 's/^/ /' + } + done +} +checkpermissions() { + for n in $NODES; do + if [ -s $1/$n/$PERMISSIONS_F ]; then + echo "WARN: problem with permissions/ownership at $n:" + cat $1/$n/$PERMISSIONS_F + fi + done +} +checklogs() { + local logs pattfile l n + logs=$(find $1 -name $HALOG_F; + for l in $EXTRA_LOGS; do find $1/* -name `basename $l`; done) + [ "$logs" ] || return + pattfile=`mktemp` || + fatal "cannot create temporary files" + add_tmpfiles $pattfile + for p in $LOG_PATTERNS; do + echo "$p" + done > $pattfile + echo "" + echo "Log patterns:" + for n in $NODES; do + cat $logs | grep -f $pattfile + done +} + +# +# check if files have same content in the cluster +# +cibdiff() { + local d1 d2 + d1=`dirname $1` + d2=`dirname $2` + if [ -f $d1/RUNNING -a -f $d2/RUNNING ] || + [ -f $d1/STOPPED -a -f $d2/STOPPED ]; then + if which crm_diff > /dev/null 2>&1; then + crm_diff -c -n $1 -o $2 + else + info "crm_diff(8) not found, cannot diff CIBs" + fi + else + echo "can't compare cibs from running and stopped systems" + fi +} +txtdiff() { + diff -bBu $1 $2 +} +diffcheck() { + [ -f "$1" ] || { + echo "$1 does not exist" + return 1 + } + [ -f "$2" ] || { + echo "$2 does not exist" + return 1 + } + case `basename $1` in + $CIB_F) + cibdiff $1 $2;; + $B_CONF) + txtdiff $1 $2;; # confdiff? + *) + txtdiff $1 $2;; + esac +} +analyze_one() { + local rc node0 n + rc=0 + node0="" + for n in $NODES; do + if [ "$node0" ]; then + diffcheck $1/$node0/$2 $1/$n/$2 + rc=$(($rc+$?)) + else + node0=$n + fi + done + return $rc +} +analyze() { + local f flist + flist="$HOSTCACHE $MEMBERSHIP_F $CIB_F $CRM_MON_F $B_CONF logd.cf $SYSINFO_F" + for f in $flist; do + printf "Diff $f... " + ls $1/*/$f >/dev/null 2>&1 || { + echo "no $1/*/$f :/" + continue + } + if analyze_one $1 $f; then + echo "OK" + [ "$f" != $CIB_F ] && + consolidate $1 $f + else + echo "" + fi + done + checkcrmvfy $1 + checkbacktraces $1 + checkpermissions $1 + checklogs $1 +} +events_all() { + local Epatt title p + Epatt=`echo "$EVENT_PATTERNS" | + while read title p; do [ -n "$p" ] && echo -n "|$p"; done | + sed 's/.//' + ` + grep -E "$Epatt" $1 +} +events() { + local destdir n + destdir=$1 + if [ -f $destdir/$HALOG_F ]; then + events_all $destdir/$HALOG_F > $destdir/events.txt + for n in $NODES; do + awk "\$4==\"$n\"" $destdir/events.txt > $destdir/$n/events.txt + done + else + for n in $NODES; do + [ -f $destdir/$n/$HALOG_F ] || + continue + events_all $destdir/$n/$HALOG_F > $destdir/$n/events.txt + done + fi +} + +# +# description template, editing, and other notes +# +mktemplate() { + cat< $outf + else + warning "no log at $WE" + fi + return + fi + if [ "$cnt" ] && [ $cnt -gt 1 -a $cnt -eq $NODECNT ]; then + MASTER_IS_HOSTLOG=1 + info "found the central log!" + fi + + if [ "$NO_str2time" ]; then + warning "a log found; but we cannot slice it" + warning "please install the perl Date::Parse module" + elif [ "$CTS" ]; then + cts_findlogseg $CTS $HA_LOG > $outf + else + getstampproc=`find_getstampproc < $HA_LOG` + if [ "$getstampproc" ]; then + export getstampproc # used by linetime + dumplogset $HA_LOG $FROM_TIME $TO_TIME > $outf && + loginfo $HA_LOG > $outf.info || + fatal "disk full" + else + warning "could not figure out the log format of $HA_LOG" + fi + fi +} +# +# get all other info (config, stats, etc) +# +collect_info() { + local l + sys_info > $WORKDIR/$SYSINFO_F 2>&1 & + sys_stats > $WORKDIR/$SYSSTATS_F 2>&1 & + getconfig $WORKDIR + getpeinputs $FROM_TIME $TO_TIME $WORKDIR & + crmconfig $WORKDIR & + skip_lvl 1 || touch_DC_if_dc $WORKDIR & + getbacktraces $FROM_TIME $TO_TIME $WORKDIR/$BT_F + getconfigurations $WORKDIR + check_perms > $WORKDIR/$PERMISSIONS_F 2>&1 + dlm_dump > $WORKDIR/$DLM_DUMP_F 2>&1 + time_status > $WORKDIR/$TIME_F 2>&1 + corosync_blackbox $FROM_TIME $TO_TIME $WORKDIR/$COROSYNC_RECORDER_F + getratraces $FROM_TIME $TO_TIME $WORKDIR + wait + skip_lvl 1 || sanitize $WORKDIR + + for l in $EXTRA_LOGS; do + [ "$NO_str2time" ] && break + [ ! -f "$l" ] && continue + if [ "$l" = "$HA_LOG" -a "$l" != "$HALOG_F" ]; then + ln -s $HALOG_F $WORKDIR/`basename $l` + continue + fi + getstampproc=`find_getstampproc < $l` + if [ "$getstampproc" ]; then + export getstampproc # used by linetime + dumplogset $l $FROM_TIME $TO_TIME > $WORKDIR/`basename $l` && + loginfo $l > $WORKDIR/`basename $l`.info || + fatal "disk full" + else + warning "could not figure out the log format of $l" + fi + done +} +finalword() { + if [ "$COMPRESS" = "1" ]; then + echo "The report is saved in $DESTDIR/$DEST.tar$COMPRESS_EXT" + else + echo "The report is saved in $DESTDIR/$DEST" + fi + echo " " + echo "Thank you for taking time to create this report." +} + +[ $# -eq 0 ] && usage + +# check for the major prereq for a) parameter parsing and b) +# parsing logs +# +NO_str2time="" +t=`str2time "12:00"` +if [ "$t" = "" ]; then + NO_str2time=1 + is_collector || + fatal "please install the perl Date::Parse module" +fi + +WE=`uname -n` # who am i? +tmpdir=`mktemp -t -d .hb_report.workdir.XXXXXX` || + fatal "disk full" +add_tmpfiles $tmpdir +WORKDIR=$tmpdir + +# +# part 1: get and check options; and the destination +# +if ! is_collector; then + setvarsanddefaults + userargs="$@" + DESTDIR=. + DEST="hb_report-"`date +"%a-%d-%b-%Y"` + while getopts f:t:l:u:X:p:L:e:E:n:MSDCZAVsvhdQ o; do + case "$o" in + h) usage;; + V) version;; + f) + if echo "$OPTARG" | grep -qs '^cts:'; then + FROM_TIME=0 # to be calculated later + CTS=`echo "$OPTARG" | sed 's/cts://'` + DEST="cts-$CTS-"`date +"%a-%d-%b-%Y"` + else + FROM_TIME=`str2time "$OPTARG"` + chktime "$FROM_TIME" "$OPTARG" + fi + ;; + t) TO_TIME=`str2time "$OPTARG"` + chktime "$TO_TIME" "$OPTARG" + ;; + n) NODES_SOURCE=user + USER_NODES="$USER_NODES $OPTARG" + ;; + u) SSH_USER="$OPTARG";; + X) SSH_OPTS="$SSH_OPTS $OPTARG";; + l) HA_LOG="$OPTARG";; + e) EDITOR="$OPTARG";; + p) SANITIZE="$SANITIZE $OPTARG";; + s) DO_SANITIZE="1";; + Q) SKIP_LVL=$((SKIP_LVL + 1));; + L) LOG_PATTERNS="$LOG_PATTERNS $OPTARG";; + S) NO_SSH=1;; + D) NO_DESCRIPTION=1;; + C) : ;; + Z) FORCE_REMOVE_DEST=1;; + M) EXTRA_LOGS="";; + E) EXTRA_LOGS="$EXTRA_LOGS $OPTARG";; + A) USER_CLUSTER_TYPE="openais";; + v) VERBOSITY=$((VERBOSITY + 1));; + d) COMPRESS="";; + [?]) usage short;; + esac + done + shift $(($OPTIND-1)) + [ $# -gt 1 ] && usage short + set_dest $* + [ "$FROM_TIME" ] || usage short + WORKDIR=$tmpdir/$DEST +else + WORKDIR=$tmpdir/$DEST/$WE +fi + +mkdir -p $WORKDIR +[ -d $WORKDIR ] || no_dir + +if is_collector; then + cat > $WORKDIR/.env + . $WORKDIR/.env +fi + +[ $VERBOSITY -gt 1 ] && { + is_collector || { + info "high debug level, please read debug.out" + } + PS4='+ `date +"%T"`: ${FUNCNAME[0]:+${FUNCNAME[0]}:}${LINENO}: ' + if echo "$SHELL" | grep bash > /dev/null && + [ ${BASH_VERSINFO[0]} = "4" ]; then + exec 3>>$WORKDIR/debug.out + BASH_XTRACEFD=3 + else + exec 2>>$WORKDIR/debug.out + fi + set -x +} + +compatibility_pcmk + +# allow user to enforce the cluster type +# if not, then it is found out on _all_ nodes +if [ -z "$USER_CLUSTER_TYPE" ]; then + CLUSTER_TYPE=`get_cluster_type` +else + CLUSTER_TYPE=$USER_CLUSTER_TYPE +fi + +# the very first thing we must figure out is which cluster +# stack is used +CORES_DIRS="`2>/dev/null ls -d $HA_VARLIB/cores $PCMK_LIB/cores | uniq`" +PACKAGES="pacemaker libpacemaker3 +pacemaker-pygui pacemaker-pymgmt pymgmt-client +openais libopenais2 libopenais3 corosync libcorosync4 +resource-agents cluster-glue libglue2 ldirectord +heartbeat heartbeat-common heartbeat-resources libheartbeat2 +ocfs2-tools ocfs2-tools-o2cb ocfs2console +ocfs2-kmp-default ocfs2-kmp-pae ocfs2-kmp-xen ocfs2-kmp-debug ocfs2-kmp-trace +drbd drbd-kmp-xen drbd-kmp-pae drbd-kmp-default drbd-kmp-debug drbd-kmp-trace +drbd-heartbeat drbd-pacemaker drbd-utils drbd-bash-completion drbd-xen +lvm2 lvm2-clvm cmirrord +libdlm libdlm2 libdlm3 +hawk ruby lighttpd +kernel-default kernel-pae kernel-xen +glibc +" +case "$CLUSTER_TYPE" in +openais) + CONF=/etc/corosync/corosync.conf # corosync? + if test -f $CONF; then + CORES_DIRS="$CORES_DIRS /var/lib/corosync" + else + CONF=/etc/ais/openais.conf + CORES_DIRS="$CORES_DIRS /var/lib/openais" + fi + CF_SUPPORT=$HA_NOARCHBIN/openais_conf_support.sh + MEMBERSHIP_TOOL_OPTS="" + unset HOSTCACHE HB_UUID_F + ;; +heartbeat) + CONF=$HA_CF + CF_SUPPORT=$HA_NOARCHBIN/ha_cf_support.sh + MEMBERSHIP_TOOL_OPTS="-H" + ;; +esac +B_CONF=`basename $CONF` + +if test -f "$CF_SUPPORT"; then + . $CF_SUPPORT +else + fatal "no stack specific support: $CF_SUPPORT" +fi + +if [ "x$CTS" = "x" ] || is_collector; then + getlogvars + debug "log settings: facility=$HA_LOGFACILITY logfile=$HA_LOGFILE debugfile=$HA_DEBUGFILE" +elif ! is_collector; then + ctslog=`findmsg "CTS: Stack:" | awk '{print $1}'` + debug "Using CTS control file: $ctslog" + USER_NODES=`grep CTS: $ctslog | grep -v debug: | grep " \* " | sed s:.*\\\*::g | sort -u | tr '\\n' ' '` + HA_LOGFACILITY=`findmsg "CTS:.*Environment.SyslogFacility" | awk '{print $NF}'` + NODES_SOURCE=user +fi + +# the goods +ANALYSIS_F=analysis.txt +DESCRIPTION_F=description.txt +HALOG_F=ha-log.txt +BT_F=backtraces.txt +SYSINFO_F=sysinfo.txt +SYSSTATS_F=sysstats.txt +DLM_DUMP_F=dlm_dump.txt +TIME_F=time.txt +export ANALYSIS_F DESCRIPTION_F HALOG_F BT_F SYSINFO_F SYSSTATS_F DLM_DUMP_F TIME_F +CRM_MON_F=crm_mon.txt +MEMBERSHIP_F=members.txt +HB_UUID_F=hb_uuid.txt +HOSTCACHE=hostcache +CRM_VERIFY_F=crm_verify.txt +PERMISSIONS_F=permissions.txt +CIB_F=cib.xml +CIB_TXT_F=cib.txt +COROSYNC_RECORDER_F=fdata.txt +export CRM_MON_F MEMBERSHIP_F CRM_VERIFY_F CIB_F CIB_TXT_F HB_UUID_F PERMISSIONS_F +export COROSYNC_RECORDER_F +CONFIGURATIONS="/etc/drbd.conf /etc/drbd.d /etc/booth/booth.conf" +export CONFIGURATIONS + +THIS_IS_NODE="" +if ! is_collector; then + MASTER_NODE=$WE + NODES=`getnodes` + debug "nodes: `echo $NODES`" +fi +NODECNT=`echo $NODES | wc -w` +if [ "$NODECNT" = 0 ]; then + fatal "could not figure out a list of nodes; is this a cluster node?" +fi +if echo $NODES | grep -wqs $WE; then # are we a node? + THIS_IS_NODE=1 +fi + +# this only on master +if ! is_collector; then + + # if this is not a node, then some things afterwards might + # make no sense (not work) + if ! is_node && [ "$NODES_SOURCE" != user ]; then + warning "this is not a node and you didn't specify a list of nodes using -n" + fi +# +# part 2: ssh business +# + # find out if ssh works + if [ -z "$NO_SSH" ]; then + # if the ssh user was supplied, consider that it + # works; helps reduce the number of ssh invocations + findsshuser + if [ -n "$SSH_USER" ]; then + SSH_OPTS="$SSH_OPTS -o User=$SSH_USER" + fi + fi + # assume that only root can collect data + SUDO="" + if [ -z "$SSH_USER" -a `id -u` != 0 ] || + [ -n "$SSH_USER" -a "$SSH_USER" != root ]; then + debug "ssh user other than root, use sudo" + SUDO="sudo -u root" + fi + LOCAL_SUDO="" + if [ `id -u` != 0 ]; then + debug "local user other than root, use sudo" + LOCAL_SUDO="sudo -u root" + fi +fi + +if is_collector && [ "$HA_LOGFACILITY" ]; then + logmark $HA_LOGFACILITY.$HA_LOGLEVEL $UNIQUE_MSG + # allow for the log message to get (hopefully) written to the + # log file + sleep 1 +fi + +# +# part 4: find the logs and cut out the segment for the period +# + +# if the master is also a node, getlog is going to be invoked +# from the collector +(is_master && is_node) || + getlog + +if ! is_collector; then + for node in $NODES; do + if node_needs_pwd $node; then + info "Please provide password for `say_ssh_user` at $node" + info "Note that collecting data will take a while." + start_slave_collector $node + else + start_slave_collector $node & + SLAVEPIDS="$SLAVEPIDS $!" + fi + done +fi + +# +# part 5: endgame: +# slaves tar their results to stdout, the master waits +# for them, analyses results, asks the user to edit the +# problem description template, and prints final notes +# +if is_collector; then + collect_info + (cd $WORKDIR/.. && tar -h -cf - $WE) +else + if [ -n "$SLAVEPIDS" ]; then + wait $SLAVEPIDS + fi + analyze $WORKDIR > $WORKDIR/$ANALYSIS_F & + events $WORKDIR & + mktemplate > $WORKDIR/$DESCRIPTION_F + [ "$NO_DESCRIPTION" ] || { + echo press enter to edit the problem description... + read junk + edittemplate $WORKDIR/$DESCRIPTION_F + } + wait + if [ "$COMPRESS" = "1" ]; then + pickcompress + (cd $WORKDIR/.. && tar cf - $DEST) | $COMPRESS_PROG > $DESTDIR/$DEST.tar$COMPRESS_EXT + else + mv $WORKDIR $DESTDIR + fi + finalword +fi diff --git a/hb_report/openais_conf_support.sh b/hb_report/openais_conf_support.sh new file mode 100644 index 0000000..b96d1aa --- /dev/null +++ b/hb_report/openais_conf_support.sh @@ -0,0 +1,97 @@ + # Copyright (C) 2007 Dejan Muhamedagic + # + # This program is free software; you can redistribute it and/or + # modify it under the terms of the GNU General Public + # License as published by the Free Software Foundation; either + # version 2.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +# +# Stack specific part (openais) +# openais.conf/logd.cf parsing +# +# cut out a stanza +getstanza() { + awk -v name="$1" ' + !in_stanza && NF==2 && /^[a-z][a-z]*[[:space:]]*{/ { # stanza start + if ($1 == name) + in_stanza = 1 + } + in_stanza { print } + in_stanza && NF==1 && $1 == "}" { exit } + ' +} +# supply stanza in $1 and variable name in $2 +# (stanza is optional) +getcfvar() { + [ -f "$CONF" ] || return + sed 's/#.*//' < $CONF | + if [ $# -eq 2 ]; then + getstanza "$1" + shift 1 + else + cat + fi | + awk -v varname="$1" ' + NF==2 && match($1,varname":$")==1 { print $2; exit; } + ' +} +iscfvarset() { + test "`getcfvar $1`" +} +iscfvartrue() { + getcfvar $1 $2 | + egrep -qsi "^(true|y|yes|on|1)" +} +uselogd() { + iscfvartrue use_logd +} +get_ais_logvars() { + if iscfvartrue to_file; then + HA_LOGFILE=`getcfvar logfile` + HA_LOGFILE=${HA_LOGFILE:-"syslog"} + HA_DEBUGFILE=$HA_LOGFILE + elif iscfvartrue to_syslog; then + HA_LOGFACILITY=`getcfvar syslog_facility` + HA_LOGFACILITY=${HA_LOGFACILITY:-"daemon"} + fi +} +getlogvars() { + HA_LOGFACILITY=${HA_LOGFACILITY:-$DEFAULT_HA_LOGFACILITY} + HA_LOGLEVEL="info" + iscfvartrue debug && # prefer debug level if set + HA_LOGLEVEL="debug" + if uselogd; then + [ -f "$LOGD_CF" ] || { + debug "logd used but logd.cf not found: using defaults" + return # no configuration: use defaults + } + debug "reading log settings from $LOGD_CF" + get_logd_logvars + else + debug "reading log settings from $CONF" + get_ais_logvars + fi +} +cluster_info() { + : echo "openais version: how?" + if [ "$CONF" = /etc/corosync/corosync.conf ]; then + /usr/sbin/corosync -v + fi +} +essential_files() { + cat< + # + # This program is free software; you can redistribute it and/or + # modify it under the terms of the GNU General Public + # License as published by the Free Software Foundation; either + # version 2.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +# +# figure out the cluster type, depending on the process list +# and existence of configuration files +# +get_cluster_type() { + if ps -ef | egrep -qs '[a]isexec|[c]orosync' || + [ -f /etc/ais/openais.conf -a ! -f "$HA_CF" ] || + [ -f /etc/corosync/corosync.conf -a ! -f "$HA_CF" ] + then + debug "this is OpenAIS cluster stack" + echo "openais" + else + debug "this is Heartbeat cluster stack" + echo "heartbeat" + fi +} +# +# find out which membership tool is installed +# +echo_membership_tool() { + local f membership_tools + membership_tools="ccm_tool crm_node" + for f in $membership_tools; do + which $f 2>/dev/null && break + done +} +# find out if ptest or crm_simulate +# +echo_ptest_tool() { + local f ptest_progs + ptest_progs="crm_simulate ptest" + for f in $ptest_progs; do + which $f 2>/dev/null && break + done +} +# +# find nodes for this cluster +# +getnodes() { + # 1. set by user? + if [ "$USER_NODES" ]; then + echo $USER_NODES + # 2. running crm + elif iscrmrunning; then + debug "querying CRM for nodes" + get_crm_nodes + # 3. hostcache + elif [ -f $HA_VARLIB/hostcache ]; then + debug "reading nodes from $HA_VARLIB/hostcache" + awk '{print $1}' $HA_VARLIB/hostcache + # 4. ha.cf + elif [ "$CLUSTER_TYPE" = heartbeat ]; then + debug "reading nodes from ha.cf" + getcfvar node + # 5. if the cluster's stopped, try the CIB + elif [ -f $CIB_DIR/$CIB_F ]; then + debug "reading nodes from the archived $CIB_DIR/$CIB_F" + (CIB_file=$CIB_DIR/$CIB_F get_crm_nodes) + fi +} + +logd_getcfvar() { + sed 's/#.*//' < $LOGD_CF | + grep -w "^$1" | + sed 's/^[^[:space:]]*[[:space:]]*//' +} +get_logd_logvars() { + # unless logfacility is set to none, heartbeat/ha_logd are + # going to log through syslog + HA_LOGFACILITY=`logd_getcfvar logfacility` + [ "" = "$HA_LOGFACILITY" ] && HA_LOGFACILITY=$DEFAULT_HA_LOGFACILITY + [ none = "$HA_LOGFACILITY" ] && HA_LOGFACILITY="" + HA_LOGFILE=`logd_getcfvar logfile` + HA_DEBUGFILE=`logd_getcfvar debugfile` +} +findlogdcf() { + local f + for f in \ + `test -x $HA_BIN/ha_logd && + which strings > /dev/null 2>&1 && + strings $HA_BIN/ha_logd | grep 'logd\.cf'` \ + `for d; do echo $d/logd.cf $d/ha_logd.cf; done` + do + if [ -f "$f" ]; then + echo $f + debug "found logd.cf at $f" + return 0 + fi + done + debug "no logd.cf" + return 1 +} +# +# logging +# +syslogmsg() { + local severity logtag + severity=$1 + shift 1 + logtag="" + [ "$HA_LOGTAG" ] && logtag="-t $HA_LOGTAG" + logger -p ${HA_LOGFACILITY:-$DEFAULT_HA_LOGFACILITY}.$severity $logtag $* +} + +# +# find log destination +# +findmsg() { + local d syslogdirs favourites mark log + # this is tricky, we try a few directories + syslogdirs="/var/log /var/logs /var/syslog /var/adm + /var/log/ha /var/log/cluster /var/log/pacemaker + /var/log/heartbeat /var/log/crm /var/log/corosync /var/log/openais" + favourites="ha-*" + mark=$1 + log="" + for d in $syslogdirs; do + [ -d $d ] || continue + log=`grep -l -e "$mark" $d/$favourites` && break + test "$log" && break + log=`grep -l -e "$mark" $d/*` && break + test "$log" && break + done 2>/dev/null + [ "$log" ] && + ls -t $log | tr '\n' ' ' + [ "$log" ] && + debug "found HA log at `ls -t $log | tr '\n' ' '`" || + debug "no HA log found in $syslogdirs" +} + +# +# print a segment of a log file +# +str2time() { + perl -e "\$time='$*';" -e ' + $unix_tm = 0; + eval "use Date::Parse"; + if (!$@) { + $unix_tm = str2time($time); + } else { + eval "use Date::Manip"; + if (!$@) { + $unit_tm = UnixDate(ParseDateString($time), "%s"); + } + } + if ($unix_tm != "") { + $unix_tm = int($unix_tm); + } + print $unix_tm; + ' +} +getstamp_syslog() { + awk '{print $1,$2,$3}' +} +getstamp_legacy() { + awk '{print $2}' | sed 's/_/ /' +} +getstamp_rfc5424() { + awk '{print $1}' +} +get_ts() { + local l="$1" ts + ts=$(str2time `echo "$l" | $getstampproc`) + if [ -z "$ts" ]; then + local fmt + for fmt in rfc5424 syslog legacy; do + [ "getstamp_$fmt" = "$getstampproc" ] && continue + ts=$(str2time `echo "$l" | getstamp_$fmt`) + [ -n "$ts" ] && break + done + fi + echo $ts +} +linetime() { + get_ts "`tail -n +$2 $1 | head -1`" +} +find_getstampproc() { + local t l func trycnt + t=0 l="" func="" + trycnt=10 + while [ $trycnt -gt 0 ] && read l; do + t=$(str2time `echo $l | getstamp_syslog`) + if [ "$t" ]; then + func="getstamp_syslog" + debug "the log file is in the syslog format" + break + fi + t=$(str2time `echo $l | getstamp_rfc5424`) + if [ "$t" ]; then + func="getstamp_rfc5424" + debug "the log file is in the rfc5424 format" + break + fi + t=$(str2time `echo $l | getstamp_legacy`) + if [ "$t" ]; then + func="getstamp_legacy" + debug "the log file is in the legacy format (please consider switching to syslog format)" + break + fi + trycnt=$(($trycnt-1)) + done + echo $func +} +find_first_ts() { + local l ts + while read l; do + ts=`get_ts "$l"` + [ "$ts" ] && break + warning "cannot extract time: |$l|; will try the next one" + done + echo $ts +} +findln_by_time() { + local logf=$1 + local tm=$2 + local first=1 + local last=`wc -l < $logf` + local tmid mid trycnt + while [ $first -le $last ]; do + mid=$((($last+$first)/2)) + trycnt=10 + while [ $trycnt -gt 0 ]; do + tmid=`linetime $logf $mid` + [ "$tmid" ] && break + warning "cannot extract time: $logf:$mid; will try the next one" + trycnt=$(($trycnt-1)) + # shift the whole first-last segment + first=$(($first-1)) + last=$(($last-1)) + mid=$((($last+$first)/2)) + done + if [ -z "$tmid" ]; then + warning "giving up on log..." + return + fi + if [ $tmid -gt $tm ]; then + last=$(($mid-1)) + elif [ $tmid -lt $tm ]; then + first=$(($mid+1)) + else + break + fi + done + echo $mid +} + +dumplog() { + local logf=$1 + local from_line=$2 + local to_line=$3 + [ "$from_line" ] || + return + tail -n +$from_line $logf | + if [ "$to_line" ]; then + head -$(($to_line-$from_line+1)) + else + cat + fi +} + +# +# find files newer than a and older than b +# +isnumber() { + echo "$*" | grep -qs '^[0-9][0-9]*$' +} +touchfile() { + local t + t=`mktemp` && + perl -e "\$file=\"$t\"; \$tm=$1;" -e 'utime $tm, $tm, $file;' && + echo $t +} +find_files() { + local dirs from_time to_time + local from_stamp to_stamp findexp + dirs=$1 + from_time=$2 + to_time=$3 + isnumber "$from_time" && [ "$from_time" -gt 0 ] || { + warning "sorry, can't find files based on time if you don't supply time" + return + } + if ! from_stamp=`touchfile $from_time`; then + warning "can't create temporary files" + return + fi + add_tmpfiles $from_stamp + findexp="-newer $from_stamp" + if isnumber "$to_time" && [ "$to_time" -gt 0 ]; then + if ! to_stamp=`touchfile $to_time`; then + warning "can't create temporary files" + return + fi + add_tmpfiles $to_stamp + findexp="$findexp ! -newer $to_stamp" + fi + find $dirs -type f $findexp +} + +# +# check permissions of files/dirs +# +pl_checkperms() { +perl -e ' +# check permissions and ownership +# uid and gid are numeric +# everything must match exactly +# no error checking! (file should exist, etc) +($filename, $perms, $in_uid, $in_gid) = @ARGV; +($mode,$uid,$gid) = (stat($filename))[2,4,5]; +$p=sprintf("%04o", $mode & 07777); +$p ne $perms and exit(1); +$uid ne $in_uid and exit(1); +$gid ne $in_gid and exit(1); +' $* +} +num_id() { + getent $1 $2 | awk -F: '{print $3}' +} +chk_id() { + [ "$2" ] && return 0 + echo "$1: id not found" + return 1 +} +check_perms() { + local f p uid gid n_uid n_gid + essential_files | + while read type f p uid gid; do + [ -$type $f ] || { + echo "$f wrong type or doesn't exist" + continue + } + n_uid=`num_id passwd $uid` + chk_id "$uid" "$n_uid" || continue + n_gid=`num_id group $gid` + chk_id "$gid" "$n_gid" || continue + pl_checkperms $f $p $n_uid $n_gid || { + echo "wrong permissions or ownership for $f:" + ls -ld $f + } + done +} + +# +# coredumps +# +pkg_mgr_list() { +# list of: +# regex pkg_mgr +# no spaces allowed in regex + cat<&1 | + awk ' + # this zypper version dumps all packages on a single line + /Missing separate debuginfos.*zypper.install/ { + sub(".*zypper.install ",""); print + exit} + n>0 && /^Try: zypper install/ {gsub("\"",""); print $NF} + n>0 {n=0} + /Missing separate debuginfo/ {n=1} + ' | sort -u +} +fetchpkg_zypper() { + local pkg + debug "get debuginfo packages using zypper: $@" + zypper -qn ref > /dev/null + for pkg in $@; do + zypper -qn install -C $pkg >/dev/null + done +} +find_pkgmgr() { + local binary=$1 core=$2 + local regex pkg_mgr + pkg_mgr_list | + while read regex pkg_mgr; do + if gdb $binary $core &1 | + grep "$regex" > /dev/null; then + echo $pkg_mgr + break + fi + done +} +get_debuginfo() { + local binary=$1 core=$2 + local pkg_mgr pkgs + gdb $binary $core /dev/null | + egrep 'Missing.*debuginfo|no debugging symbols found' > /dev/null || + return # no missing debuginfo + pkg_mgr=`find_pkgmgr $binary $core` + if [ -z "$pkg_mgr" ]; then + warning "found core for $binary but there is no debuginfo and we don't know how to get it on this platform" + return + fi + pkgs=`listpkg_$pkg_mgr $binary $core` + [ -n "$pkgs" ] && + fetchpkg_$pkg_mgr $pkgs +} +findbinary() { + local random_binary binary fullpath + random_binary=`which cat 2>/dev/null` # suppose we are lucky + binary=`gdb $random_binary $1 < /dev/null 2>/dev/null | + grep 'Core was generated' | awk '{print $5}' | + sed "s/^.//;s/[.':]*$//"` + if [ x = x"$binary" ]; then + debug "could not detect the program name for core $1 from the gdb output; will try with file(1)" + binary=$(file $1 | awk '/from/{ + for( i=1; i<=NF; i++ ) + if( $i == "from" ) { + print $(i+1) + break + } + }') + binary=`echo $binary | tr -d "'"` + binary=$(echo $binary | tr -d '`') + if [ "$binary" ]; then + binary=`which $binary 2>/dev/null` + fi + fi + if [ x = x"$binary" ]; then + warning "could not find the program path for core $1" + return + fi + fullpath=`which $binary 2>/dev/null` + if [ x = x"$fullpath" ]; then + for d in $HA_BIN $CRM_DAEMON_DIR; do + if [ -x $d/$binary ]; then + echo $d/$binary + debug "found the program at $d/$binary for core $1" + else + warning "could not find the program path for core $1" + fi + done + else + echo $fullpath + debug "found the program at $fullpath for core $1" + fi +} +getbt() { + local corefile absbinpath + which gdb > /dev/null 2>&1 || { + warning "please install gdb to get backtraces" + return + } + for corefile; do + absbinpath=`findbinary $corefile` + [ x = x"$absbinpath" ] && continue + get_debuginfo $absbinpath $corefile + echo "====================== start backtrace ======================" + ls -l $corefile + gdb -batch -n -quiet -ex ${BT_OPTS:-"thread apply all bt full"} -ex quit \ + $absbinpath $corefile 2>/dev/null + echo "======================= end backtrace =======================" + done +} + +# +# heartbeat configuration/status +# +iscrmrunning() { + local pid maxwait + ps -ef | grep -qs [c]rmd || return 1 + crmadmin -D >/dev/null 2>&1 & + pid=$! + maxwait=100 + while kill -0 $pid 2>/dev/null && [ $maxwait -gt 0 ]; do + sleep 0.1 + maxwait=$(($maxwait-1)) + done + if kill -0 $pid 2>/dev/null; then + kill $pid + false + else + wait $pid + fi +} +dumpstate() { + crm_mon -1 | grep -v '^Last upd' > $1/$CRM_MON_F + cibadmin -Ql > $1/$CIB_F + `echo_membership_tool` $MEMBERSHIP_TOOL_OPTS -p > $1/$MEMBERSHIP_F 2>&1 +} +getconfig() { + [ -f "$CONF" ] && + cp -p $CONF $1/ + [ -f "$LOGD_CF" ] && + cp -p $LOGD_CF $1/ + if iscrmrunning; then + dumpstate $1 + touch $1/RUNNING + else + cp -p $CIB_DIR/$CIB_F $1/ 2>/dev/null + touch $1/STOPPED + fi + [ "$HOSTCACHE" ] && + cp -p $HA_VARLIB/hostcache $1/$HOSTCACHE 2>/dev/null + [ "$HB_UUID_F" ] && + crm_uuid -r > $1/$HB_UUID_F 2>&1 + [ -f "$1/$CIB_F" ] && + crm_verify -V -x $1/$CIB_F >$1/$CRM_VERIFY_F 2>&1 +} +crmconfig() { + [ -f "$1/$CIB_F" ] && which crm >/dev/null 2>&1 && + CIB_file=$1/$CIB_F crm configure show >$1/$CIB_TXT_F 2>&1 +} +get_crm_nodes() { + cibadmin -Ql -o nodes | + awk ' + //dev/null 2>&1; then + fping -a $@ 2>/dev/null + else + local h + for h; do ping -c 2 -q $h >/dev/null 2>&1 && echo $h; done + fi +} + +# +# remove values of sensitive attributes +# +# this is not proper xml parsing, but it will work under the +# circumstances +is_sensitive_xml() { + local patt epatt + epatt="" + for patt in $SANITIZE; do + epatt="$epatt|$patt" + done + epatt="`echo $epatt|sed 's/.//'`" + egrep -qs "name=\"$epatt\"" +} +test_sensitive_one() { + local file compress decompress + file=$1 + compress="" + echo $file | grep -qs 'gz$' && compress=gzip + echo $file | grep -qs 'bz2$' && compress=bzip2 + if [ "$compress" ]; then + decompress="$compress -dc" + else + compress=cat + decompress=cat + fi + $decompress < $file | is_sensitive_xml +} +sanitize_xml_attrs() { + local patt + sed $( + for patt in $SANITIZE; do + echo "-e /name=\"$patt\"/s/value=\"[^\"]*\"/value=\"****\"/" + done + ) +} +sanitize_hacf() { + awk ' + $1=="stonith_host"{ for( i=5; i<=NF; i++ ) $i="****"; } + {print} + ' +} +sanitize_one() { + local file compress decompress tmp ref + file=$1 + compress="" + echo $file | grep -qs 'gz$' && compress=gzip + echo $file | grep -qs 'bz2$' && compress=bzip2 + if [ "$compress" ]; then + decompress="$compress -dc" + else + compress=cat + decompress=cat + fi + tmp=`mktemp` + ref=`mktemp` + add_tmpfiles $tmp $ref + if [ -z "$tmp" -o -z "$ref" ]; then + fatal "cannot create temporary files" + fi + touch -r $file $ref # save the mtime + if [ "`basename $file`" = ha.cf ]; then + sanitize_hacf + else + $decompress | sanitize_xml_attrs | $compress + fi < $file > $tmp + mv $tmp $file + touch -r $ref $file +} + +# +# keep the user posted +# +fatal() { + echo "`uname -n`: ERROR: $*" >&2 + exit 1 +} +warning() { + echo "`uname -n`: WARN: $*" >&2 +} +info() { + echo "`uname -n`: INFO: $*" >&2 +} +debug() { + [ "$VERBOSITY" ] && [ $VERBOSITY -gt 0 ] && + echo "`uname -n`: DEBUG: $*" >&2 + return 0 +} +pickfirst() { + for x; do + which $x >/dev/null 2>&1 && { + echo $x + return 0 + } + done + return 1 +} + +# tmp files business +drop_tmpfiles() { + trap 'rm -rf `cat $__TMPFLIST`; rm $__TMPFLIST' EXIT +} +init_tmpfiles() { + if __TMPFLIST=`mktemp`; then + drop_tmpfiles + else + # this is really bad, let's just leave + fatal "eek, mktemp cannot create temporary files" + fi +} +add_tmpfiles() { + test -f "$__TMPFLIST" || return + echo $* >> $__TMPFLIST +} + +# +# get some system info +# +distro() { + local relf f + which lsb_release >/dev/null 2>&1 && { + lsb_release -d + debug "using lsb_release for distribution info" + return + } + relf=`ls /etc/debian_version 2>/dev/null` || + relf=`ls /etc/slackware-version 2>/dev/null` || + relf=`ls -d /etc/*-release 2>/dev/null` && { + for f in $relf; do + test -f $f && { + echo "`ls $f` `cat $f`" + debug "found $relf distribution release file" + return + } + done + } + warning "no lsb_release, no /etc/*-release, no /etc/debian_version: no distro information" +} + +pkg_ver_deb() { + dpkg-query -f '${Name} ${Version}' -W $* 2>/dev/null +} +pkg_ver_rpm() { + rpm -q --qf '%{name} %{version}-%{release} - %{distribution} %{arch}\n' $* 2>&1 | + grep -v 'not installed' +} +pkg_ver_pkg_info() { + for pkg; do + pkg_info | grep $pkg + done +} +pkg_ver_pkginfo() { + for pkg; do + pkginfo $pkg | awk '{print $3}' # format? + done +} +verify_deb() { + debsums -s $* 2>/dev/null +} +verify_rpm() { + rpm --verify $* 2>&1 | grep -v 'not installed' +} +verify_pkg_info() { + : +} +verify_pkginfo() { + : +} + +get_pkg_mgr() { + local pkg_mgr + if which dpkg >/dev/null 2>&1 ; then + pkg_mgr="deb" + elif which rpm >/dev/null 2>&1 ; then + pkg_mgr="rpm" + elif which pkg_info >/dev/null 2>&1 ; then + pkg_mgr="pkg_info" + elif which pkginfo >/dev/null 2>&1 ; then + pkg_mgr="pkginfo" + else + warning "Unknown package manager!" + return + fi + echo $pkg_mgr +} + +pkg_versions() { + local pkg_mgr=`get_pkg_mgr` + [ -z "$pkg_mgr" ] && + return + debug "the package manager is $pkg_mgr" + pkg_ver_$pkg_mgr $* +} +verify_packages() { + local pkg_mgr=`get_pkg_mgr` + [ -z "$pkg_mgr" ] && + return + verify_$pkg_mgr $* +} + +crm_info() { + $CRM_DAEMON_DIR/crmd version 2>&1 +} diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..2e07275 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,25 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = clplumbing pils stonith lrm + +idir=$(includedir)/heartbeat +i_HEADERS = compress.h glue_config.h ha_msg.h + +noinst_HEADERS = config.h lha_internal.h replace_uuid.h diff --git a/include/clplumbing/GSource.h b/include/clplumbing/GSource.h new file mode 100644 index 0000000..2acc9eb --- /dev/null +++ b/include/clplumbing/GSource.h @@ -0,0 +1,236 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_GSOURCE_H +# define _CLPLUMBING_GSOURCE_H +# include + +typedef struct GFDSource_s GFDSource; +typedef struct GCHSource_s GCHSource; +typedef struct GWCSource_s GWCSource; +typedef struct GSIGSource_s GSIGSource; +typedef struct GTRIGSource_s GTRIGSource; + + +void G_main_setmaxdispatchdelay(GSource* s, unsigned long delayms); +void G_main_setmaxdispatchtime(GSource* s, unsigned long dispatchms); +void G_main_setdescription(GSource* s, const char * description); + +void G_main_setmaxdispatchdelay_id(guint id, unsigned long delayms); +void G_main_setmaxdispatchtime_id(guint id, unsigned long dispatchms); +void G_main_setdescription_id(guint id, const char * description); +void G_main_setall_id(guint id, const char * description, unsigned long delayms, unsigned long dispatchms); + + +/*********************************************************************** + * Functions for interfacing input to the mainloop + ***********************************************************************/ + +GSource* +G_main_add_input(int priority, + gboolean can_recurse, + GSourceFuncs* funcs); + +/*********************************************************************** + * Functions for interfacing "raw" file descriptors to the mainloop + ***********************************************************************/ +/* +* Add a file descriptor to the gmainloop world... + */ +GFDSource* G_main_add_fd(int priority, int fd, gboolean can_recurse +, gboolean (*dispatch)(int fd, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify); + +/* + * Delete a file descriptor from the gmainloop world... + * Note: destroys the GFDSource object. + */ +gboolean G_main_del_fd(GFDSource* fdp); + +/* + * Notify us that a file descriptor is blocked on output. + * (i.e., we should poll for output events) + */ +void g_main_output_is_blocked(GFDSource* fdp); + + +/************************************************************** + * Functions for interfacing IPC_Channels to the mainloop + **************************************************************/ +/* + * Add an IPC_channel to the gmainloop world... + */ +GCHSource* G_main_add_IPC_Channel(int priority, IPC_Channel* ch +, gboolean can_recurse +, gboolean (*dispatch)(IPC_Channel* source_data +, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify); + +/* + * the events in this source is paused/resumed + */ + +void G_main_IPC_Channel_pause(GCHSource* chp); +void G_main_IPC_Channel_resume(GCHSource* chp); + + +/* + * Delete an IPC_channel from the gmainloop world... + * Note: destroys the GCHSource object, and the IPC_Channel + * object automatically. + */ +gboolean G_main_del_IPC_Channel(GCHSource* chp); + + +/* + * Set the destroy notify function + * + */ +void set_IPC_Channel_dnotify(GCHSource* chp, + GDestroyNotify notify); + + +/********************************************************************* + * Functions for interfacing IPC_WaitConnections to the mainloop + ********************************************************************/ +/* + * Add an IPC_WaitConnection to the gmainloop world... + * Note that the dispatch function is called *after* the + * connection is accepted. + */ +GWCSource* G_main_add_IPC_WaitConnection(int priority, IPC_WaitConnection* ch +, IPC_Auth* auth_info +, gboolean can_recurse +, gboolean (*dispatch)(IPC_Channel* source_data +, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify); + +/* + * Delete an IPC_WaitConnection from the gmainloop world... + * Note: destroys the GWCSource object, and the IPC_WaitConnection + * object automatically. + */ +gboolean G_main_del_IPC_WaitConnection(GWCSource* wcp); + + +/************************************************************** + * Functions for interfacing Signals to the mainloop + **************************************************************/ +/* + * Add an Signal to the gmainloop world... + */ +GSIGSource* G_main_add_SignalHandler( + int priority, int signal, + gboolean (*dispatch)(int nsig, gpointer user_data), + gpointer userdata, GDestroyNotify notify); + +/* + * Delete an signal from the gmainloop world... + * Note: destroys the GSIGSource object, and the removes the + * Signal Handler automatically. + */ +gboolean G_main_del_SignalHandler(GSIGSource* chp); + + +/* + * Set the destroy notify function + * + */ +void set_SignalHandler_dnotify(GSIGSource* chp, GDestroyNotify notify); + + +/* manage child process death using sig source*/ +#define DEFAULT_MAXDISPATCHTIME 30 /* in ms */ +void set_sigchld_proctrack(int priority, unsigned long maxdisptime); + + + +/************************************************************** + * Functions for interfacing Manual triggers to the mainloop + **************************************************************/ +/* + * Add an Trigger to the gmainloop world... + */ +GTRIGSource* G_main_add_TriggerHandler( + int priority, gboolean (*dispatch)(gpointer user_data), + gpointer userdata, GDestroyNotify notify); + +/* + * Delete an signal from the gmainloop world... + * Note: destroys the GTRIGSource object, and the removes the + * Trigger Handler automatically. + */ +gboolean G_main_del_TriggerHandler(GTRIGSource* chp); + + +/* + * Set the destroy notify function + * + */ +void set_TriggerHandler_dnotify(GTRIGSource* chp, GDestroyNotify notify); + + +void G_main_set_trigger(GTRIGSource* man_src); + +/* + * Create a trigger for triggering an action in a short-lived (temporary) + * child process. + * + * The name isn't wonderful, but we couldn't think of a better one. + */ +GTRIGSource* G_main_add_tempproc_trigger(int priority +, int (*fun)(gpointer p) /* What to do? */ + /* Called in child process */ +, const char * procname /* What do we call this process? */ +, gpointer userdata /* Passed to 'triggerfun' */ +, void (*prefork)(gpointer p) /* Called before fork */ +, void (*postfork)(gpointer p) /* Called by parent process + * after fork(2) call. + * Each has 'userdata' + * passed to it. + */ +, void (*complete)(gpointer p, int status, int signo, int exitcode)); /* called after the child process completes */ + +/* + * Special notes: + * - No more than one child process will be active at a time per trigger + * object. + * + * - If you trigger the action while this object has a child active, + * then it will be re-triggered after the currently running child + * completes. There is no necessary correlation between the + * number of times a the action is triggered and how many + * times it is executed. What is guaranteed is that after you + * trigger the action, it will happen (at least) once - as soon + * as the scheduler gets around to it at the priority you've + * assigned it. But if several are triggered while a child + * process is running, only one process will be instantiated to + * take the action requested by all the trigger calls. + * + * - Child processes are forked off at the priority of the trigger, + * not the priority of the SIGCHLD handler. + * + * - This is useful for writing out updates to a file for example. + * While we're writing one copy out, subsequent updates are + * held off until this one completes. When it completes, then + * the file is written again - but not "n" times - just the + * latest version available at the time the trigger is + * activated (run). + */ +#endif diff --git a/include/clplumbing/GSource_internal.h b/include/clplumbing/GSource_internal.h new file mode 100644 index 0000000..c20a9c9 --- /dev/null +++ b/include/clplumbing/GSource_internal.h @@ -0,0 +1,111 @@ +/* + * Author: Alan Robertson + * Copyright (C) 2005 International Business Machines Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#define MAG_GFDSOURCE 0xfeed0001U +#define MAG_GCHSOURCE 0xfeed0002U +#define MAG_GWCSOURCE 0xfeed0003U +#define MAG_GSIGSOURCE 0xfeed0004U +#define MAG_GTRIGSOURCE 0xfeed0005U +#define MAG_GTIMEOUTSRC 0xfeed0006U + +#define IS_FDSOURCE(p) (p && (p)->magno == MAG_GFDSOURCE) +#define IS_CHSOURCE(p) (p && (p)->magno == MAG_GCHSOURCE) +#define IS_WCSOURCE(p) (p && (p)->magno == MAG_GWCSOURCE) +#define IS_SIGSOURCE(p) (p && (p)->magno == MAG_GSIGSOURCE) +#define IS_TRIGSOURCE(p) (p && (p)->magno == MAG_GTRIGSOURCE) +#define IS_TIMEOUTSRC(p) (p && (p)->magno == MAG_GTIMEOUTSRC) + +#define IS_ONEOFOURS(p) (IS_CHSOURCE(p)|IS_FDSOURCE(p)|IS_WCSOURCE(p)|| \ + IS_SIGSOURCE(p)|IS_TRIGSOURCE(p)||IS_TIMEOUTSRC(p)) + + +#define DEFAULT_MAXDISPATCH 0 +#define DEFAULT_MAXDELAY 0 +#define OTHER_MAXDELAY 100 + +#define COMMON_STRUCTSTART \ +GSource source; /* Common glib struct - must be 1st */ \ +unsigned magno; /* Magic number */ \ +long maxdispatchms; /* Time limit for dispatch function */ \ +long maxdispatchdelayms; /* Max delay before processing */ \ +char detecttime[sizeof(longclock_t)]; \ + /* Time last input detected */ \ +void* udata; /* User-defined data */ \ +guint gsourceid; /* Source id of this source */ \ +const char * description; /* Description of this source */ \ +GDestroyNotify dnotify + +struct GFDSource_s { + COMMON_STRUCTSTART; + gboolean (*dispatch)(int fd, gpointer user_data); + GPollFD gpfd; +}; + + +typedef gboolean (*GCHdispatch)(IPC_Channel* ch, gpointer user_data); + +struct GCHSource_s { + COMMON_STRUCTSTART; + IPC_Channel* ch; + gboolean fd_fdx; + GPollFD infd; + GPollFD outfd; + gboolean dontread; /* TRUE when we don't want to read + * more input for a while - we're + * flow controlling the writer off + */ + gboolean (*dispatch)(IPC_Channel* ch, gpointer user_data); +}; + +struct GWCSource_s { + COMMON_STRUCTSTART; + GPollFD gpfd; + IPC_WaitConnection* wch; + IPC_Auth* auth_info; + gboolean (*dispatch)(IPC_Channel* accept_ch, gpointer udata); +}; + +struct GSIGSource_s { + COMMON_STRUCTSTART; + clock_t sh_detecttime; + int signal; + gboolean signal_triggered; + gboolean (*dispatch)(int signal, gpointer user_data); +}; + +struct GTRIGSource_s { + COMMON_STRUCTSTART; + gboolean manual_trigger; + gboolean (*dispatch)(gpointer user_data); +}; + +/************************************************************ + * Functions for IPC_Channels + ***********************************************************/ +gboolean G_CH_prepare_int(GSource* source, gint* timeout); +gboolean G_CH_check_int(GSource* source); +gboolean G_CH_dispatch_int(GSource* source, GSourceFunc callback, + gpointer user_data); +void G_CH_destroy_int(GSource* source); +GCHSource* +G_main_IPC_Channel_constructor(GSource* source, IPC_Channel* ch +, gpointer userdata, GDestroyNotify notify); diff --git a/include/clplumbing/Gmain_timeout.h b/include/clplumbing/Gmain_timeout.h new file mode 100644 index 0000000..c696a9d --- /dev/null +++ b/include/clplumbing/Gmain_timeout.h @@ -0,0 +1,44 @@ +#ifndef _CLPLUMBING_GMAIN_TIMEOUT_H +#define _CLPLUMBING_GMAIN_TIMEOUT_H +#include +/* + * Copyright (C) 2002 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * These functions must work correctly even if someone resets the + * time-of-day clock. The g_main_timeout_add() function does not have + * this property, since it relies on gettimeofday(). + * + * Our functions have the same semantics - except they always work ;-) + * + * This is because we use longclock_t for our time values. + */ +guint Gmain_timeout_add(guint interval +, GSourceFunc function +, gpointer data); + +guint Gmain_timeout_add_full(gint priority +, guint interval +, GSourceFunc function +, gpointer data +, GDestroyNotify notify); + +void Gmain_timeout_remove(guint tag); +#endif diff --git a/include/clplumbing/Makefile.am b/include/clplumbing/Makefile.am new file mode 100644 index 0000000..599b24c --- /dev/null +++ b/include/clplumbing/Makefile.am @@ -0,0 +1,59 @@ +# +# linux-ha: Linux-HA heartbeat code +# +# Copyright (C) 2002 International Business Machines. +# Author: Alan Robertson +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# +MAINTAINERCLEANFILES = Makefile.in + +idir=$(includedir)/clplumbing + +i_HEADERS = \ + Gmain_timeout.h \ + GSource.h \ + GSource_internal.h \ + apphb_cs.h \ + base64.h \ + cl_log.h \ + cl_poll.h \ + cl_signal.h \ + cl_pidfile.h \ + cl_random.h \ + cl_reboot.h \ + cl_syslog.h \ + cl_uuid.h \ + coredumps.h \ + cpulimits.h \ + ipc.h \ + lsb_exitcodes.h \ + loggingdaemon.h \ + longclock.h \ + mkstemp_mode.h \ + netstring.h \ + proctrack.h \ + realtime.h \ + replytrack.h \ + setproctitle.h \ + timers.h \ + uids.h \ + cl_misc.h \ + md5.h \ + cl_plugin.h \ + cl_tiebreaker.h \ + cl_quorum.h \ + cl_quorumd.h diff --git a/include/clplumbing/apphb_cs.h b/include/clplumbing/apphb_cs.h new file mode 100644 index 0000000..9506db6 --- /dev/null +++ b/include/clplumbing/apphb_cs.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2002 Alan Robertson + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _CLPLUMBING_APPHB_CS_H +#define _CLPLUMBING_APPHB_CS_H + +/* Internal client-server messages for APP heartbeat service */ + +#ifndef HA_VARRUNDIR +#define HA_VARRUNDIR "/var/run" +#endif +#define APPHBSOCKPATH HA_VARRUNDIR "/heartbeat/apphb.comm" + +#define APPHB_TLEN 8 +#define APPHB_OLEN 256 + +#define REGISTER "reg" +#define UNREGISTER "unreg" +#define HEARTBEAT "hb" +#define SETINTERVAL "setint" +#define SETWARNTIME "setwarn" +#define SETREBOOT "setboot" + +/* + * These messages are really primitive. + * They don't have any form of version control, they're in host byte order, + * and they're all in binary... + * + * Fortunately, this is a very simple local service ;-) + */ + +/* Generic (no parameter) App heartbeat message */ +struct apphb_msg { + char msgtype [APPHB_TLEN]; +}; + +/* App heartbeat Registration message */ +struct apphb_signupmsg { + char msgtype [APPHB_TLEN]; + char appname [APPHB_OLEN]; + char appinstance [APPHB_OLEN]; + char curdir [APPHB_OLEN]; + pid_t pid; + uid_t uid; + gid_t gid; +}; + +/* App heartbeat setinterval / setwarn message */ +struct apphb_msmsg { + char msgtype [APPHB_TLEN]; + unsigned long ms; +}; + +/* App heartbeat server return code (errno) */ +struct apphb_rc { + int rc; +}; +#endif diff --git a/include/clplumbing/base64.h b/include/clplumbing/base64.h new file mode 100644 index 0000000..4ea6810 --- /dev/null +++ b/include/clplumbing/base64.h @@ -0,0 +1,50 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_BASE64_H +# define _CLPLUMBING_BASE64_H +/* + * + * Base64 conversion functions. + * They convert from a binary array into a single string + * in base 64. This is almost (but not quite) like section 5.2 of RFC 1341 + * The only difference is that we don't care about line lengths. + * We do use their encoding algorithm. + * + */ + +#define B64inunit 3 +#define B64outunit 4 + +/* How long will the base64 string be for a particular binary object size? */ +/* This is like strlen() and doesn't include the '\0' byte at the end */ +#define B64_stringlen(bytes) \ + ((((bytes)+(B64inunit-1))/B64inunit)*B64outunit) + +/* How many bytes to you need to malloc to store a base64 string? */ +/* (includes space for the '\0' terminator byte) */ +#define B64_stringspace(bytes) (B64_stringlen(bytes)+1) + +/* How many bytes will a base64 string take up back in binary? */ +/* Note: This may be as much as two 2 bytes more than strictly needed */ +#define B64_maxbytelen(slen) (((slen) / B64outunit)*B64inunit) + +/* Returns strlen() of base64 string returned in "output" */ +int binary_to_base64(const void * data, int nbytes, char * output, int outlen); + +/* Returns the size of the binary object we returned in "output" */ +int base64_to_binary(const char * input, int inlen, void * output, int outlen); +#endif diff --git a/include/clplumbing/cl_log.h b/include/clplumbing/cl_log.h new file mode 100644 index 0000000..aa30fcd --- /dev/null +++ b/include/clplumbing/cl_log.h @@ -0,0 +1,99 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_CL_LOG_H +# define _CLPLUMBING_CL_LOG_H +# include +# include + +#define TIME_T unsigned long +#define HA_FAIL 0 +#define HA_OK 1 +#define MAXLINE (512*10) + +/* this is defined by the caller */ +struct logspam { + const char *id; /* identifier */ + int max; /* maximum number of messages ... */ + time_t window; /* ... within this timeframe */ + time_t reset_time; /* log new messages after this time */ + const char *advice; /* what to log in case messages get suppressed */ +}; + +/* this is internal (oblique to the caller) */ +struct msg_ctrl { + struct logspam *lspam; /* */ + time_t *msg_slots; /* msg slot root (space for lspam->max) */ + int last; /* last used msg slot [0..lspam->max-1]; -1 on init */ + int cnt; /* current msg count [0..lspam->max] */ + time_t suppress_t; /* messages blocked since this time */ +}; + +struct IPC_CHANNEL; + +extern int debug_level; +#define ANYDEBUG (debug_level) +#define DEBUGDETAILS (debug_level >= 2) +#define DEBUGAUTH (debug_level >=3) +#define DEBUGMODULE (debug_level >=3) +#define DEBUGPKT (debug_level >= 4) +#define DEBUGPKTCONT (debug_level >= 5) + +void cl_direct_log(int priority, const char* buf, gboolean, const char*, int, TIME_T); +void cl_log(int priority, const char * fmt, ...) G_GNUC_PRINTF(2,3); +void cl_limit_log(struct msg_ctrl *ml, int priority, const char * fmt, ...) G_GNUC_PRINTF(3,4); +struct msg_ctrl *cl_limit_log_new(struct logspam *lspam); +void cl_limit_log_destroy(struct msg_ctrl *ml); +void cl_limit_log_reset(struct msg_ctrl *ml); +void cl_perror(const char * fmt, ...) G_GNUC_PRINTF(1,2); +void cl_log_enable_stderr(int truefalse); +void cl_log_enable_stdout(int truefalse); +gboolean cl_log_test_logd(void); +void cl_log_set_uselogd(int truefalse); +void cl_log_enable_syslog_filefmt(int truefalse); +void cl_log_use_buffered_io(int truefalse); +gboolean cl_log_get_uselogd(void); +void cl_log_set_facility(int facility); +void cl_log_set_entity(const char * entity); +void cl_log_set_syslogprefix(const char *prefix); +void cl_log_set_logfile(const char * path); +void cl_log_set_debugfile(const char * path); +void cl_inherit_logging_environment(int maxqlen); +int cl_log_set_logd_channel_source( void (*create_callback)(struct IPC_CHANNEL* chan), + GDestroyNotify destroy_callback); +int cl_log_get_logdtime(void); +void cl_log_set_logdtime(int logdintval); + +char * ha_timestamp(TIME_T t); +void cl_glib_msg_handler(const gchar *log_domain +, GLogLevelFlags log_level, const gchar *message +, gpointer user_data); + +void cl_flush_logs(void); +void cl_log_args(int argc, char **argv); +int cl_log_is_logd_fd(int fd); +const char * prio2str(int priority); + +/* cl_log_use_buffered_io and cl_log_do_fflush as optimization for logd, + * so it may buffer a few message lines, then fflush them out in one write. + * Set do_fsync != 0, if you even want it to fsync. */ +void cl_log_do_fflush(int do_fsync); +void cl_log_use_buffered_io(int truefalse); +/* We now keep the file handles open for a potentially very long time. + * Sometimes we may need to close them explicitly. */ +void cl_log_close_log_files(void); + +#endif diff --git a/include/clplumbing/cl_misc.h b/include/clplumbing/cl_misc.h new file mode 100644 index 0000000..6f698b5 --- /dev/null +++ b/include/clplumbing/cl_misc.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_CL_MISC_H +#define _CLPLUMBING_CL_MISC_H +int cl_str_to_boolean(const char*, int*); + +int cl_file_exists(const char* filename); + +char* cl_get_env(const char* env_name); + +int cl_binary_to_int(const char* data, int len); + +long cl_get_msec(const char * input); /* string to msec */ + +#endif diff --git a/include/clplumbing/cl_pidfile.h b/include/clplumbing/cl_pidfile.h new file mode 100644 index 0000000..3dba50f --- /dev/null +++ b/include/clplumbing/cl_pidfile.h @@ -0,0 +1,25 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LOCKFILE_H_ +#define _LOCKFILE_H_ + +int cl_read_pidfile(const char *filename); +int cl_read_pidfile_no_checking(const char *filename); +int cl_lock_pidfile(const char *filename); +int cl_unlock_pidfile(const char *filename); + +#endif diff --git a/include/clplumbing/cl_plugin.h b/include/clplumbing/cl_plugin.h new file mode 100644 index 0000000..e2431bf --- /dev/null +++ b/include/clplumbing/cl_plugin.h @@ -0,0 +1,29 @@ + + +/* + * cl_manage_plugin.c: This file handle plugin loading and deleting + * + * Copyright (C) 2005 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __CL_PLUGIN__ +#define __CL_PLUGIN__ + +int cl_remove_plugin(const char* type, const char* pluginname); +void* cl_load_plugin(const char* type, const char* pluginname); + +#endif diff --git a/include/clplumbing/cl_poll.h b/include/clplumbing/cl_poll.h new file mode 100644 index 0000000..1b1908f --- /dev/null +++ b/include/clplumbing/cl_poll.h @@ -0,0 +1,46 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef CLPLUMBING_CL_POLL_H +# define CLPLUMBING_CL_POLL_H + +#include +#include + +/* + * Poll the file descriptors described by the NFDS structures starting at + * FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for + * an event to occur; if TIMEOUT is -1, block until an event occurs. + * Returns the number of file descriptors with events, zero if timed out, + * or -1 for errors. + * + * When available, this function uses POSIX signals, and Linux F_SETSIG() + * calls to provide this capability. When it is not available it + * uses the real poll() call. + * + */ +int cl_poll(struct pollfd *fds, unsigned int nfds, int timeout_ms); + +/* + * Call cl_poll_ignore() when you close a file descriptor you monitored + * via cl_poll() before, or if you don't want it monitored any more. + */ +int cl_poll_ignore(int fd); + +/* Select the signal you want us to use (must be a RT signal) */ +int cl_poll_setsig(int nsig); + +int cl_glibpoll(GPollFD* ufds, guint nfsd, gint timeout); +#endif diff --git a/include/clplumbing/cl_quorum.h b/include/clplumbing/cl_quorum.h new file mode 100644 index 0000000..b7798ba --- /dev/null +++ b/include/clplumbing/cl_quorum.h @@ -0,0 +1,44 @@ +/* + * quorum.h: head file for quorum module + * + * Copyright (C) 2005 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _QUORUM_H_ +#define _QUORUM_H_ + +#define HB_QUORUM_TYPE quorum +#define HB_QUORUM_TYPE_S "quorum" + +#define QUORUM_YES 0 +#define QUORUM_NO 1 +#define QUORUM_TIE 2 +typedef void(*callback_t)(void); +/* + * List of functions provided by implementations of the quorum interface. + */ +struct hb_quorum_fns { + + int (*getquorum) (const char* cluster + , int member_count, int member_quorum_votes + , int total_node_count, int total_quorum_votes); + int (*init) (callback_t notify, const char* cluster, const char* quorum_server); + void (*stop) (void); +}; + + +#endif diff --git a/include/clplumbing/cl_quorumd.h b/include/clplumbing/cl_quorumd.h new file mode 100644 index 0000000..6d282b3 --- /dev/null +++ b/include/clplumbing/cl_quorumd.h @@ -0,0 +1,48 @@ +/* + * quorum.h: head file for quorum module + * + * Author: Huang Zhen + * Copyright (C) 2006 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _QUORUMD_H_ +#define _QUORUMD_H_ + +#define HB_QUORUMD_TYPE quorumd +#define HB_QUORUMD_TYPE_S "quorumd" + +#define CONFIGFILE HA_HBCONF_DIR"/quorumd.conf" +#define MAX_DN_LEN 256 +#define quorum_log(priority, fmt...); \ + cl_log(priority, fmt); \ + +#define quorum_debug(priority, fmt...); \ + if ( debug_level > 0 ) { \ + cl_log(priority, fmt); \ + } + +/* List of functions provided by implementations of the quorumd interface. */ +struct hb_quorumd_fns { + int (*test) (void); + int (*init) (void); + int (*load_config_file) (void); + int (*dump_data) (int priority); + int (*on_connect) (int sock, gnutls_session session, const char* CN); +}; + + +#endif diff --git a/include/clplumbing/cl_random.h b/include/clplumbing/cl_random.h new file mode 100644 index 0000000..d1e37ce --- /dev/null +++ b/include/clplumbing/cl_random.h @@ -0,0 +1,81 @@ + +/* + * Copyright (C) 2005 Guochun Shi + * Copyright (C) 2005 International Business Machines Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +/* Intended usage is srand(cl_randseed()). + * This returns on "as good as it gets" random number usually taken from + * /dev/urandom to have a nice seed for future random numbers generated by + * rand(). */ +unsigned int cl_randseed(void); + +/* get_next_random() currently rand() based. + * + * You probably want to use cl_rand_from_interval instead. + * + * You don't need to srand(), it will seed once with cl_randseed internally. + * + * It is called that way, because it was exposed in the header file for a long + * time, and used to be coded in an attempt to pregenerate a queue of random + * numbers from the mainloop, and it would shift the next random number from + * that queue and trigger generation of new random numbers "at idle time" to + * refill that queue. + * Only that functionality never actually worked, is not interessting anymore + * anyways (rand() is cheap enough), and is now ripped out. + * + * So it now does srand(cl_randseed()) once internally, + * and from there on is equivalent to calling rand() directly, + * and could be called cl_rand(). + * + * If you want your own specific rand seed to re-generate a particular + * sequence, call it once, throw away the return code, then call + * srand(yourseed). Or don't use it anywhere in your code. */ +int get_next_random(void); + +/* generate some random number in the range [a;b]; + * typically used to randomly delay messages. */ +#define HAVE_CL_RAND_FROM_INTERVAL 1 +static inline +int cl_rand_from_interval(const int a, const int b) +{ + /* + * Be careful here, you don't know RAND_MAX at coding time, + * only at compile time. If you think + * (int)(a + (rand()*(b-a)+(RAND_MAX/2))/RAND_MAX); + * was correct, think again with RAND_MAX = INT_MAX, + * which is the case for many rand() implementations nowadays. + * + * Don't do modulo, either, as that will skew the distribution, and + * still has possible wraparounds, or an insufficient input set for too + * small RAND_MAX. + * + * Rather do the whole calculation in 64 bit, which should be correct + * as long as r, a, b, and RAND_MAX are all int. + * Of course, if you prefer, you can do it with floating point as well. + */ +#if 1 /* use long long */ + long long r = get_next_random(); + r = a + (r * (b-a) + RAND_MAX/2)/RAND_MAX; +#else /* use floating point */ + int r = get_next_random(); + r = a + (int)(1.0 / RAND_MAX * r * (b-a) + 0.5); +#endif + return r; +} diff --git a/include/clplumbing/cl_reboot.h b/include/clplumbing/cl_reboot.h new file mode 100644 index 0000000..1c759c8 --- /dev/null +++ b/include/clplumbing/cl_reboot.h @@ -0,0 +1,6 @@ +#ifndef CLPLUMBING_CL_REBOOT_H +#define CLPLUMBING_CL_REBOOT_H 1 +#include +void cl_enable_coredump_before_reboot(gboolean yesno); /* not implemented in all OSes */ +void cl_reboot(int msdelaybeforereboot, const char * reason); +#endif diff --git a/include/clplumbing/cl_signal.h b/include/clplumbing/cl_signal.h new file mode 100644 index 0000000..1a13a6b --- /dev/null +++ b/include/clplumbing/cl_signal.h @@ -0,0 +1,91 @@ +/* + * cl_signal.h: signal handling routines to be used by Linux-HA programmes + * + * Copyright (C) 2002 Horms + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _CL_SIGNAL_H +#define _CL_SIGNAL_H + +#include +#include +#include + +typedef struct { + int sig; + void (*handler)(int); + int interrupt; +} cl_signal_mode_t; + +#define CL_SIGNAL(_sig, _handler) \ + cl_signal_set_simple_handler((_sig), (_handler), NULL) +#if HAVE_SIGIGNORE +#define CL_IGNORE_SIG(_sig) sigignore((_sig)) +#else +#define CL_IGNORE_SIG(_sig) CL_SIGNAL((_sig), SIG_IGN) +#endif +#define CL_DEFAULT_SIG(_sig) CL_SIGNAL((_sig), SIG_DFL) + +#define CL_SIGINTERRUPT(_sig, _flag) siginterrupt((_sig), (_flag)) + +#define CL_SIGACTION(_signum, _act, _oldact) \ + sigaction((_signum), (_act), (_oldact)) +#define CL_SIGPROCMASK(_how, _set, _oldset) \ + cl_signal_block_set((_how), (_set), (_oldset)) +#define CL_SIGPENDING(_set) sigpending(_set) +#define CL_SIGSUSPEND(_mask) sigsuspend(_mask) + +#define CL_SIGEMPTYSET(_set) sigemptyset(_set) +#define CL_SIGFILLSET(_set) sigfillset(_set) +#define CL_SIGADDSET(_set, _signum) sigaddset((_set), (_signum)) +#define CL_SIGDELSET(_set, _signum) sigdelset((_set), (_signum)) +#define CL_SIGISMEMBER(_set, _signum) sigmember((_set), (_signum)) + +#define CL_KILL(_pid, _sig) kill((_pid), (_sig)) + +#define CL_PID_EXISTS(_pid) ( CL_KILL((_pid), 0) >= 0 || errno != ESRCH ) + +int +cl_signal_set_handler(int sig, void (*handler)(int), sigset_t *mask +, int flags, struct sigaction *oldact); + +int +cl_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact); + +int +cl_signal_set_action(int sig, void (*action)(int, siginfo_t *, void *) +, sigset_t *mask, int flags, struct sigaction *oldact); + +int +cl_signal_set_simple_action(int sig, void (*action)(int, siginfo_t *, void *) +, struct sigaction *oldact); + +int +cl_signal_set_interrupt(int sig, int flag); + +int +cl_signal_block(int how, int signal, sigset_t *oldset); + +int +cl_signal_block_set(int how, const sigset_t *set, sigset_t *oldset); + +int +cl_signal_set_handler_mode(const cl_signal_mode_t *mode, sigset_t *set); + + +#endif /* _CL_SIGNAL_H */ diff --git a/include/clplumbing/cl_syslog.h b/include/clplumbing/cl_syslog.h new file mode 100644 index 0000000..a7c1bfa --- /dev/null +++ b/include/clplumbing/cl_syslog.h @@ -0,0 +1,32 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Functions to support syslog. + * David Lee (c) 2005 + */ + +#ifndef _CLPLUMBING_CL_SYSLOG_H +#define _CLPLUMBING_CL_SYSLOG_H + +/* Convert string "auth" to equivalent number "LOG_AUTH" etc. */ +int cl_syslogfac_str2int(const char *); + +/* Convert number "LOG_AUTH" to equivalent string "auth" etc. */ +/* Returns static string; caller must NOT free. */ +const char *cl_syslogfac_int2str(int); + +#endif /* _CLPLUMBING_CL_SYSLOG_H */ diff --git a/include/clplumbing/cl_tiebreaker.h b/include/clplumbing/cl_tiebreaker.h new file mode 100644 index 0000000..11c10c4 --- /dev/null +++ b/include/clplumbing/cl_tiebreaker.h @@ -0,0 +1,35 @@ +/* + * cl_tiebreaker.h: head file for tiebreaker module + * + * Copyright (C) 2005 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CL_TIEBREAKER_H_ +#define _CL_TIEBREAKER_H_ + +#define HB_TIEBREAKER_TYPE tiebreaker +#define HB_TIEBREAKER_TYPE_S "tiebreaker" + +/* + * List of functions provided by implementations of tiebreaker interface. + */ +struct hb_tiebreaker_fns { + gboolean (*break_tie) (int, int); +}; + + +#endif diff --git a/include/clplumbing/cl_uuid.h b/include/clplumbing/cl_uuid.h new file mode 100644 index 0000000..12542cd --- /dev/null +++ b/include/clplumbing/cl_uuid.h @@ -0,0 +1,40 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CL_UUID_H_ +#define _CL_UUID_H_ +#include + +typedef struct cl_uuid_s{ + unsigned char uuid[16]; +}cl_uuid_t; + +void cl_uuid_copy(cl_uuid_t* dst, cl_uuid_t* src); +void cl_uuid_clear(cl_uuid_t* uu); +int cl_uuid_compare(const cl_uuid_t* uu1, const cl_uuid_t* uu2); +void cl_uuid_generate(cl_uuid_t* out); +int cl_uuid_is_null(cl_uuid_t* uu); +int cl_uuid_parse( char *in, cl_uuid_t* uu); +#define UU_UNPARSE_SIZEOF 37 /* Including NULL byte */ +void cl_uuid_unparse(const cl_uuid_t* uu, char *out); + +/* Suitable for ues as a GHashFunc from glib */ +guint cl_uuid_g_hash(gconstpointer uuid_ptr); +/* Suitable for ues as a GEqualFunc from glib */ +gboolean cl_uuid_g_equal(gconstpointer uuid_ptr_a, gconstpointer uuid_ptr_b); + + +#endif diff --git a/include/clplumbing/coredumps.h b/include/clplumbing/coredumps.h new file mode 100644 index 0000000..4d5ce79 --- /dev/null +++ b/include/clplumbing/coredumps.h @@ -0,0 +1,36 @@ +/* + * Basic Core dump control functions. + * + * Copyright (C) 2004 IBM Corporation + * + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _CLPLUMBING_COREFILES_H +# define _CLPLUMBING_COREFILES_H 1 + /* Set the root directory of our core directory hierarchy */ +int cl_set_corerootdir(const char * dir); + /* Change directory to the directory our core file needs to go in */ + /* Call after you establish the userid you're running under */ +int cl_cdtocoredir(void); + /* Enable/disable core dumps for ourselves and our child processes */ +int cl_enable_coredumps(int truefalse); +void cl_untaint_coredumps(void); +void cl_set_coredump_signal_handler(int nsig); +void cl_set_all_coredump_signal_handlers(void); + +#endif diff --git a/include/clplumbing/cpulimits.h b/include/clplumbing/cpulimits.h new file mode 100644 index 0000000..f7dd875 --- /dev/null +++ b/include/clplumbing/cpulimits.h @@ -0,0 +1,66 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Functions to put limits on CPU consumption. + * This allows us to better catch runaway realtime processes that + * might otherwise hang the whole system. + * + * The process is basically this: + * - Set the CPU percentage limit with cl_cpu_limit_setpercent() + * according to what you expect the CPU percentage to top out at + * measured over an interval at >= 10 seconds + * - Call cl_cpu_limit_ms_interval() to figure out how often to update + * the CPU limit (it returns milliseconds) + * - At least as often as indicated above, call cl_cpu_limit_update() + * to update our current CPU limit. + * + * These limits are approximate, so be a little conservative. + * If you've gone into an infinite loop, it'll likely get caught ;-) + * + * Note that exceeding the soft CPU limits we set here will cause a + * SIGXCPU signal to be sent. + * + * The default action for this signal is to cause a core dump. + * This is a good choice ;-) + * + * As of this writing, this code will never set the soft CPU limit less + * than two seconds, or greater than 10 seconds. + * + * It will currrently return a limit update interval between 10000 and + * 400000 milliseconds. + * + */ + +/* + * Set expected CPU percentage upper bound + */ +int cl_cpu_limit_setpercent(int ipercent); + +/* + * Update the current CPU limit + */ +int cl_cpu_limit_update(void); + +/* + * How often should we call cl_cpu_limit_update()? + * + * Note: return result is in milliseconds + */ +int cl_cpu_limit_ms_interval(void); + +/* Disable further CPU limits... */ +int cl_cpu_limit_disable(void); diff --git a/include/clplumbing/ipc.h b/include/clplumbing/ipc.h new file mode 100644 index 0000000..4a5e151 --- /dev/null +++ b/include/clplumbing/ipc.h @@ -0,0 +1,788 @@ +/* + * ipc.h IPC abstraction data structures. + * + * author Xiaoxiang Liu , + * Alan Robertson + * + * + * Copyright (c) 2002 International Business Machines + * Copyright (c) 2002 Xiaoxiang Liu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _IPC_H_ +#define _IPC_H_ +#include +#undef MIN +#undef MAX +#include +#include + +#ifdef IPC_TIME_DEBUG +#include +#define MAXIPCTIME 3000 + +#endif + +/* constants */ +#define DEFAULT_MAX_QLEN 64 +#define MAX_MSGPAD 128 +/* channel and connection status */ +#define IPC_CONNECT 1 /* Connected: can read, write */ +#define IPC_WAIT 2 /* Waiting for connection */ +#define IPC_DISCONNECT 3 /* Disconnected, can't read or write*/ +#define IPC_DISC_PENDING 4 /* Disconnected, can't write but */ + /* may be more data to read */ + +#define MAXFAILREASON 128 + +#define IPC_SERVER 1 +#define IPC_CLIENT 2 +#define IPC_PEER 3 + +#define IPC_ISRCONN(ch) ((ch)->ch_status == IPC_CONNECT \ + || (ch)->ch_status == IPC_DISC_PENDING) + +#define IPC_ISWCONN(ch) ((ch)->ch_status == IPC_CONNECT) + +/* general return values */ +#define IPC_OK 0 +#define IPC_FAIL 1 +#define IPC_BROKEN 2 +#define IPC_INTR 3 +#define IPC_TIMEOUT 4 + +/* + * IPC: Sockets-like Interprocess Communication Abstraction + * + * We have two fundamental abstractions which we maintain. + * Everything else is in support of these two abstractions. + * + * These two main abstractions are: + * + * IPC_WaitConnection: + * A server-side abstraction for waiting for someone to connect. + * + * IPC_Channel: + * An abstraction for an active communications channel. + * + * All the operations on these two abstractions are carried out + * via function tables (channel->ops). Below we refer to the + * function pointers in these tables as member functions. + * + * On the server side, everything starts up with a call to + * ipc_wait_conn_constructor(), which returns an IPC_WaitConnection. + * + * Once the server has the IPC_WaitConnection object in hand, + * it can give the result of the get_select_fd() member function + * to poll or select to inform you when someone tries to connect. + * + * Once select tells you someone is trying to connect, you then + * use the accept_connection() member function to accept + * the connection. accept_connection() returns an IPC_Channel. + * + * With that, the server can talk to the client, and away they + * go ;-) + * + * On the client side, everything starts up with a call to + * ipc_channel_constructor() which we use to talk to the server. + * The client is much easier ;-) + */ + + +typedef struct IPC_WAIT_CONNECTION IPC_WaitConnection; +typedef struct IPC_CHANNEL IPC_Channel; + +typedef struct IPC_MESSAGE IPC_Message; +typedef struct IPC_QUEUE IPC_Queue; +typedef struct IPC_AUTH IPC_Auth; + +typedef struct IPC_OPS IPC_Ops; +typedef struct IPC_WAIT_OPS IPC_WaitOps; + + + +/* wait connection structure. */ +struct IPC_WAIT_CONNECTION{ + int ch_status; /* wait conn. status.*/ + void * ch_private; /* wait conn. private data. */ + IPC_WaitOps *ops; /* wait conn. function table .*/ +}; + + +typedef void(*flow_callback_t)(IPC_Channel*, void*); + +/* channel structure.*/ +struct IPC_CHANNEL{ + int ch_status; /* identify the status of channel.*/ + int refcount; /* reference count */ + pid_t farside_pid; /* far side pid */ + void* ch_private; /* channel private data. */ + /* (may contain conn. info.) */ + IPC_Ops* ops; /* IPC_Channel function table.*/ + + /* number of bytes needed + * at the begginging of ->msg_body + * it's for msg head needed to tranmit in wire + */ + unsigned int msgpad; + + /* the number of bytes remainng to send for the first message in send queue + 0 means nothing has been sent thus all bytes needs to be send + n != 0 means there are still n bytes needs to be sent + */ + unsigned int bytes_remaining; + + + /* is the send blocking or nonblocking*/ + gboolean should_send_block; + + /* if send would block, should an error be returned or not */ + gboolean should_block_fail; + +/* There are two queues in channel. One is for sending and the other + * is for receiving. + * Those two queues are channel's internal queues. They should not be + * accessed directly. + */ + /* private: */ + IPC_Queue* send_queue; + IPC_Queue* recv_queue; + + /* buffer pool for receive in this channel*/ + struct ipc_bufpool* pool; + + /* the follwing is for send flow control*/ + int high_flow_mark; + int low_flow_mark; + void* high_flow_userdata; + void* low_flow_userdata; + flow_callback_t high_flow_callback; + flow_callback_t low_flow_callback; + + int conntype; + + char failreason[MAXFAILREASON]; + + /* New members to support Multi-level ACLs for the CIB, + * available since libplumb.so.2.1.0, added at the + * end of the struct to maintain backwards ABI compatibility. + * + * If you don't like to care for library versions, + * create your IPC channels with + * c = ipc_wait_conn_constructor(IPC_UDS_CRED, ...), + * and these members will be available. + */ + uid_t farside_uid; /* far side uid */ + gid_t farside_gid; /* far side gid */ +}; + +struct IPC_QUEUE{ + size_t current_qlen; /* Current qlen */ + size_t max_qlen; /* Max allowed qlen */ + GList* queue; /* List of messages */ + /* keep the time of the last max queue warning */ + time_t last_maxqlen_warn; + /* and the number of messages lost */ + unsigned maxqlen_cnt; +}; + +/* authentication information : set of gids and uids */ +struct IPC_AUTH { + GHashTable * uid; /* hash table for user id */ + GHashTable * gid; /* hash table for group id */ +}; + + +/* Message structure. */ +struct IPC_MESSAGE{ + size_t msg_len; + void* msg_buf; + void* msg_body; +/* + * IPC_MESSAGE::msg_done + * the callback function pointer which can be called after this + * message is sent, received or otherwise processed. + * + * Parameter: + * msg: the back pointer to the message which contains this + * function pointer. + * + */ + void (* msg_done)(IPC_Message * msg); + void* msg_private; /* the message private data. */ + /* Belongs to message creator */ + /* May be used by callback function. */ + IPC_Channel * msg_ch; /* Channel the */ + /* message is from/in */ + +}; + +struct IPC_WAIT_OPS{ +/* + * IPC_WAIT_OPS::destroy + * destroy the wait connection and free the memory space used by + * this wait connection. + * + * Parameters: + * wait_conn (IN): the pointer to the wait connection. + * + */ + void (* destroy)(IPC_WaitConnection *wait_conn); +/* + * IPC_WAIT_OPS::get_select_fd + * provide a fd which user can listen on for a new coming connection. + * + * Parameters: + * wait_conn (IN) : the pointer to the wait connection which + * we're supposed to return the file descriptor for + * (the file descriptor can be used with poll too ;-)) + * + * Return values: + * integer >= 0 : the select_fd. + * -1 : can't get the select fd. + * + */ + int (* get_select_fd)(IPC_WaitConnection *wait_conn); +/* + * IPC_WAIT_OPS::accept_connection + * accept and create a new connection and verify the authentication. + * + * Parameters: + * wait_conn (IN) : the waiting connection which will accept + * create the new connection. + * auth_info (IN) : the authentication information which will be + * verified for the new connection. + * + * Return values: + * the pointer to the new IPC channel; NULL if the creation or + * authentication fails. + * + */ + IPC_Channel * (* accept_connection) + (IPC_WaitConnection * wait_conn, IPC_Auth *auth_info); +}; + +/* Standard IPC channel operations */ + +struct IPC_OPS{ +/* + * IPC_OPS::destroy + * brief destroy the channel object. + * + * Parameters: + * ch (IN) : the pointer to the channel which will be destroyed. + * + */ + void (*destroy) (IPC_Channel * ch); +/* + * IPC_OPS::initiate_connection + * used by service user side to set up a connection. + * + * Parameters: + * ch (IN) : the pointer to channel used to initiate the connection. + * + * Return values: + * IPC_OK : the channel set up the connection successfully. + * IPC_FAIL : the connection initiation fails. + * + */ + int (* initiate_connection) (IPC_Channel * ch); +/* + * IPC_OPS::verify_auth + * used by either side to verify the identity of peer on connection. + * + * Parameters + * ch (IN) : the pointer to the channel. + * + * Return values: + * IPC_OK : the peer is trust. + * IPC_FAIL : verifying authentication fails. + */ + int (* verify_auth) (IPC_Channel * ch, IPC_Auth* info); +/* + * IPC_OPS::assert_auth + * service user asserts to be certain qualified service user. + * + * Parameters: + * ch (IN): the active channel. + * auth (IN): the hash table which contains the asserting information. + * + * Return values: + * IPC_OK : assert the authentication successfully. + * IPC_FAIL : assertion fails. + * + * NOTE: This operation is a bit obscure. It isn't needed with + * UNIX domain sockets at all. The intent is that some kinds + * of IPC (like FIFOs), do not have an intrinsic method to + * authenticate themselves except through file permissions. + * The idea is that you must tell it how to chown/grp your + * FIFO so that the other side and see that if you can write + * this, you can ONLY be the user/group they expect you to be. + * But, I think the parameters may be wrong for this ;-) + */ + int (* assert_auth) (IPC_Channel * ch, GHashTable * auth); +/* + * IPC_OPS::send + * send the message through the sending connection. + * + * Parameters: + * ch (IN) : the channel which contains the connection. + * msg (IN) : pointer to the sending message. User must + * allocate the message space. + * + * Return values: + * IPC_OK : the message was either sent out successfully or + * appended to the send_queue. + * IPC_FAIL : the send operation failed. + * IPC_BROKEN : the channel is broken. + * +*/ + int (* send) (IPC_Channel * ch, IPC_Message* msg); + +/* + * IPC_OPS::recv + * receive the message through receving queue. + * + * Parameters: + * ch (IN) : the channel which contains the connection. + * msg (OUT): the IPC_MESSAGE** pointer which contains the pointer + * to the received message or NULL if there is no + * message available. + * + * Return values: + * IPC_OK : receive operation is completed successfully. + * IPC_FAIL : operation failed. + * IPC_BROKEN : the channel is broken (disconnected) + * + * Note: + * return value IPC_OK doesn't mean the message is already + * sent out to (or received by) the peer. It may be pending + * in the send_queue. In order to make sure the message is no + * longer needed, please specify the msg_done function in the + * message structure and once this function is called, the + * message is no longer needed. + * + * is_sending_blocked() is another way to check if there is a message + * pending in the send_queue. + * + */ + int (* recv) (IPC_Channel * ch, IPC_Message** msg); + +/* + * IPC_OPS::waitin + * Wait for input to become available + * + * Parameters: + * ch (IN) : the channel which contains the connection. + * + * Side effects: + * If output becomes unblocked while waiting, it will automatically + * be resumed without comment. + * + * Return values: + * IPC_OK : a message is pending or output has become unblocked. + * IPC_FAIL : operation failed. + * IPC_BROKEN : the channel is broken (disconnected) + * IPC_INTR : waiting was interrupted by a signal + */ + int (* waitin) (IPC_Channel * ch); +/* + * IPC_OPS::waitout + * Wait for output to finish + * + * Parameters: + * ch (IN) : the channel which contains the connection. + * + * Side effects: + * If input becomes available while waiting, it will automatically + * be read into the channel queue without comment. + * + * Return values: + * IPC_OK : output no longer blocked + * IPC_FAIL : operation failed. + * IPC_BROKEN : the channel is broken (disconnected) + * IPC_INTR : waiting was interrupted by a signal + */ + int (* waitout) (IPC_Channel * ch); + +/* + * IPC_OPS::is_message_pending + * check to see if there is any messages ready to read, or hangup has + * occurred. + * + * Parameters: + * ch (IN) : the pointer to the channel. + * + * Return values: + * TRUE : there are messages ready to read, or hangup. + * FALSE: there are no messages ready to be read. + */ + gboolean (* is_message_pending) (IPC_Channel * ch); + +/* + * IPC_OPS::is_sending_blocked + * check the send_queue to see if there are any messages blocked. + * + * Parameters: + * ch (IN) : the pointer to the channel. + * + * Return values: + * TRUE : there are messages blocked (waiting) in the send_queue. + * FALSE: there are no message blocked (waiting) in the send_queue. + * + * See also: + * get_send_select_fd() + */ + gboolean (* is_sending_blocked) (IPC_Channel *ch); + +/* + * IPC_OPS::resume_io + * Resume all possible IO operations through the IPC transport + * + * Parameters: + * the pointer to the channel. + * + * Return values: + * IPC_OK : resume all the possible I/O operation successfully. + * IPC_FAIL : the operation fails. + * IPC_BROKEN : the channel is broken. + * + */ + int (* resume_io) (IPC_Channel *ch); +/* + * IPC_OPS::get_send_select_fd() + * return a file descriptor which can be given to select/poll. This fd + * is used by the IPC code for sending. It is intended that this be + * ONLY used with select, poll, or similar mechanisms, not for direct I/O. + * Note that due to select(2) and poll(2) semantics, you must check + * is_sending_blocked() to see whether you should include this FD in + * your poll for writability, or you will loop very fast in your + * select/poll loop ;-) + * + * Parameters: + * ch (IN) : the pointer to the channel. + * + * Return values: + * integer >= 0 : the send fd for selection. + * -1 : there is no send fd. + * + * See also: + * is_sending_blocked() + */ + int (* get_send_select_fd) (IPC_Channel * ch); +/* + * IPC_OPS::get_recv_select_fd + * return a file descriptor which can be given to select. This fd + * is for receiving. It is intended that this be ONLY used with select, + * poll, or similar mechanisms, NOT for direct I/O. + * + * Parameters: + * ch (IN) : the pointer to the channel. + * + * Return values: + * integer >= 0 : the recv fd for selection. + * -1 : there is no recv fd. + * + * NOTE: This file descriptor is often the same as the send + * file descriptor. + */ + int (* get_recv_select_fd) (IPC_Channel * ch); +/* + * IPC_OPS::set_send_qlen + * allow user to set the maximum send_queue length. + * + * Parameters + * ch (IN) : the pointer to the channel. + * q_len (IN) : the max length for the send_queue. + * + * Return values: + * IPC_OK : set the send queue length successfully. + * IPC_FAIL : there is no send queue. (This isn't supposed + * to happen). + * It means something bad happened. + * + */ + int (* set_send_qlen) (IPC_Channel * ch, int q_len); +/* + * IPC_OPS::set_recv_qlen + * allow user to set the maximum recv_queue length. + * + * Parameters: + * ch (IN) : the pointer to the channel. + * q_len (IN) : the max length for the recv_queue. + * + * Return values: + * IPC_OK : set the recv queue length successfully. + * IPC_FAIL : there is no recv queue. + * + */ + int (* set_recv_qlen) (IPC_Channel * ch, int q_len); + + +/* + * IPC_OPS: set callback for high/low flow mark + * ch (IN) : the pointer to the channel + * callback (IN) : the callback function + * user_data(IN) : a pointer to user_data + * callback will be called with channel and + * this user_data as parameters + * + * Return values: + * void + * + */ + + + void (* set_high_flow_callback) (IPC_Channel* ch , + flow_callback_t callback, + void* user_data); + + void (* set_low_flow_callback) (IPC_Channel* ch , + flow_callback_t callback, + void* user_data); + +/* + * IPC_OPS::new_ipcmsg + * ch (IN) : the pointer to the channel + * data (IN) : data to be copied to the message body + * len (IN) : data len + * private (IN): the pointer value to set as in the message + * + * Return values: + * the pointer to a new created message will be + * returned if success or NULL if failure + * + */ + + IPC_Message* (*new_ipcmsg)(IPC_Channel* ch, const void* data, + int len, void* private); + + +/* + * IPC_OPS::nget_chan_status + * ch (IN) : the pointer to the channel + * + * Return value: + * channel status. + * + */ + int (*get_chan_status)(IPC_Channel* ch); + + +/* + * These two functions returns true if the corresponding queue + * is full, otherwise it returns false + */ + + gboolean (*is_sendq_full)(struct IPC_CHANNEL * ch); + gboolean (*is_recvq_full)(struct IPC_CHANNEL * ch); + + + /* Get the connection type for the channel + * it can be IPC_SERVER, IPC_CLIENT, IPC_PEER + */ + + int (*get_conntype)(struct IPC_CHANNEL* ch); + + int (*disconnect)(struct IPC_CHANNEL* ch); + +}; + + +/* + * ipc_wait_conn_constructor: + * the common constructor for ipc waiting connection. + * Use ch_type to identify the connection type. Usually it's only + * needed by server side. + * + * Parameters: + * ch_type (IN) : the type of the waiting connection to create. + * ch_attrs (IN) : the hash table which contains the attributes + * needed by this waiting connection in name/value + * pair format. + * + * For example, the only attribute needed by UNIX + * domain sockets is path name. + * + * Return values: + * the pointer to a new waiting connection or NULL if the connection + * can't be created. + * Note: + * current implementation supports + * IPC_ANYTYPE: This is what program code should typically use. + * Internally it is an alias to IPC_UDS_CRED. + * IPC_UDS_CRED: Unix Domain Sockets, + * farside uid + gid credentials is available. + * Available since libplumb.so.2.1.0. + * IPC_DOMAIN_SOCKET: An other alias to Unix Domain Sockets; + * internally it is equivalent to both above. + * Using this explicitly, your code will work + * even with libplumb.so.2.0.0. + * Which also means that you MUST NOT use the + * farside_uid/gid functionality then. + */ +extern IPC_WaitConnection * ipc_wait_conn_constructor(const char * ch_type +, GHashTable* ch_attrs); + +/* + * ipc_channel_constructor: + * brief the common constructor for ipc channel. + * Use ch_type to identify the channel type. + * Usually this function is only called by client side. + * + * Parameters: + * ch_type (IN): the type of the channel you want to create. + * ch_attrs (IN): the hash table which contains the attributes needed + * by this channel. + * For example, the only attribute needed by UNIX domain + * socket is path name. + * + * Return values: + * the pointer to the new channel whose status is IPC_DISCONNECT + * or NULL if the channel can't be created. + * + * Note: + * See comments for ipc_wait_conn_constructor above + * for currently implemented ch_type channel types. + */ +extern IPC_Channel * ipc_channel_constructor(const char * ch_type +, GHashTable* ch_attrs); + +/* + * ipc_channel_pair: + * Construct a pair of connected IPC channels in a fashion analogous + * to pipe(2) or socketpair(2). + * + * Parameters: + * channels: an array of two IPC_Channel pointers for return result + */ +int ipc_channel_pair(IPC_Channel* channels[2]); + +/* + * ipc_set_auth: + * A helper function used to convert array of uid and gid into + * an authentication structure (IPC_Auth) + * + * Parameters: + * a_uid (IN): the array of a set of user ids. + * a_gid (IN): the array of a set of group ids. + * num_uid (IN): the number of user ids. + * num_gid (IN): the number of group ids. + * + * Return values: + * the pointer to the authentication structure which contains the + * set of uid and the set of gid. Or NULL if this structure can't + * be created. + * + */ + + +IPC_Auth* ipc_str_to_auth(const char * uidlist, int, const char * gidlist, int); + +extern IPC_Auth * ipc_set_auth(uid_t * a_uid, gid_t * a_gid +, int num_uid, int num_gid); + +/* Destroys an object constructed by ipc_set_auth or ipc_str_to_auth() */ +extern void ipc_destroy_auth(IPC_Auth * auth); + +extern void ipc_set_pollfunc(int (*)(struct pollfd*, unsigned int, int)); +extern void ipc_bufpool_dump_stats(void); + +#ifdef IPC_TIME_DEBUG + +enum MSGPOS_IN_IPC{ + MSGPOS_ENQUEUE, + MSGPOS_SEND, + MSGPOS_RECV, + MSGPOS_DEQUEUE +}; + +#endif + + +struct SOCKET_MSG_HEAD{ + int msg_len; + unsigned int magic; +#ifdef IPC_TIME_DEBUG + longclock_t enqueue_time; + longclock_t send_time; + longclock_t recv_time; + longclock_t dequeue_time; +#endif + +}; + + +/* MAXMSG is the maximum final message size on the wire. */ +#define MAXMSG (256*1024) +/* MAXUNCOMPRESSED is the maximum, raw data size prior to compression. */ +/* 1:8 compression ratio is to be expected on data such as xml */ +#define MAXUNCOMPRESSED (2048*1024) +#define HEADMAGIC 0xabcd +#define POOL_SIZE (4*1024) +struct ipc_bufpool{ + + int refcount; + char* currpos; + char* consumepos; + char* startpos; + char* endpos; + int size; +}; + +struct ipc_bufpool* ipc_bufpool_new(int); + +void ipc_bufpool_del(struct ipc_bufpool* pool); + +int ipc_bufpool_spaceleft(struct ipc_bufpool* pool); + +int ipc_bufpool_update(struct ipc_bufpool* pool, + struct IPC_CHANNEL * ch, + int msg_len, + IPC_Queue* rqueue); + +gboolean ipc_bufpool_full(struct ipc_bufpool* pool, + struct IPC_CHANNEL* ch, + int*); +int ipc_bufpool_partial_copy(struct ipc_bufpool* dstpool, + struct ipc_bufpool* srcpool); + +void ipc_bufpool_ref(struct ipc_bufpool* pool); + +void ipc_bufpool_unref(struct ipc_bufpool* pool); + +void set_ipc_time_debug_flag(gboolean flag); + +/* pathname attribute */ +#define IPC_PATH_ATTR "path" +/* socket mode attribute */ +#define IPC_MODE_ATTR "sockmode" +/* Unix domain socket, used by old code. + * See also the comment block above ipc_wait_conn_constructor() */ +#define IPC_DOMAIN_SOCKET "uds" +/* Unix domain socket with farside uid + gid credentials. + * Available since libplumb.so.2.1.0 */ +#define IPC_UDS_CRED "uds_c" + +#ifdef IPC_UDS_CRED +# define IPC_ANYTYPE IPC_UDS_CRED +#else +# error "No IPC types defined(!)" +#endif + +#endif diff --git a/include/clplumbing/loggingdaemon.h b/include/clplumbing/loggingdaemon.h new file mode 100644 index 0000000..ba986f5 --- /dev/null +++ b/include/clplumbing/loggingdaemon.h @@ -0,0 +1,32 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Messages sent to the logging daemon */ +#define LD_LOGIT 2 +#define MAXENTITY 64 + +/* Message contains following header, followed by the text (char[]) itself */ +struct LogDaemonMsgHdr_s { + int msgtype; + int facility; + int priority; + int msglen; + gboolean use_pri_str; + int entity_pid; + char entity[MAXENTITY]; + TIME_T timestamp; +}; +typedef struct LogDaemonMsgHdr_s LogDaemonMsgHdr; diff --git a/include/clplumbing/longclock.h b/include/clplumbing/longclock.h new file mode 100644 index 0000000..ae95b28 --- /dev/null +++ b/include/clplumbing/longclock.h @@ -0,0 +1,143 @@ +/* + * Longclock operations + * + * Copyright (c) 2002 International Business Machines + * Author: Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _LONGCLOCK_H +# define _LONGCLOCK_H +/* + * A longclock_t object is a lot like a clock_t object, except that it + * won't wrap in the lifetime of the earth. It is guaranteed to be at + * least 64 bits. This means it should go for around 2 billion years. + * + * It is also supposed to be proof against changes in the local time on + * the computer. This is easy if you have a properly-working times(2) + * for us to use. + * + * longclock_t's are definitely not comparable between computers, and in + * some implementations, not even between processes on the same computer. + * + * + * The functions provided here are: + * + * unsigned long cl_times(void); + * A rational wrapper for the times(2) call + * for those cases where only the return value + * is wanted. + * longclock_t time_longclock(void); + * Returns current time as a longclock_t. + * + * longclock_t msto_longclock(unsigned long); + * Converts quantity in milliseconds to longclock_t + * + * unsigned long longclockto_ms(longclock_t); + * Converts quantity in longclock_t to milliseconds + * NOTE: Can overflow! + * + * unsigned long longclockto_long(longclock_t); + * Converts quantity in longclock_t to clock_t + * NOTE: Can overflow! + * + * longclock_t secsto_longclock(unsigned long); + * Converts quantity in seconds to longclock_t + * + * longclock_t add_longclock(longclock_t l, longclock_t r); + * Adds two longclock_t values + * + * int cmp_longclock(longclock_t l, longclock_t r); + * Returns negative, zero or positive value + * + * longclock_t sub_longclock(longclock_t l, longclock_t r); + * Subtracts two longclock_t values + * NOTE: Undefined if l is < r + * + * longclock_t dsecsto_longclock(double); + * Converts quantity in seconds (as a double) + * to a longclock_t + * + * unsigned hz_longclock(void); + * Returns frequency of longclock_t clock. + * + * We provide this constant: + * + * extern const longclock_t zero_longclock; + */ +extern unsigned long cl_times(void); + +#ifdef CLOCK_T_IS_LONG_ENOUGH +# ifndef HAVE_LONGCLOCK_ARITHMETIC +# define HAVE_LONGCLOCK_ARITHMETIC +# endif + +# include + + typedef clock_t longclock_t; + +#else /* clock_t isn't at least 64 bits */ + typedef unsigned long long longclock_t; +#endif + +longclock_t time_longclock(void); + +extern const longclock_t zero_longclock; + +unsigned hz_longclock(void); +longclock_t secsto_longclock(unsigned long); +longclock_t dsecsto_longclock(double); +longclock_t msto_longclock(unsigned long); +unsigned long longclockto_ms(longclock_t); /* Can overflow! */ +long longclockto_long(longclock_t); /* May overflow! */ + + +#ifndef HAVE_LONGCLOCK_ARITHMETIC + +longclock_t add_longclock(longclock_t l, longclock_t r); + + /* Undefined if l is < r according to cmp_longclock() */ +longclock_t sub_longclock(longclock_t l, longclock_t r); + +int cmp_longclock(longclock_t l, longclock_t r); + + +#else /* We HAVE_LONGCLOCK_ARITHMETIC */ + +# define longclockto_long(lc) ((long)(lc)) + +# define add_longclock(l,r) \ + ((longclock_t)(l) + (longclock_t)(r)) + +# define sub_longclock(l,r) \ + ((longclock_t)(l) - (longclock_t)(r)) + +# define cmp_longclock(l,r) \ + (((longclock_t)(l) < (longclock_t)(r)) \ + ? -1 \ + : (((longclock_t)(l) > (longclock_t)(r)) \ + ? +1 : 0)) +#endif + + +/* N.B: Possibly not the best place for this, but it will do for now */ +/* This is consistent with OpenBSD, and is a good choice anyway */ +#define TIME_T unsigned long +#define TIME_F "%lu" +#define TIME_X "%lx" + +#endif diff --git a/include/clplumbing/lsb_exitcodes.h b/include/clplumbing/lsb_exitcodes.h new file mode 100644 index 0000000..e46b5be --- /dev/null +++ b/include/clplumbing/lsb_exitcodes.h @@ -0,0 +1,92 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* LSB status exit codes. + * + * All of these and the supporting text are taken from the LSB. + * + * If the status command is given, the init script will return + * the following exit status codes. + * + * 0 program is running or service is OK + * 1 program is dead and /var/run pid file exists + * 2 program is dead and /var/lock lock file exists + * 3 program is stopped + * 4 program or service status is unknown + * 5-99 reserved for future LSB use + * 100-149 reserved for distribution use + * 150-199 reserved for application use + * 200-254 reserved + */ + +#define LSB_STATUS_OK 0 +#define LSB_STATUS_VAR_PID 1 +#define LSB_STATUS_VAR_LOCK 2 +#define LSB_STATUS_STOPPED 3 +#define LSB_STATUS_UNKNOWN 4 +#define LSB_STATUS_LSBRESERVED 5 +#define LSB_STATUS_DISTRESERVED 100 +#define LSB_STATUS_APPRESERVED 150 +#define LSB_STATUS_RESERVED 200 +/* + * + * In the case of init script commands other than "status" + * (i.e., "start", "stop", "restart", "reload", and "force-reload"), + * the init script must return an exit status of zero if the action + * described by the argument has been successful. Otherwise, the + * exit status shall be non-zero, as defined below. In addition + * to straightforward success, the following situations are also + * to be considered successful: + * + * restarting a service (instead of reloading it) with the + * "force-reload" argument + * running "start" on a service already running + * running "stop" on a service already stopped or not running + * running "restart" on a service already stopped or not running + * In case of an error, while processing any init script action + * except for "status", the init script must print an error + * message and return one of the following non-zero exit + * status codes. + * + * 1 generic or unspecified error (current practice) + * 2 invalid or excess argument(s) + * 3 unimplemented feature (for example, "reload") + * 4 user had insufficient privilege + * 5 program is not installed + * 6 program is not configured + * 7 program is not running + * 8-99 reserved for future LSB use + * 100-149 reserved for distribution use + * 150-199 reserved for application use + * 200-254 reserved + * + * All error messages must be printed on standard error. + * All status messages must be printed on standard output. + * (This does not prevent scripts from calling the logging + * functions such as log_failure_msg). + */ +#define LSB_EXIT_OK 0 +#define LSB_EXIT_GENERIC 1 +#define LSB_EXIT_EINVAL 2 +#define LSB_EXIT_ENOTSUPPORTED 3 +#define LSB_EXIT_EPERM 4 +#define LSB_EXIT_NOTINSTALLED 5 +#define LSB_EXIT_NOTCONFIGED 6 +#define LSB_EXIT_NOTRUNNING 7 +#define LSB_EXIT_LSBRESERVED 8 +#define LSB_EXIT_DISTRESERVED 100 +#define LSB_EXIT_APPRESERVED 150 +#define LSB_EXIT_RESERVED 200 diff --git a/include/clplumbing/md5.h b/include/clplumbing/md5.h new file mode 100644 index 0000000..95b2c33 --- /dev/null +++ b/include/clplumbing/md5.h @@ -0,0 +1,49 @@ +/* + * md5.h: MD5 and keyed-MD5 algorithms + * + * Author: Sun Jiang Dong + * Copyright (c) 2005 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _MD5_H_ +#define _MD5_H__ + +/* + * MD5: The MD5 Message-Digest Algorithm ( RFC 1321 ) + * return value: 0 - success + * <0 - fail + * Note: The digest buffer should be not less than 16. + * + */ +int MD5( const unsigned char *data + , unsigned long data_len + , unsigned char * digest); + +/* + * HMAC: Keyed-Hashing for Message Authentication + * return value: 0 - success + * <0 - fail + * Note: The digest buffer should be not less than 16. + */ +int HMAC( const unsigned char * key + , unsigned int key_len + , const unsigned char * data + , unsigned long data_len + , unsigned char * digest); + +#endif diff --git a/include/clplumbing/mkstemp_mode.h b/include/clplumbing/mkstemp_mode.h new file mode 100644 index 0000000..ff5f893 --- /dev/null +++ b/include/clplumbing/mkstemp_mode.h @@ -0,0 +1,34 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * A slightly safer version of mkstemp(3) + * + * In this version, the file is initially created mode 0, (using umask) and + * then chmod-ed to the requested permissions after calling mkstemp(3). + * This guarantees that the file is not even momentarily open beyond the + * requested permissions. + * + * Return values: + * + * Like mkstemp, it returns the file descriptor of the open file, or -1 + * on error. + * + * In addition to the errno values documented for mkstemp(3), this functio + * can also fail with any of the errno values documented for chmod(2). + * + */ +int mkstemp_mode(char* template, mode_t requested_filemode); diff --git a/include/clplumbing/netstring.h b/include/clplumbing/netstring.h new file mode 100644 index 0000000..ef24e8f --- /dev/null +++ b/include/clplumbing/netstring.h @@ -0,0 +1,48 @@ +/* + * Intracluster message object (struct ha_msg) + * + * Copyright (C) 1999, 2000 Guochun Shi + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef NET_STRING_H +#define NET_STRING_H +#include +#include +#include + +extern gboolean cl_msg_quiet_fmterr; + +/* Convert a message to netstring data */ +char* msg2netstring(const struct ha_msg*, size_t*); +char * msg2netstring_noauth(const struct ha_msg *m, size_t * slen); + +/* Convert netstring data to a message */ +struct ha_msg * netstring2msg(const char*, size_t, int); + +/* Is this netstring authentic? */ +int is_auth_netstring(const char* datap, size_t datalen, + const char* authstring, size_t authlen); + +void cl_set_authentication_computation_method(int (*method)(int authmethod +, const void * data +, size_t datalen +, char * authstr +, size_t authlen)); + +#endif diff --git a/include/clplumbing/proctrack.h b/include/clplumbing/proctrack.h new file mode 100644 index 0000000..975ff1b --- /dev/null +++ b/include/clplumbing/proctrack.h @@ -0,0 +1,120 @@ +/* + * Process tracking object. + * + * Copyright (c) 2002 International Business Machines + * Author: Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PROCTRACK_H +# define _PROCTRACK_H +#include +#include +#include + +/* + * We track processes, mainly so we can do something appropriate + * when they die, and find processes should we need to kill them... + */ + +typedef struct _ProcTrack ProcTrack; +typedef struct _ProcTrack_ops ProcTrack_ops; +typedef struct _ProcTrackKillInfo ProcTrackKillInfo; + +/* + * The levels of logging possible for our process + */ +enum _ProcTrackLogType { + PT_LOGNONE = 2, /* Exits never automatically logged */ + PT_LOGNORMAL, /* Automatically log abnormal exits */ + PT_LOGVERBOSE /* Automatically log every exit */ +}; +typedef enum _ProcTrackLogType ProcTrackLogType; + +#define proctrack_pid(p) (p)->pid +#define proctrack_data(p) (p)->privatedata +#define reset_proctrack_data(p) (p)->privatedata = NULL +#define proctrack_timedout(p) ((p)->timeoutseq > 0) + +struct _ProcTrack { + pid_t pid; + int isapgrp; + ProcTrackLogType loglevel; + void * privatedata; + ProcTrack_ops* ops; + + longclock_t startticks; + TIME_T starttime; + unsigned timerid; + int timeoutseq; + ProcTrackKillInfo* killinfo; +}; + +/* + * The set of operations to perform on our tracked processes. + */ +struct _ProcTrack_ops { + + /* Called when a process dies */ + void (*procdied) + (ProcTrack* p, int status, int signo, int exitcode + , int waslogged); + + /* Called when a process registers */ + void (*procregistered) + (ProcTrack*p); + + /* Returns a "name" for a process (for messages) */ + /* (may have to be copied, because it may be a static value) */ + const char * + (*proctype) + (ProcTrack* p); +}; + +struct _ProcTrackKillInfo { + long mstimeout; /* Timeout in milliseconds */ + int signalno; /* Signal number to issue @ timeout */ +}; + +/* A function for calling by the process table iterator */ +typedef void (*ProcTrackFun) (ProcTrack* p, void * data); + +/* Call this function to activate the procdied member function */ +/* Returns TRUE if 'pid' was registered */ +int ReportProcHasDied(int pid, int status); + +/* Create/Log a new tracked process */ +void NewTrackedProc(pid_t pid, int isapgrp, ProcTrackLogType loglevel +, void * privatedata , ProcTrack_ops* ops); + +/* "info" is 0-terminated (terminated by a 0 signal) */ +int SetTrackedProcTimeouts(pid_t pid, ProcTrackKillInfo* info); +void RemoveTrackedProcTimeouts(pid_t pid); + +/* Return information associated with the given PID (or NULL) */ +ProcTrack* GetProcInfo(pid_t pid); + +/* + * Iterate over the set of tracked processes. + * If proctype is NULL, then walk through them all, otherwise only those + * of the given type ("f") + */ +void ForEachProc(ProcTrack_ops* proctype, ProcTrackFun f, void * data); + +void DisableProcLogging(void); /* Useful for shutdowns */ +void EnableProcLogging(void); +#endif diff --git a/include/clplumbing/realtime.h b/include/clplumbing/realtime.h new file mode 100644 index 0000000..45eb76c --- /dev/null +++ b/include/clplumbing/realtime.h @@ -0,0 +1,65 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_REALTIME_H +# define _CLPLUMBING_REALTIME_H +# include + +#if defined(SCHED_RR) && defined(_POSIX_PRIORITY_SCHEDULING) && !defined(ON_DARWIN) +# define DEFAULT_REALTIME_POLICY SCHED_RR +#endif + +/* + * + * make_realtime() will make the current process a soft realtime process + * and lock it into memory after growing the heap by heapgrowK*1024 bytes + * + * If you set spolicy or priority to <= 0, then defaults will be used. + * Otherwise you need to use a value for spolicy from + * and use an appropriate priority for the given spolicy. + * + * WARNING: badly behaved programs which use the make_realtime() function + * can easily hang the machine. + */ + +void cl_make_realtime +( int spolicy, /* SCHED_RR or SCHED_FIFO (or SCHED_OTHER) */ + int priority, /* typically 1-99 */ + int stackgrowK, /* Amount to grow stack by */ + int heapgrowK /* Amount to grow heap by */ +); + +void cl_make_normaltime(void); + +/* Cause calls to make_realtime() to be ignored */ +void cl_disable_realtime(void); + +/* Cause calls to make_realtime() to be accepted. + * This is the default behaviour */ +void cl_enable_realtime(void); + +/* Sleep a really short (the shortest) time */ +int cl_shortsleep(void); + +/* Print messages if we've done (more) non-realtime mallocs */ +void cl_realtime_malloc_check(void); + +/* Number of times we "go to the well" for memory after becoming realtime */ +int cl_nonrealtime_malloc_count(void); +/* Number of bytes we "got from the well" for memory after becoming realtime */ +unsigned long cl_nonrealtime_malloc_size(void); + +#endif diff --git a/include/clplumbing/replytrack.h b/include/clplumbing/replytrack.h new file mode 100644 index 0000000..f98fe48 --- /dev/null +++ b/include/clplumbing/replytrack.h @@ -0,0 +1,208 @@ +/* + * Process tracking object. + * + * Copyright (c) 2007 Alan Robertson + * Author: Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _REPLYTRACK_H +# define _REPLYTRACK_H +#include +#include +#include +#include + +/* + * We track replies - so we can tell when all expected replies were received. + * + * There is a problem in clusters where a message is sent to each node, and a + * reply is expected from each node of knowing when all the replies have been + * received. + * + * If all nodes are up, it's easy to see when all replies are received. + * But, if some nodes are down, we really don't want to wait for a timeout + * before we decide that we've gotten all the replies we're going to get, + * since nodes can be down for potentially very long periods of time, and + * waiting for a long timeout can delay things a great deal again and + * again - causing significant delays and user frustration. + * + * That's where these functions come in! + * Instead, inform these functions what nodes are up and what ones are down, + * and when you receive a reply, and it will tell you when you've gotten + * them all - managing all that tedious bookwork for you. + */ + +typedef enum _replytrack_completion_type replytrack_completion_type_t; +typedef enum _nodetrack_change nodetrack_change_t; +typedef struct _replytrack replytrack_t; +typedef struct _nodetrack nodetrack_t; +typedef struct _nodetrack_intersection nodetrack_intersection_t; + +/* + * The levels of logging possible for our process + */ +enum _replytrack_completion_type { + REPLYT_ALLRCVD = 2, /* All replies received */ + REPLYT_TIMEOUT, /* Timeout occurred with replies missing */ +}; + + +typedef void (*replytrack_callback_t) +( replytrack_t * rl +, gpointer user_data +, replytrack_completion_type_t reason); + +typedef void (*replytrack_iterator_t) +( replytrack_t* rl +, gpointer user_data +, const char* node +, cl_uuid_t uuid); + +typedef void (*nodetrack_iterator_t) +( nodetrack_t* rl +, gpointer user_data +, const char* node +, cl_uuid_t uuid); + + +/* + * Note: + * If you use the timeout feature of this code, it relies on you using glib mainloop + * for your scheduling. timeout_ms should be zero for no timeout. + */ +replytrack_t* replytrack_new(nodetrack_t* membership +, replytrack_callback_t callback +, unsigned long timeout_ms +, gpointer user_data); + +void replytrack_del(replytrack_t *rl); +gboolean replytrack_gotreply(replytrack_t *rl +, const char * node +, cl_uuid_t uuid); + /* Returns TRUE if this was the final expected reply */ +/* + * Iterate over the set of outstanding replies: + * return count of how many items in the iteration + */ +int replytrack_outstanding_iterate(replytrack_t* rl +, replytrack_iterator_t i, gpointer user_data); +int replytrack_outstanding_count(replytrack_t* rl); + +/* + * The functions above operate using a view of membership which is established + * through the functions below. + * + * This can either be through the heartbeat low-level membership API, or any + * other view of membership you wish. Mentioning a node as either up or down + * will automatically add that node to our view of potential membership. + * + * These functions only support one view of membership per process. + * + * The general idea of how to use these functions: + * Initially: + * 1) iterate through init membership and call nodetrack_node(up|down) for + * each node to start things off. + * + * On an ongoing basis: + * 2) call nodetrack_node_up whenever a node comes up + * We expect a reply from nodes that are up. + * 3) call nodetrack_node_down whenever a node goes down + * We don't expect a reply from nodes that are down. + * + * For each set of replies you want tracked: + * 4) Create a replytrack_t for a set of expected replies + * 5) call replytrack_gotreply() each time you get an expected reply + * 6) replist_gotreply() returns TRUE when the final message was received. + * (it does this by comparing against the membership as defined below) + * 7) you will get a callback when timeout occurs or final message is received + * n. b.: + * No callback function => manage timeouts yourself + * 8) call replytrack_del() when you're done with the reply list + * n. b.: + * If you have replies outstanding, and you have a timeout and + * a callback function set, you will get a warning for destroying + * a replytrack_t object 'prematurely'. + * You will also log a warning if you call replytrack_gotreply() after + * all replies were received or a timeout occurred. + * + */ + +/* + * The levels of logging possible for our process + */ +enum _nodetrack_change { + NODET_UP = 2, /* This node came up */ + NODET_DOWN, /* This node went down */ +}; + +typedef void (*nodetrack_callback_t) +( nodetrack_t * mbr +, const char * node +, cl_uuid_t u +, nodetrack_change_t reason +, gpointer user_data); + +nodetrack_t* nodetrack_new(nodetrack_callback_t callback +, gpointer user_data); +void nodetrack_del(nodetrack_t*); +gboolean nodetrack_nodeup(nodetrack_t* mbr, const char * node +, cl_uuid_t u); +gboolean nodetrack_nodedown(nodetrack_t* mbr, const char * node +, cl_uuid_t u); +gboolean nodetrack_ismember(nodetrack_t* mbr, const char * node +, cl_uuid_t u); +int nodetrack_iterate(nodetrack_t* mbr +, nodetrack_iterator_t i, gpointer user_data); + +/* An intesection nodetrack table + * A node is put into the "intersection" nodetrack_t table when it is in all + * the underlying constituent nodetrack_t tables, and removed when it is + * removed from any of them. + * Note that you can set a callback to be informed when these "intersection" + * membership changes occur. + */ +nodetrack_intersection_t* + nodetrack_intersection_new(nodetrack_t** tables, int ntables +, nodetrack_callback_t callback, gpointer user_data); +void nodetrack_intersection_del(nodetrack_intersection_t*); +nodetrack_t* nodetrack_intersection_table(nodetrack_intersection_t*); + +#if 0 +/* + * I don't know if this should be in this library, or just in + * the CCM. Probably only the CCM _should_ be using it (when I write it) + */ +/* + * Use of the nodetrack_hb_* functions implies you're using the heartbeat + * peer-connectivity information as your source of information. This is + * really only suitable if you're using heartbeat's low-level group membership + * for your source of who to expect replies from. + * If you're using nodetrack_hb_init, this replaces step (1) above. + */ +void nodetrack_hb_init(void) +/* + * If you're using nodetrack_hb_statusmsg, just pass it all status messages + * and all peer-connectivity status messages or even all heartbeat messages + * (non-status messages will be ignored). + * This replaces steps (2) and (3) above _if_ you're using heartbeat low + * level membership for your source of who to expect replies from. + */ +void nodetrack_hb_statusmsg(struct ha_msg* statusmsg); +#endif /*0*/ + +#endif diff --git a/include/clplumbing/setproctitle.h b/include/clplumbing/setproctitle.h new file mode 100644 index 0000000..5caeef0 --- /dev/null +++ b/include/clplumbing/setproctitle.h @@ -0,0 +1,64 @@ +/* + * setproctitle.h + * + * The code in this file, setproctitle.h is heavily based on code from + * proftpd, please see the licening information below. + * + * This file added to the heartbeat tree by Horms + * + * Code to portably change the title of a programme as displayed + * by ps(1). + * + * heartbeat: Linux-HA heartbeat code + * + * Copyright (C) 1999,2000,2001 Alan Robertson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * ProFTPD - FTP server daemon + * Copyright (c) 1997, 1998 Public Flood Software + * Copyright (C) 1999, 2000 MacGyver aka Habeeb J. Dihu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu + * and other respective copyright holders give permission to link this program + * with OpenSSL, and distribute the resulting executable, without including + * the source code for OpenSSL in the source distribution. + */ + +#ifndef _HA_SETPROCTITLE_H +#define _HA_SETPROCTITLE_H + +#include +int init_set_proc_title(int argc, char *argv[], char *envp[]); + +void set_proc_title(const char *fmt,...) G_GNUC_PRINTF(1,2); + +#endif /* _HA_SETPROCTITLE_H */ diff --git a/include/clplumbing/timers.h b/include/clplumbing/timers.h new file mode 100644 index 0000000..669ac21 --- /dev/null +++ b/include/clplumbing/timers.h @@ -0,0 +1,23 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CLPLUMBING_TIMERS_H +# define _CLPLUMBING_TIMERS_H +int setmsrepeattimer(long ms); +int setmsalarm(long ms); +int cancelmstimer(void); +long mssleep(long ms); +#endif diff --git a/include/clplumbing/uids.h b/include/clplumbing/uids.h new file mode 100644 index 0000000..89ba303 --- /dev/null +++ b/include/clplumbing/uids.h @@ -0,0 +1,32 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CLPLUMBING_UIDS_H +# define CLPLUMBING_UIDS_H +#include + +/* Tell us who you want to be - or zero for nobody */ +int drop_privs(uid_t uid, gid_t gid); + +/* Return to original privileged state */ +int return_to_orig_privs(void); + +/* Drop down to (probably nobody) privileges again */ +int return_to_dropped_privs(void); + +/* Return TRUE if we have full privileges at the moment */ +int cl_have_full_privs(void); +#endif diff --git a/include/compress.h b/include/compress.h new file mode 100644 index 0000000..9cd733c --- /dev/null +++ b/include/compress.h @@ -0,0 +1,50 @@ +/* + * compress.h: Compression functions for Linux-HA + * + * Copyright (C) 2005 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _COMPRESS_H_ +#define _COMPRESS_H_ + +#define HB_COMPRESS_TYPE compress +#define HB_COMPRESS_TYPE_S "compress" + +/* + * List of functions provided by implementations of the heartbeat + * compress interface. + */ +struct hb_compress_fns { + int (*compress) (char*, size_t*, const char*, size_t); + int (*decompress) (char*, size_t* , const char*, size_t); + const char* (*getname) (void); +}; + +struct ha_msg; + +/* set the compression method*/ +int cl_compress_remove_plugin(const char* pluginname); +int cl_compress_load_plugin(const char* pluginname); +struct hb_compress_fns* cl_get_compress_fns(void); +int cl_set_compress_fns(const char*); +char* cl_compressmsg(struct ha_msg*m, size_t* len); +struct ha_msg* cl_decompressmsg(struct ha_msg* m); +gboolean is_compressed_msg(struct ha_msg* m); +int cl_compress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen); +int cl_decompress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen); + +#endif diff --git a/include/glue_config.h.in b/include/glue_config.h.in new file mode 100644 index 0000000..0850a63 --- /dev/null +++ b/include/glue_config.h.in @@ -0,0 +1,104 @@ +/* include/config.h.in. Generated from configure.in by autoheader. */ + +/* Location for daemons */ +#undef GLUE_DAEMON_DIR + +/* Group to run daemons as */ +#undef GLUE_DAEMON_GROUP + +/* User to run daemons as */ +#undef GLUE_DAEMON_USER + +/* Where to keep state files and sockets */ +#undef GLUE_STATE_DIR + +/* Location of shared data */ +#undef GLUE_SHARED_DIR + +/* User to run daemons as */ +#undef HA_CCMUSER + +/* Group to run daemons as */ +#undef HA_APIGROUP + +/* Location for daemons */ +#undef HA_LIBHBDIR + +/* top directory of area to drop core files in */ +#undef HA_COREDIR + +/* top directory for LRM related files */ +#undef LRM_VARLIBDIR + +/* CIB secrets */ +#undef LRM_CIBSECRETS + +/* Logging Daemon IPC socket name */ +#undef HA_LOGDAEMON_IPC + +/* Default logging facility */ +#undef HA_LOG_FACILITY + +/* Default plugin search path */ +#undef PILS_BASE_PLUGINDIR + +/* Where to find plugins */ +#undef HA_PLUGIN_DIR + +/* Location of system configuration files */ +#undef HA_SYSCONFDIR + +/* Web site base URL */ +#undef HA_URLBASE + +/* Whatever this used to mean */ +#undef HA_VARLIBHBDIR + +#undef HA_VARLIBDIR + +/* System lock directory */ +#undef HA_VARLOCKDIR + +/* Where Heartbeat keeps state files and sockets - old name */ +#undef HA_VARRUNDIR + +/* Location for v1 Heartbeat RAs */ +#undef HB_RA_DIR + +/* Where to find LRM plugins */ +#undef LRM_PLUGIN_DIR + +/* Location for LSB RAs */ +#undef LSB_RA_DIR + +/* Location for OCF RAs */ +#undef OCF_RA_DIR + +/* OCF root directory - specified by the OCF standard */ +#undef OCF_ROOT_DIR + +/* Compiling for Darwin platform */ +#undef ON_DARWIN + +/* Compiling for Linux platform */ +#undef ON_LINUX + +/* Current glue version */ +#undef GLUE_VERSION + +/* Build version */ +#undef GLUE_BUILD_VERSION + +/* Location of non-pluing stonith scripts */ +#undef STONITH_EXT_PLUGINDIR + +/* Location of RHCS stonith scripts */ +#undef STONITH_RHCS_PLUGINDIR + +/* Location of stonith plugins */ +#undef STONITH_MODULES + +/* Stonith plugin domain */ +#undef ST_TEXTDOMAIN + +#undef HA_HBCONF_DIR diff --git a/include/ha_msg.h b/include/ha_msg.h new file mode 100644 index 0000000..fcb6cf6 --- /dev/null +++ b/include/ha_msg.h @@ -0,0 +1,464 @@ +/* + * Intracluster message object (struct ha_msg) + * + * Copyright (C) 1999, 2000 Alan Robertson + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _HA_MSG_H +# define _HA_MSG_H 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +enum cl_netstring_type{ + FT_STRING = 0, + FT_BINARY, + FT_STRUCT, + FT_LIST, + FT_COMPRESS, + FT_UNCOMPRESS +}; + +enum cl_msgfmt{ + MSGFMT_NVPAIR, + MSGFMT_NETSTRING +}; + + +#define NEEDHEAD 1 +#define NOHEAD 0 +#define HA_MSG_ASSERT(X) do{ if(!(X)){ \ + cl_log(LOG_ERR, "Assertion failed on line %d in file \"%s\"" \ + , __LINE__, __FILE__); \ + abort(); \ + } \ + }while(0) + +typedef struct hb_msg_stats_s { + unsigned long totalmsgs; /* Total # of messages */ + /* ever handled */ + unsigned long allocmsgs; /* # Msgs currently allocated */ + longclock_t lastmsg; +}hb_msg_stats_t; + +struct ha_msg { + int nfields; + int nalloc; + char ** names; + size_t* nlens; + void ** values; + size_t* vlens; + int * types; +}; + +typedef struct ha_msg HA_Message; + +struct fieldtypefuncs_s{ + + /* memfree frees the memory involved*/ + void (*memfree)(void*); + + /* dup makes a complete copy of the field*/ + void* (*dup)(const void*, size_t); + + /* display printout the field*/ + void (*display)(int, int, char* , void*, int); + + /* add the field into a message*/ + int (*addfield) (struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth); + + /* return the string length required to add this field*/ + int (*stringlen) (size_t namlen, size_t vallen, const void* value); + + /* return the netstring length required to add this field*/ + int (*netstringlen) (size_t namlen, size_t vallen, const void* value); + + /* print the field into the provided buffer, convert it first */ + /* if ncecessary*/ + int (*tostring)(char*, char*, void* ,size_t,int); + + /* print the field into the provided buffer*/ + int (*tonetstring)(char*, char*, char*, size_t, + void*, size_t, int, size_t*); + + /* convert the given string to a field + note: this functions involves allocate memory for + for the field + */ + int (*stringtofield)(void*, size_t, int depth, void**, size_t* ); + + /* convert the given netstring to a field + note: this functions involves allocate memory for + for the field + */ + int (*netstringtofield)(const void*, size_t, void**, size_t*); + + /* action before packing*/ + int (*prepackaction)(struct ha_msg* m, int index); + + /* action before a user get the value of a field*/ + int (*pregetaction)(struct ha_msg* m, int index); + +}; + +#define NUM_MSG_TYPES 6 +extern struct fieldtypefuncs_s fieldtypefuncs[NUM_MSG_TYPES]; + +#define MSG_NEEDAUTH 0x01 +#define MSG_ALLOWINTR 0X02 +#define MSG_NEEDCOMPRESS 0x04 +#define MSG_NOSIZECHECK 0x08 + +#define IFACE "!^!\n" +#define MSG_START ">>>\n" +#define MSG_END "<<<\n" +#define MSG_START_NETSTRING "###\n" +#define MSG_END_NETSTRING "%%%\n" +#define EQUAL "=" + +#define MAXDEPTH 16 /* Maximum recursive message depth */ +#define MAXLENGTH 1024 + + /* Common field names for our messages */ +#define F_TYPE "t" /* Message type */ +#define F_SUBTYPE "subt" /* Message type */ +#define F_ORIG "src" /* Real Originator */ +#define F_ORIGUUID "srcuuid" /* Real Originator uuid*/ +#define F_NODE "node" /* Node being described */ +#define F_NODELIST "nodelist" /* Node list being described */ +#define F_DELNODELIST "delnodelist" /* Del node list being described */ +#define F_TO "dest" /* Destination (optional) */ +#define F_TOUUID "destuuid" /* Destination uuid(optional) */ +#define F_STATUS "st" /* New status (type = status) */ +#define F_WEIGHT "weight" /* weight of node */ +#define F_SITE "site" /* site of node */ +#define F_PROTOCOL "protocol" /* Protocol number for communication*/ +#define F_CLIENTNAME "cn" /* Client name */ +#define F_CLIENTSTATUS "cs" /* Client status */ +#define F_TIME "ts" /* Timestamp */ +#define F_SEQ "seq" /* Sequence number */ +#define F_LOAD "ld" /* Load average */ +#define F_COMMENT "info" /* Comment */ +#define F_TTL "ttl" /* Time To Live */ +#define F_AUTH "auth" /* Authentication string */ +#define F_HBGENERATION "hg" /* Heartbeat generation number */ +#define F_CLIENT_GENERATION "client_gen" /* client generation number*/ +#define F_FIRSTSEQ "firstseq" /* Lowest seq # to retransmit */ +#define F_LASTSEQ "lastseq" /* Highest seq # to retransmit */ +#define F_RESOURCES "rsc_hold" /* What resources do we hold? */ +#define F_FROMID "from_id" /* from Client id */ +#define F_TOID "to_id" /* To client id */ +#define F_PID "pid" /* PID of client */ +#define F_UID "uid" /* uid of client */ +#define F_GID "gid" /* gid of client */ +#define F_ISSTABLE "isstable" /* true/false for RESOURCES */ +#define F_APIREQ "reqtype" /* API request type for "hbapi" */ +#define F_APIRESULT "result" /* API request result code */ +#define F_IFNAME "ifname" /* Interface name */ +#define F_PNAME "pname" /* Parameter name */ +#define F_PVALUE "pvalue" /* Parameter name */ +#define F_DEADTIME "deadtime" /* Dead time interval in ms. */ +#define F_KEEPALIVE "keepalive" /* Keep alive time interval in ms. */ +#define F_LOGFACILITY "logfacility" /* Suggested cluster syslog facility */ +#define F_NODETYPE "nodetype" /* Type of node */ +#define F_NUMNODES "numnodes" /* num of total nodes(excluding ping nodes*/ +#define F_RTYPE "rtype" /* Resource type */ +#define F_ORDERSEQ "oseq" /* Order Sequence number */ +#define F_DT "dt" /* Dead time field for heartbeat*/ +#define F_ACKSEQ "ackseq" /* The seq number this msg is acking*/ +#define F_CRM_DATA "crm_xml" +#define F_XML_TAGNAME "__name__" +#define F_STATE "state" /*used in ccm for state info*/ + + + /* Message types */ +#define T_STATUS "status" /* Status (heartbeat) */ +#define T_IFSTATUS "ifstat" /* Interface status */ +#define T_ASKRESOURCES "ask_resources" /* Let other node ask my resources */ +#define T_ASKRELEASE "ip-request" /* Please give up these resources... */ +#define T_ACKRELEASE "ip-request-resp"/* Resources given up... */ +#define T_QCSTATUS "query-cstatus" /* Query client status */ +#define T_RCSTATUS "respond-cstatus"/* Respond client status */ +#define T_STONITH "stonith" /* Stonith return code */ +#define T_SHUTDONE "shutdone" /* External Shutdown complete */ +#define T_CRM "crmd" /* Cluster resource manager message */ +#define T_ATTRD "attrd" /* Cluster resource manager message */ +#define T_ADDNODE "addnode" /* Add node message*/ +#define T_DELNODE "delnode" /* Delete node message*/ +#define T_SETWEIGHT "setweight" /* Set node weight*/ +#define T_SETSITE "setsite" /* Set node site*/ +#define T_REQNODES "reqnodes" /* Request node list */ +#define T_REPNODES "repnodes" /* reply node list rquest*/ + +#define T_APIREQ "hbapi-req" /* Heartbeat API request */ +#define T_APIRESP "hbapi-resp" /* Heartbeat API response */ +#define T_APICLISTAT "hbapi-clstat" /* Client status notification" */ + +#define NOSEQ_PREFIX "NS_" /* PREFIX: Give no sequence number */ + /* Used for messages which can't be retransmitted */ + /* Either they're protocol messages or from dumb (ping) endpoints */ +#define T_REXMIT NOSEQ_PREFIX "rexmit" /* Rexmit request */ +#define T_NAKREXMIT NOSEQ_PREFIX "nak_rexmit" /* NAK Rexmit request */ +#define T_NS_STATUS NOSEQ_PREFIX "st" /* ping status */ +#define T_ACKMSG NOSEQ_PREFIX "ackmsg" /* ACK message*/ + +/* Messages associated with nice_failback */ +#define T_STARTING "starting" /* Starting Heartbeat */ + /* (requesting resource report) */ +#define T_RESOURCES "resource" /* Resources report */ + +/* Messages associated with stonith completion results */ +#define T_STONITH_OK "OK" /* stonith completed successfully */ +#define T_STONITH_BADHOST "badhost" /* stonith failed */ +#define T_STONITH_BAD "bad" /* stonith failed */ +#define T_STONITH_NOTCONFGD "n_stnth" /* no stonith device configured */ +#define T_STONITH_UNNEEDED "unneeded" /* STONITH not required */ + +/* Set up message statistics area */ + +int netstring_extra(int); +int cl_msg_stats_add(longclock_t time, int size); + +void cl_msg_setstats(volatile hb_msg_stats_t* stats); +void cl_dump_msgstats(void); +void cl_set_compression_threshold(size_t threadhold); +void cl_set_traditional_compression(gboolean value); + +/* Allocate new (empty) message */ +struct ha_msg * ha_msg_new(int nfields); + +/* Free message */ +void ha_msg_del(struct ha_msg *msg); + +/* Copy message */ +struct ha_msg* ha_msg_copy(const struct ha_msg *msg); + +int ha_msg_expand(struct ha_msg* msg ); + +/*Add a null-terminated name and binary value to a message*/ +int ha_msg_addbin(struct ha_msg * msg, const char * name, + const void * value, size_t vallen); + +int ha_msg_adduuid(struct ha_msg * msg, const char * name, + const cl_uuid_t* uuid); + +/* Add null-terminated name and a value to the message */ +int ha_msg_add(struct ha_msg * msg + , const char* name, const char* value); + +int cl_msg_remove(struct ha_msg* msg, const char* name); +int cl_msg_remove_value(struct ha_msg* msg, const void* value); +int cl_msg_remove_offset(struct ha_msg* msg, int offset); + +/* Modify null-terminated name and a value to the message */ +int cl_msg_modstring(struct ha_msg * msg, + const char* name, + const char* value); +int cl_msg_modbin(struct ha_msg * msg, + const char* name, + const void* value, + size_t vlen); + +int cl_msg_moduuid(struct ha_msg * msg, const char * name, + const cl_uuid_t* uuid); + +int cl_msg_modstruct(struct ha_msg * msg, + const char* name, + const struct ha_msg* value); +#define ha_msg_mod(msg, name, value) cl_msg_modstring(msg, name, value) +int cl_msg_replace(struct ha_msg* msg, int index, + const void* value, size_t vlen, int type); +int cl_msg_replace_value(struct ha_msg* msg, const void *old_value, + const void* value, size_t vlen, int type); + +/* Add name, value (with known lengths) to the message */ +int ha_msg_nadd(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen); + +/* Add a name/value/type to a message (with sizes for name and value) */ +int ha_msg_nadd_type(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen, int type); + +/* Add name=value string to a message */ +int ha_msg_add_nv(struct ha_msg* msg, const char * nvline, const char * bufmax); + + +/* Return value associated with particular name */ +#define ha_msg_value(m,name) cl_get_string(m, name) + +/* Call wait(in|out) but only for a finite time */ +int cl_ipc_wait_timeout( + IPC_Channel * chan, int (*waitfun)(IPC_Channel * chan), unsigned int timeout); + +/* Reads an IPC stream -- converts it into a message */ +struct ha_msg * msgfromIPC_timeout(IPC_Channel *ch, int flag, unsigned int timeout, int *rc_out); +struct ha_msg * msgfromIPC(IPC_Channel * f, int flag); + +IPC_Message * ipcmsgfromIPC(IPC_Channel * ch); + +/* Reads a stream -- converts it into a message */ +struct ha_msg * msgfromstream(FILE * f); + +/* Reads a stream with string format--converts it into a message */ +struct ha_msg * msgfromstream_string(FILE * f); + +/* Reads a stream with netstring format--converts it into a message */ +struct ha_msg * msgfromstream_netstring(FILE * f); + +/* Same as above plus copying the iface name to "iface" */ +struct ha_msg * if_msgfromstream(FILE * f, char *iface); + +/* Writes a message into a stream */ +int msg2stream(struct ha_msg* m, FILE * f); + +/* Converts a message into a string and adds the iface name on start */ +char * msg2if_string(const struct ha_msg *m, const char * iface); + +/* Converts a string gotten via UDP into a message */ +struct ha_msg * string2msg(const char * s, size_t length); + +/* Converts a message into a string */ +char * msg2string(const struct ha_msg *m); + +/* Converts a message into a string in the provided buffer with certain +depth and with or without start/end */ +int msg2string_buf(const struct ha_msg *m, char* buf, + size_t len, int depth, int needhead); + +/* Converts a message into wire format */ +char* msg2wirefmt(struct ha_msg *m, size_t* ); +char* msg2wirefmt_noac(struct ha_msg*m, size_t* len); + +/* Converts wire format data into a message */ +struct ha_msg* wirefmt2msg(const char* s, size_t length, int flag); + +/* Convets wire format data into an IPC message */ +IPC_Message* wirefmt2ipcmsg(void* p, size_t len, IPC_Channel* ch); + +/* Converts an ha_msg into an IPC message */ +IPC_Message* hamsg2ipcmsg(struct ha_msg* m, IPC_Channel* ch); + +/* Converts an IPC message into an ha_msg */ +struct ha_msg* ipcmsg2hamsg(IPC_Message*m); + +/* Outputs a message to an IPC channel */ +int msg2ipcchan(struct ha_msg*m, IPC_Channel*ch); + +/* Outpus a message to an IPC channel without authencating +the message */ +struct ha_msg* msgfromIPC_noauth(IPC_Channel * ch); + +/* Reads from control fifo, and creates a new message from it */ +/* This adds the default sequence#, load avg, etc. to the message */ +struct ha_msg * controlfifo2msg(FILE * f); + +/* Check if the message is authenticated */ +gboolean isauthentic(const struct ha_msg * msg); + +/* Get the required string length for the given message */ +int get_stringlen(const struct ha_msg *m); + +/* Get the requried netstring length for the given message*/ +int get_netstringlen(const struct ha_msg *m); + +/* Add a child message to a message as a field */ +int ha_msg_addstruct(struct ha_msg * msg, const char * name, const void* ptr); + +int ha_msg_addstruct_compress(struct ha_msg*, const char*, const void*); + +/* Get binary data from a message */ +const void * cl_get_binary(const struct ha_msg *msg, const char * name, size_t * vallen); + +/* Get uuid data from a message */ +int cl_get_uuid(const struct ha_msg *msg, const char * name, cl_uuid_t* retval); + +/* Get string data from a message */ +const char * cl_get_string(const struct ha_msg *msg, const char *name); + +/* Get the type for a field from a message */ +int cl_get_type(const struct ha_msg *msg, const char *name); + +/* Get a child message from a message*/ +struct ha_msg *cl_get_struct(struct ha_msg *msg, const char* name); + +/* Log the contents of a message */ +void cl_log_message (int log_level, const struct ha_msg *m); + +/* Supply messaging system with old style authentication/authorization method */ +void cl_set_oldmsgauthfunc(gboolean (*authfunc)(const struct ha_msg*)); + +/* Set default messaging format */ +void cl_set_msg_format(enum cl_msgfmt mfmt); + +/* Add a string to a list*/ +int cl_msg_list_add_string(struct ha_msg* msg, const char* name, const char* value); + +/* Return length of a list*/ +int cl_msg_list_length(struct ha_msg* msg, const char* name); + +/* Return nth element of a list*/ +void* cl_msg_list_nth_data(struct ha_msg* msg, const char* name, int n); + +/* Functions to add/mod/get an integer */ +int ha_msg_add_int(struct ha_msg * msg, const char * name, int value); +int ha_msg_mod_int(struct ha_msg * msg, const char * name, int value); +int ha_msg_value_int(const struct ha_msg * msg, const char * name, int* value); + +/* Functions to add/mod/get an unsigned long */ +int ha_msg_add_ul(struct ha_msg * msg, const char * name, unsigned long value); +int ha_msg_mod_ul(struct ha_msg * msg, const char * name, unsigned long value); +int ha_msg_value_ul(const struct ha_msg * msg, const char * name, unsigned long* value); + +/* Functions to add/get a string list*/ +GList* ha_msg_value_str_list(struct ha_msg * msg, const char * name); + +int cl_msg_add_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t n); +int cl_msg_get_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t* n); +GList* cl_msg_get_list(struct ha_msg* msg, const char* name); +int cl_msg_add_list(struct ha_msg* msg, const char* name, GList* list); +int cl_msg_add_list_str(struct ha_msg* msg, const char* name, + char** buf, size_t n); + +/* Function to add/get a string hash table*/ +GHashTable* ha_msg_value_str_table(struct ha_msg * msg, const char * name); +int ha_msg_add_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table); +int ha_msg_mod_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table); + +/*internal use for list type*/ +size_t string_list_pack_length(const GList* list); +int string_list_pack(GList* list, char* buf, char* maxp); +GList* string_list_unpack(const char* packed_str_list, size_t length); +void list_cleanup(GList* list); + +gboolean must_use_netstring(const struct ha_msg*); + + +#endif /* __HA_MSG_H */ diff --git a/include/lha_internal.h b/include/lha_internal.h new file mode 100644 index 0000000..bae10a0 --- /dev/null +++ b/include/lha_internal.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2001 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * + */ + +#ifndef LHA_INTERNAL_H +# define LHA_INTERNAL_H + +#define EOS '\0' +#define DIMOF(a) ((int) (sizeof(a)/sizeof(a[0])) ) +#define STRLEN_CONST(conststr) ((size_t)((sizeof(conststr)/sizeof(char))-1)) +#define STRNCMP_CONST(varstr, conststr) strncmp((varstr), conststr, STRLEN_CONST(conststr)+1) +#define STRLEN(c) STRLEN_CONST(c) +#define MALLOCT(t) ((t *) malloc(sizeof(t))) + +#define HADEBUGVAL "HA_DEBUG" /* current debug value (if nonzero) */ +#define HALOGD "HA_LOGD" /* whether we use logging daemon or not */ + +/* Needs to be defined before any other includes, otherwise some system + * headers do not behave as expected! Major black magic... */ +#undef _GNU_SOURCE /* in case it was defined on the command line */ +#define _GNU_SOURCE + +/* Please leave this as the first #include - Solaris needs it there */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef BSD +# define SCANSEL_CAST (void *) +#else +# define SCANSEL_CAST /* Nothing */ +#endif + +#if defined(ANSI_ONLY) && !defined(inline) +# define inline /* nothing */ +# undef NETSNMP_ENABLE_INLINE +# define NETSNMP_NO_INLINE 1 +#endif + +#ifndef HAVE_DAEMON + /* We supply a replacement function, but need a prototype */ +int daemon(int nochdir, int noclose); +#endif /* HAVE_DAEMON */ + +#ifndef HAVE_SETENV + /* We supply a replacement function, but need a prototype */ +int setenv(const char *name, const char * value, int why); +#endif /* HAVE_SETENV */ + +#ifndef HAVE_UNSETENV + /* We supply a replacement function, but need a prototype */ +int unsetenv(const char *name); +#endif /* HAVE_UNSETENV */ + +#ifndef HAVE_STRERROR + /* We supply a replacement function, but need a prototype */ +char * strerror(int errnum); +#endif /* HAVE_STRERROR */ + +#ifndef HAVE_SCANDIR + /* We supply a replacement function, but need a prototype */ +# include +int +scandir (const char *directory_name, + struct dirent ***array_pointer, + int (*select_function) (const struct dirent *), +#ifdef USE_SCANDIR_COMPARE_STRUCT_DIRENT + /* This is what the Linux man page says */ + int (*compare_function) (const struct dirent**, const struct dirent**) +#else + /* This is what the Linux header file says ... */ + int (*compare_function) (const void *, const void *) +#endif + ); +#endif /* HAVE_SCANDIR */ + +#ifndef HAVE_ALPHASORT +# include +int +alphasort(const void *dirent1, const void *dirent2); +#endif /* HAVE_ALPHASORT */ + +#ifndef HAVE_INET_PTON + /* We supply a replacement function, but need a prototype */ +int +inet_pton(int af, const char *src, void *dst); + +#endif /* HAVE_INET_PTON */ + +#ifndef HAVE_STRNLEN + size_t strnlen(const char *s, size_t maxlen); +#else +# define USE_GNU +#endif + +#ifndef HAVE_STRNDUP + char *strndup(const char *str, size_t len); +#else +# define USE_GNU +#endif +#ifndef HAVE_STRLCPY + size_t strlcpy(char * dest, const char *source, size_t len); +#endif +#ifndef HAVE_STRLCAT + size_t strlcat(char * dest, const char *source, size_t len); +#endif + +#ifndef HAVE_NFDS_T + typedef unsigned int nfds_t; +#endif + +#ifdef HAVE_STRUCT_UCRED_DARWIN +# include +# ifndef SYS_NMLN +# define SYS_NMLN _SYS_NAMELEN +# endif /* SYS_NMLN */ +#endif + +#define POINTER_TO_SIZE_T(p) ((size_t)(p)) /*pointer cast as size_t*/ +#define POINTER_TO_SSIZE_T(p) ((ssize_t)(p)) /*pointer cast as ssize_t*/ +#define POINTER_TO_ULONG(p) ((unsigned long)(p)) /*pointer cast as unsigned long*/ + /* Sometimes we get a const g_something *, but need to pass it internally + * to other functions taking a non-const g_something *, which results + * with gcc and -Wcast-qual in a compile time warning, and with -Werror + * even to a compile time error. + * Workarounds have been to e.g. memcpy(&list, _list); or similar, + * the reason of which is non-obvious to the casual reader. + * This macro achieves the same, and annotates why it is done. + */ +#define UNCONST_CAST_POINTER(t, p) ((t)(unsigned long)(p)) + +#define HAURL(url) HA_URLBASE url + +/* + * Some compilers may not have defined __FUNCTION__. + */ +#ifndef __FUNCTION__ + +/* Sun studio compiler */ +# ifdef __SUNPRO_C +# define __FUNCTION__ __func__ +# endif + +/* Similarly add your compiler here ... */ + +#endif + +/* You may need to change this for your compiler */ +#ifdef HAVE_STRINGIZE +# define ASSERT(X) {if(!(X)) ha_assert(#X, __LINE__, __FILE__);} +#else +# define ASSERT(X) {if(!(X)) ha_assert("X", __LINE__, __FILE__);} +#endif + +/* shamelessly stolen from linux kernel */ +/* Force a compilation error if condition is true */ +#define BUILD_BUG_ON(condition) ((void)BUILD_BUG_ON_ZERO(condition)) +/* Force a compilation error if condition is true, but also produce a + * result (of value 0 and type size_t), so the expression can be used + * e.g. in a structure initializer (or where-ever else comma expressions + * aren't permitted). */ +#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); })) +#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); })) + +#endif /* LHA_INTERNAL_H */ diff --git a/include/lrm/Makefile.am b/include/lrm/Makefile.am new file mode 100644 index 0000000..ec4f5a5 --- /dev/null +++ b/include/lrm/Makefile.am @@ -0,0 +1,22 @@ +# +# Author: Sun Jiang Dong +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +idir=$(includedir)/heartbeat/lrm +i_HEADERS = lrm_api.h lrm_msg.h racommon.h raexec.h diff --git a/include/lrm/lrm_api.h b/include/lrm/lrm_api.h new file mode 100644 index 0000000..cebff1b --- /dev/null +++ b/include/lrm/lrm_api.h @@ -0,0 +1,455 @@ +/* + * Client-side Local Resource Manager API. + * + * Author: Huang Zhen + * Copyright (C) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * + * By Huang Zhen 2004/2/23 + * + * It is based on the works of Alan Robertson, Lars Marowsky Bree, + * Andrew Beekhof. + * + * The Local Resource Manager needs to provide the following functionalities: + * 1. Provide the information of the resources holding by the node to its + * clients, including listing the resources and their status. + * 2. Its clients can add new resources to lrm or remove from it. + * 3. Its clients can ask lrm to operate the resources, including start, + * restart, stop and so on. + * 4. Provide the information of the lrm itself, including what types of + * resource are supporting by lrm. + * + * The typical clients of lrm are crm and lrmadmin. + */ + + /* + * Notice: + * "status" indicates the exit status code of "status" operation + * its value is defined in LSB, OCF... + * + * "state" indicates the state of resource, maybe LRM_RSC_BUSY, LRM_RSC_IDLE + * + * "op_status" indicates how the op exit. like LRM_OP_DONE,LRM_OP_CANCELLED, + * LRM_OP_TIMEOUT,LRM_OP_NOTSUPPORTED. + * + * "rc" is the return code of an opertioan. it's value is in following enum + * which is defined in "raexec.h" + * enum UNIFORM_RET_EXECRA { + * EXECRA_EXEC_UNKNOWN_ERROR = -2, + * EXECRA_NO_RA = -1, + * EXECRA_OK = 0, + * EXECRA_UNKNOWN_ERROR = 1, + * EXECRA_INVALID_PARAM = 2, + * EXECRA_UNIMPLEMENT_FEATURE = 3, + * EXECRA_INSUFFICIENT_PRIV = 4, + * EXECRA_NOT_INSTALLED = 5, + * EXECRA_NOT_CONFIGURED = 6, + * EXECRA_NOT_RUNNING = 7, + * + * EXECRA_RA_DEAMON_DEAD1 = 11, + * EXECRA_RA_DEAMON_DEAD2 = 12, + * EXECRA_RA_DEAMON_STOPPED = 13, + * EXECRA_STATUS_UNKNOWN = 14 + * }; + */ + +#ifndef __LRM_API_H +#define __LRM_API_H 1 + +#include +#include +#include + +#define LRM_PROTOCOL_MAJOR 0 +#define LRM_PROTOCOL_MINOR 1 +#define LRM_PROTOCOL_VERSION ((LRM_PROTCOL_MAJOR << 16) | LRM_PROTOCOL_MINOR) + +#define RID_LEN 128 + +/*lrm's client uses this structure to access the resource*/ +typedef struct +{ + char* id; + char* type; + char* class; + char* provider; + GHashTable* params; + struct rsc_ops* ops; +}lrm_rsc_t; + + +/*used in struct lrm_op_t to show how an operation exits*/ +typedef enum { + LRM_OP_PENDING = -1, + LRM_OP_DONE, + LRM_OP_CANCELLED, + LRM_OP_TIMEOUT, + LRM_OP_NOTSUPPORTED, + LRM_OP_ERROR +}op_status_t; + +/*for all timeouts: in milliseconds. 0 for no timeout*/ + +/*this structure is the information of the operation.*/ + +#define EVERYTIME -1 +#define CHANGED -2 + +/* Notice the interval and target_rc + * + * when interval==0, the operation will be executed only once + * when interval>0, the operation will be executed repeatly with the interval + * + * when target_rc==EVERYTIME, the client will be notified every time the + * operation executed. + * when target_rc==CHANGED, the client will be notified when the return code + * is different with the return code of last execute of the operation + * when target_rc is other value, only when the return code is the same of + * target_rc, the client will be notified. + */ + +typedef struct{ + /*input fields*/ + char* op_type; + GHashTable* params; + int timeout; + char* user_data; + int user_data_len; + int interval; + int start_delay; + int copyparams; /* copy parameters to the rsc */ + int target_rc; + + /*output fields*/ + op_status_t op_status; + int rc; + int call_id; + char* output; + char* rsc_id; + char* app_name; + char* fail_reason; + unsigned long t_run; /* when did the op run (as age) */ + unsigned long t_rcchange; /* last rc change (as age) */ + unsigned long exec_time; /* time it took the op to run */ + unsigned long queue_time; /* time spent in queue */ + int rsc_deleted; /* resource just deleted? */ +}lrm_op_t; + +extern const lrm_op_t lrm_zero_op; /* an all-zeroes lrm_op_t value */ + +lrm_op_t* lrm_op_new(void); +void lrm_free_op(lrm_op_t* op); +void lrm_free_rsc(lrm_rsc_t* rsc); +void lrm_free_str_list(GList* list); +void lrm_free_op_list(GList* list); +void lrm_free_str_table(GHashTable* table); + + +/*this enum is used in get_cur_state*/ +typedef enum { + LRM_RSC_IDLE, + LRM_RSC_BUSY +}state_flag_t; + +/* defaults for the asynchronous resource failures */ +enum { DEFAULT_FAIL_RC = EXECRA_UNKNOWN_ERROR }; +#define DEFAULT_FAIL_REASON "asynchronous monitor error" +#define ASYNC_OP_NAME "asyncmon" + +/* in addition to HA_OK and HA_FAIL */ +#define HA_RSCBUSY 2 + +struct rsc_ops +{ +/* + *perform_op: Performs the operation on the resource. + *Notice: op is the operation which need to pass to RA and done asyn + * + *op: the structure of the operation. Caller can create the op by + * lrm_op_new() and release the op using lrm_free_op() + * + *return: All operations will be asynchronous. + * The call will return the call id or failed code immediately. + * The call id will be passed to the callback function + * when the operation finished later. + */ + int (*perform_op) (lrm_rsc_t*, lrm_op_t* op); + + +/* + *cancel_op: cancel the operation on the resource. + * + *callid: the call id returned by perform_op() + * + *return: HA_OK for success, HA_FAIL for failure op not found + * or other failure + * NB: the client always gets a notification over callback + * even for operations which were idle (of course, if + * the request has been accepted for processing) + */ + int (*cancel_op) (lrm_rsc_t*, int call_id); + +/* + *flush_ops: throw away all operations queued for this resource, + * and return them as cancelled. + * + *return: HA_OK for success, HA_FAIL for failure + * NB: op is not flushed unless it is idle; + * in that case this call will block + */ + int (*flush_ops) (lrm_rsc_t*); + +/* + *get_cur_state: + * return the current state of the resource + * + *cur_state: current state of the resource + * + *return: cur_state should be in LRM_RSC_IDLE or LRM_RSC_BUSY. + * and the function returns a list of ops. + * the list includes: + * 1. last ops for each type (start/stop/etc) from current client + * 2. current pending ops + * 3. all recurring ops waiting to execute + * the list is sorted by the call_id of ops. + * client can release the list using lrm_free_op_list() + */ + GList* (*get_cur_state) (lrm_rsc_t*, state_flag_t* cur_state); + +/* + *get_last_result: + * return the last op of given type from current client + * + *op_type: the given type + * + *return: the last op. if there is no such op, return NULL. + * client can release the op using lrm_free_op() + */ + lrm_op_t* (*get_last_result)(lrm_rsc_t*, const char *op_type); +}; + + +/* + *lrm_op_done_callback_t: + * this type of callback functions are called when some + * asynchronous operation is done. + * client can release op by lrm_free_op() + */ +typedef void (*lrm_op_done_callback_t) (lrm_op_t* op); + + +typedef struct ll_lrm +{ + struct lrm_ops* lrm_ops; +}ll_lrm_t; + +struct lrm_ops +{ + int (*signon) (ll_lrm_t*, const char * app_name); + + int (*signoff) (ll_lrm_t*); + + int (*delete) (ll_lrm_t*); + + int (*set_lrm_callback) (ll_lrm_t*, + lrm_op_done_callback_t op_done_callback_func); + +/* + *set_lrmd_param: set lrmd parameter + *get_lrmd_param: get lrmd parameter + * + *return: HA_OK for success, HA_FAIL for failure + * NB: currently used only for max_child_count + */ + int (*set_lrmd_param)(ll_lrm_t*, const char *name, const char *value); + char* (*get_lrmd_param)(ll_lrm_t*, const char *name); + +/* + int (*set_parameters)(ll_lrm_t*, const GHashTable* option); + + GHashTable* (*get_all_parameters)(ll_lrm_t*); + + char * (*get_parameter)(ll_lrm_t *, const char * paramname); + + char * (*get_parameter_description)(ll_lrm_t*); +*/ + +/* + *get_rsc_class_supported: + * Returns the resource classes supported. + * e.g. ocf, heartbeat,lsb... + * + *return: a list of the names of supported resource classes. + * caller can release the list by lrm_free_str_list(). + */ + GList* (*get_rsc_class_supported)(ll_lrm_t*); + +/* + *get_rsc_type_supported: + * Returns the resource types supported of class rsc_class. + * e.g. drdb, apache,IPaddr... + * + *return: a list of the names of supported resource types. + * caller can release the list by lrm_free_str_list(). + */ + GList* (*get_rsc_type_supported)(ll_lrm_t*, const char* rsc_class); + +/* + *get_rsc_provider_supported: + * Returns the provider list of the given resource types + * e.g. heartbeat, failsafe... + * + *rsc_provider: if it is null, the default one will used. + * + *return: a list of the names of supported resource provider. + * caller can release the list by lrm_free_str_list(). + */ + GList* (*get_rsc_provider_supported)(ll_lrm_t*, + const char* rsc_class, const char* rsc_type); + +/* + *get_rsc_type_metadata: + * Returns the metadata of the resource type + * + *rsc_provider: if it is null, the default one will used. + * + *return: the metadata. use g_free() to free. + * + */ + char* (*get_rsc_type_metadata)(ll_lrm_t*, const char* rsc_class, + const char* rsc_type, const char* rsc_provider); + +/* + *get_all_type_metadatas: + * Returns all the metadata of the resource type of the class + * + *return: A GHashtable, the key is the RA type, + * the value is the metadata. + * Now only default RA's metadata will be returned. + * please use lrm_free_str_table() to free the return value. + */ + GHashTable* (*get_all_type_metadata)(ll_lrm_t*, const char* rsc_class); + +/* + *get_all_rscs: + * Returns all resources. + * + *return: a list of id of resources. + * caller can release the list by lrm_free_str_list(). + */ + GList* (*get_all_rscs)(ll_lrm_t*); + + +/* + *get_rsc: Gets one resource pointer by the id + * + *return: the lrm_rsc_t type pointer, NULL for failure + * caller can release the pointer by lrm_free_rsc(). + */ + lrm_rsc_t* (*get_rsc)(ll_lrm_t*, const char* rsc_id); + +/* + *add_rsc: Adds a new resource to lrm. + * lrmd holds nothing when it starts. + * crm or lrmadmin should add resources to lrm using + * this function. + * + *rsc_id: An id which sould be generated by client, + * 128byte(include '\0') UTF8 string + * + *class: the class of the resource + * + *type: the type of the resource. + * + *rsc_provider: if it is null, the default provider will used. + * + *params: the parameters for the resource. + * + *return: HA_OK for success, HA_FAIL for failure + */ + int (*add_rsc)(ll_lrm_t*, const char* rsc_id, const char* class, + const char* type, const char* provider, GHashTable* params); + +/* + *delete_rsc: delete the resource by the rsc_id + * + *return: HA_OK for success, HA_FAIL for failure + * NB: resource removal is delayed until all operations are + * removed; the client, however, gets the reply immediately + */ + int (*delete_rsc)(ll_lrm_t*, const char* rsc_id); + +/* + *fail_rsc: fail a resource + * Allow asynchronous monitor failures. Notifies all clients + * which have operations defined for the resource. + * The fail_rc parameter should be set to one of the OCF + * return codes (if non-positive it defaults to + * OCF_ERR_GENERIC). The fail_reason parameter should + * contain the description of the failure (i.e. "daemon + * panicked" or similar). If NULL is passed or empty string, + * it defaults to "asynchronous monitor failure". + * + *return: HA_OK for success, HA_FAIL for failure + */ + int (*fail_rsc)(ll_lrm_t* lrm, const char* rsc_id, + const int fail_rc, const char* fail_reason); +/* + *ipcchan: Return the IPC channel which can be used for determining + * when messages are ready to be read. + *return: the IPC Channel + */ + + IPC_Channel* (*ipcchan)(ll_lrm_t*); + +/* + *msgready: Returns TRUE (1) when a message is ready to be read. + */ + gboolean (*msgready)(ll_lrm_t*); + +/* + *rcvmsg: Cause the next message to be read - activating callbacks for + * processing the message. If no callback processes the message + * it will be ignored. The message is automatically disposed of. + * + *return: the count of message was received. + */ + int (*rcvmsg)(ll_lrm_t*, int blocking); + +}; + +/* + *ll_lrm_new: + * initializes the lrm client library. + * + *llctype: "lrm" + * + */ +ll_lrm_t* ll_lrm_new(const char * llctype); + +/* + *execra_code2string: + * Translate the return code of the operation to string + * + *code: the rc field in lrm_op_t structure + */ +const char *execra_code2string(uniform_ret_execra_t code); +#endif /* __LRM_API_H */ + diff --git a/include/lrm/lrm_msg.h b/include/lrm/lrm_msg.h new file mode 100644 index 0000000..6f671e1 --- /dev/null +++ b/include/lrm/lrm_msg.h @@ -0,0 +1,160 @@ +/* + * Message Define For Local Resource Manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * By Huang Zhen 2004/2/23 + * + */ +/* + * Notice: + *"status" indicates the exit status code of "status" operation + * its value is defined in LSB + *"state" indicates the state of resource, maybe LRM_RSC_BUSY, LRM_RSC_IDLE + *"opstate" indicates how the op exit.like LRM_OP_DONE,LRM_OP_CANCELLED, + * LRM_OP_TIMEOUT,LRM_OP_NOTSUPPORTED. + */ +#ifndef __LRM_MSG_H +#define __LRM_MSG_H 1 + +#include + +#define LRM_CMDPATH HA_VARRUNDIR"/heartbeat/lrm_cmd_sock" +#define LRM_CALLBACKPATH HA_VARRUNDIR"/heartbeat/lrm_callback_sock" + +/*define the field type used by lrm*/ +#define F_LRM_TYPE "lrm_t" +#define F_LRM_APP "lrm_app" +#define F_LRM_PID "lrm_pid" +#define F_LRM_UID "lrm_uid" +#define F_LRM_GID "lrm_gid" +#define F_LRM_RID "lrm_rid" +#define F_LRM_RTYPE "lrm_rtype" +#define F_LRM_RTYPES "lrm_rtypes" +#define F_LRM_RCLASS "lrm_rclass" +#define F_LRM_RPROVIDER "lrm_rprovider" +#define F_LRM_RPROVIDERS "lrm_rproviders" +#define F_LRM_PARAM "lrm_param" +#define F_LRM_COPYPARAMS "lrm_copyparams" +#define F_LRM_TIMEOUT "lrm_timeout" +#define F_LRM_OP "lrm_op" +#define F_LRM_OPCNT "lrm_opcount" +#define F_LRM_OPSTATUS "lrm_opstatus" +#define F_LRM_RC "lrm_rc" +#define F_LRM_RET "lrm_ret" +#define F_LRM_CALLID "lrm_callid" +#define F_LRM_RCOUNT "lrm_rcount" +#define F_LRM_RIDS "lrm_rids" +#define F_LRM_DATALEN "lrm_datalen" +#define F_LRM_DATA "lrm_data" +#define F_LRM_STATE "lrm_state" +#define F_LRM_INTERVAL "lrm_interval" +#define F_LRM_TARGETRC "lrm_targetrc" +#define F_LRM_LASTRC "lrm_lastrc" +#define F_LRM_STATUS "lrm_status" +#define F_LRM_RSCDELETED "lrm_rscdeleted" +#define F_LRM_METADATA "lrm_metadata" +#define F_LRM_USERDATA "lrm_userdata" +#define F_LRM_DELAY "lrm_delay" +#define F_LRM_T_RUN "lrm_t_run" +#define F_LRM_T_RCCHANGE "lrm_t_rcchange" +#define F_LRM_EXEC_TIME "lrm_exec_time" +#define F_LRM_QUEUE_TIME "lrm_queue_time" +#define F_LRM_FAIL_REASON "lrm_fail_reason" +#define F_LRM_ASYNCMON_RC "lrm_asyncmon_rc" +#define F_LRM_LRMD_PARAM_NAME "lrm_lrmd_param_name" +#define F_LRM_LRMD_PARAM_VAL "lrm_lrmd_param_val" + +#define PRINT printf("file:%s,line:%d\n",__FILE__,__LINE__); + + +/*define the message typs between lrmd and client lib*/ +#define REGISTER "reg" +#define GETRSCCLASSES "rclasses" +#define GETRSCTYPES "rtypes" +#define GETPROVIDERS "rproviders" +#define GETRSCMETA "rmetadata" +#define GETALLRCSES "getall" +#define GETRSC "getrsc" +#define GETLASTOP "getlastop" +#define GETRSCSTATE "getstate" +#define SETMONITOR "setmon" +#define GETMONITORS "getmons" +#define FLUSHRSC "flush" +#define ADDRSC "addrsc" +#define DELRSC "delrsc" +#define FAILRSC "failrsc" +#define PERFORMOP "op" +#define ISOPSUPPORT "opspt" +#define OPDONE "opdone" +#define MONITOR "monitor" +#define RETURN "return" +#define FLUSHOPS "flushops" +#define CANCELOP "cancelop" +#define SETLRMDPARAM "setparam" +#define GETLRMDPARAM "getparam" + +#define MAX_INT_LEN 64 +#define MAX_NAME_LEN 255 +#define MAX_VALUE_LEN 255 +#define MAX_PARAM_LEN 1024 + + +GHashTable* copy_str_table(GHashTable* hash_table); +GHashTable* merge_str_tables(GHashTable* old, GHashTable* new); +void free_str_table(GHashTable* hash_table); + + /* + * message for no parameter, like unreg,types,getall + * they do not include any paramters + */ +struct ha_msg* create_lrm_msg(const char* msg); + +/* + * message for only one parameter - resource id, + * like getrsc,delrsc,flush,getstate,getmons + */ +struct ha_msg* create_lrm_rsc_msg(const char* rid, const char* msg); + +/* register client message */ +struct ha_msg* create_lrm_reg_msg(const char* app_name); + +/* + * add new resource + * according to the opinion of Lars, it is awkward that we combine all + * parameters in to one string. I think so too. So this call may changed soon + */ +struct ha_msg* create_lrm_addrsc_msg(const char* rid, const char* class, + const char* type, const char* provider, GHashTable* parameter); + +/* + * + *the return message from lrmd for reg,unreg,addrsc,delrsc,isopsupport. + *these return messages only include return code. + * + */ +struct ha_msg* create_lrm_ret(int rc, int fields); + + +/* + * the return message for a status change monitoring. + */ + +struct ha_msg* create_rsc_perform_op_msg (const char* rid, lrm_op_t* op); + +#endif /* __LRM_MSG_H */ diff --git a/include/lrm/racommon.h b/include/lrm/racommon.h new file mode 100644 index 0000000..b16aa24 --- /dev/null +++ b/include/lrm/racommon.h @@ -0,0 +1,29 @@ +/* + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef RACOMMON_H +#define RACOMMON_H + +void get_ra_pathname(const char* class_path, const char* type, const char* provider, char pathname[]); +gboolean filtered(char * file_name); +int get_runnable_list(const char* class_path, GList ** rsc_info); +int get_failed_exec_rc(void); +void closefiles(void); + +#endif /* RACOMMON_H */ diff --git a/include/lrm/raexec.h b/include/lrm/raexec.h new file mode 100644 index 0000000..0b69831 --- /dev/null +++ b/include/lrm/raexec.h @@ -0,0 +1,157 @@ +/* + * raexec.h: The universal interface of RA Execution Plugin + * + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef RAEXEC_H +#define RAEXEC_H +#include +#include + +/* Uniform return value of executing RA */ +enum UNIFORM_RET_EXECRA { + EXECRA_EXEC_UNKNOWN_ERROR = -2, + EXECRA_NO_RA = -1, + EXECRA_OK = 0, + EXECRA_UNKNOWN_ERROR = 1, + EXECRA_INVALID_PARAM = 2, + EXECRA_UNIMPLEMENT_FEATURE = 3, + EXECRA_INSUFFICIENT_PRIV = 4, + EXECRA_NOT_INSTALLED = 5, + EXECRA_NOT_CONFIGURED = 6, + EXECRA_NOT_RUNNING = 7, + EXECRA_RUNNING_MASTER = 8, + EXECRA_FAILED_MASTER = 9, + + /* For status command only */ + EXECRA_RA_DEAMON_DEAD1 = 11, + EXECRA_RA_DEAMON_DEAD2 = 12, + EXECRA_RA_DEAMON_STOPPED = 13, + EXECRA_STATUS_UNKNOWN = 14 +}; +typedef enum UNIFORM_RET_EXECRA uniform_ret_execra_t; + +#define RA_MAX_NAME_LENGTH 240 +#define RA_MAX_DIRNAME_LENGTH 200 +#define RA_MAX_BASENAME_LENGTH 40 + +/* + * RA Execution Interfaces + * The plugin usage is divided into two step. First to send out a command to + * execute a resource agent via calling function execra. Execra is a unblock + * function, always return at once. Then to call function post_query_result to + * get the RA exection result. +*/ +struct RAExecOps { + /* + * Description: + * Launch a exection of a resource agent -- normally is a script + * + * Parameters: + * rsc_id: The resource instance id. + * rsc_type: The basename of a RA. + * op_type: The operation that hope RA to do, such as "start", + * "stop" and so on. + * cmd_params: The command line parameters need to be passed to + * the RA for a execution. + * env_params: The enviroment parameters need to be set for + * affecting the action of a RA execution. As for + * OCF style RA, it's the only way to pass + * parameter to the RA. + * + * Return Value: + * 0: RA execution is ok, while the exec_key is a valid value. + * -1: The RA don't exist. + * -2: There are invalid command line parameters. + * -3: Other unkown error when launching the execution. + */ + int (*execra)( + const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + + /* + * Description: + * Map the specific ret value to a uniform value. + * + * Parameters: + * ret_execra: the RA type specific ret value. + * op_type: the operation type + * std_output: the output which the RA write to stdout. + * + * Return Value: + * A uniform value without regarding RA type. + */ + uniform_ret_execra_t (*map_ra_retvalue)( + int ret_execra + , const char * op_type + , const char * std_output); + + /* + * Description: + * List all resource info of this class + * + * Parameters: + * rsc_info: a GList which item data type is rsc_info_t as + * defined above, containing all resource info of + * this class in the local machine. + * + * Return Value: + * >=0 : succeed. the RA type number of this RA class + * -1: failed due to invalid RA directory such as not existing. + * -2: failed due to other factors + */ + int (*get_resource_list)(GList ** rsc_info); + + /* + * Description: + * List all providers of this type + * + * Parameters: + * providers: a GList which item data type is string. + * the name of providers of the resource agent + * + * Return Value: + * >=0 : succeed. the provider number of this RA + * -1: failed due to invalid RA directory such as not existing. + * -2: failed due to other factors + */ + int (*get_provider_list)(const char* ra_type, GList ** providers); + + /* + * Description: + * List the metadata of the resource agent this class + * + * Parameters: + * rsc_type: the type of the ra + * + * Return Value: + * !NULL : succeed. the RA metadata. + * NULL: failed + */ + char* (*get_resource_meta)(const char* rsc_type, const char* provider); +}; + +#define RA_EXEC_TYPE RAExec +#define RA_EXEC_TYPE_S "RAExec" + +#endif /* RAEXEC_H */ diff --git a/include/pils/Makefile.am b/include/pils/Makefile.am new file mode 100644 index 0000000..4b6c6a0 --- /dev/null +++ b/include/pils/Makefile.am @@ -0,0 +1,25 @@ +# +# linux-ha: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# This instance created by Horms +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +idir=$(includedir)/pils + +i_HEADERS = generic.h interface.h plugin.h diff --git a/include/pils/generic.h b/include/pils/generic.h new file mode 100644 index 0000000..83bf3e3 --- /dev/null +++ b/include/pils/generic.h @@ -0,0 +1,118 @@ +#ifndef PILS_GENERIC_H +#define PILS_GENERIC_H +/* + * Copyright (C) 2000 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Generic interface (implementation) manager + * + * This manager will manage any number of types of interfaces. + * + * This means that when any implementations of our client interfaces register + * or unregister, it is us that makes their interfaces show up in the outside + * world. + * + * And, of course, we have to do this in a very generic way, since we have + * no idea about the client programs or interface types, or anything else. + * + * We do that by getting a parameter passed to us which tell us the names + * of the interface types we want to manage, and the address of a GHashTable + * for each type that we put the implementation in when they register + * themselves. + * + * So, each type of interface that we manage gets its own private + * GHashTable of the implementations of that type that are currently + * registered. + * + * For example, if we manage communication modules, their exported + * interfaces will be registered in a hash table. If we manage + * authentication modules, they'll have their (separate) hash table that + * their exported interfaces are registered in. + * + */ +#include + +/* + * Header defintions for using the generic interface/implementation + * manager plugin. + */ + +/* + * Notification types for the callback function. + */ +typedef enum { + PIL_REGISTER, /* Someone has registered an implementation */ + PIL_UNREGISTER /* Someone has unregistered an implementation */ +}GenericPILCallbackType; + +/* A user callback for the generic interface manager */ +typedef int (*GenericPILCallback) +( GenericPILCallbackType type /* Event type */ +, PILPluginUniv* univ /* pointer to plugin universe */ +, const char * iftype /* Interface type */ +, const char * ifname /* Implementation (interface) name */ +, void * userptr /* Whatever you want it to be ;-) */ +); + +/* + * Structures to declare the set of interface types we're managing. + */ +typedef struct { + const char * iftype; /* What type of interface is this? */ + GHashTable** ifmap; /* Table with implementation info */ + void* importfuns; /* Functions for interface to import */ + GenericPILCallback callback; /* Function2call when events occur */ + void* userptr; /* Passed to Callback function */ +}PILGenericIfMgmtRqst; +/* + * What does this look like in practice? + * + * GHashTable* authmodules = NULL; + * GHashTable* commmodules = NULL; + * PILGenericIfMgmtRqst RegisterRequests[] = + * { + * {"auth", &authmodules, &authimports, NULL, NULL}, + * {"comm", &commmodules, &commimports, NULL, NULL}, + * {NULL, NULL, NULL, NULL, NULL} + // NULL entry must be here + * }; + * + * PILPlugin* PluginUniverse; + * + * PluginUniverse = NewPILPlugin("/usr/lib/whatever/plugins"); + * + * PILLoadPlugin(PluginUniverse, "InterfaceMgr", "generic", &RegisterRequests); + * // N. B.: Passing RegisterRequests as an argument is essential + * + * Then, when you load an auth module, its exported interface gets added + * to "authmodules". When you unload an auth module, it gets removed + * from authmodules. + * + * Then, when you load a comm module, its exported interfaces gets added + * to "commodules". When you unload a comm module, its exported + * interfaces get removed from "commodules" + * + * If there are simple changes that would be useful for this generic + * plugin manager, then "patches are being accepted" :-) + * + * On the other hand, If you don't like the way this plugin manager works + * in a broader way, you're free to write your own - it's just another + * plugin ;-) + */ +#endif diff --git a/include/pils/interface.h b/include/pils/interface.h new file mode 100644 index 0000000..5a5114e --- /dev/null +++ b/include/pils/interface.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2000 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef PILS_INTERFACE_H +# define PILS_INTERFACE_H +# ifndef PILS_PLUGIN_H +# include +# endif + +/***************************************************************************** + * + * The most basic interface type is the "IFManager" interface. + * Each interface manager registers and deals with interfaces of a given type. + * + * Such an interface must be loaded before any plugins of it's type can + * be loaded. + * + * In order to register any plugin of type "foo", we must load a interface of + * type "Interface" named "foo". This interface then manages the + * registration of all interfaces of type foo. + * + * To bootstrap, we load a interface of type "Interface" named "Interface" + * during the initialization of the plugin system. + * + * IFManagers will be autoloaded if certain conditions are met... + * + * If a IFManager is to be autoloaded, there must be one interface manager + * per file, and the file must be named according to the type of the + * interface it implements, and loaded in the directory named PI_IFMANAGER + * ("Interface"). + * + */ + + +/* + * I'm unsure exactly which of the following structures + * are needed to write a interface, or a interface manager. + * We'll get that figured out and scope the defintions accordingly... + */ + +/* + * PILInterface (AKA struct PILInterface_s) holds the information + * we use to track a single interface manager. + */ + + +struct PILInterface_s { + unsigned long MagicNum; + PILInterfaceType* interfacetype; /* Parent pointer */ + char * interfacename; /* malloced interface name */ + PILInterface* ifmanager; /* plugin managing us */ + void* exports; /* Exported Functions */ + /* for this interface */ + PILInterfaceFun if_close; /* Interface close operation*/ + void* ud_interface; /* per-interface user data */ + int refcnt; /* Ref count for plugin */ + PILPlugin* loadingpi; /* Plugin that loaded us */ +}; +/* + * PILInterfaceType (AKA struct PILInterfaceType_s) holds the info + * we use to track the set of all interfaces of a single kind. + */ +struct PILInterfaceType_s { + unsigned long MagicNum; + char* typename; /* Our interface type name */ + GHashTable* interfaces; /* The set of interfaces + * of our type. The + * "values" are all + * PILInterface * objects + */ + void* ud_if_type; /* per-interface-type user + data*/ + PILInterfaceUniv* universe; /* Pointer to parent (up) */ + PILInterface* ifmgr_ref; /* Pointer to our interface + manager */ +}; + +/* + * PILInterfaceUniv (AKA struct PILInterfaceUniv_s) holds the information + * for all interfaces of all types. From our point of view this is + * our universe ;-) + */ + +struct PILInterfaceUniv_s{ + unsigned long MagicNum; + GHashTable* iftypes; /* + * Set of Interface Types + * The values are all + * PILInterfaceType objects + */ + struct PILPluginUniv_s* piuniv; /* parallel universe of + * plugins + */ +}; + +#ifdef ENABLE_PLUGIN_MANAGER_PRIVATE +/* + * From here to the end is specific to interface managers. + * This data is only needed by interface managers, and the interface + * management system itself. + * + */ +typedef struct PILInterfaceOps_s PILInterfaceOps; + + +/* Interfaces imported by a IFManager interface */ +struct PILInterfaceImports_s { + + /* Return current reference count */ + int (*RefCount)(PILInterface * eifinfo); + + /* Incr/Decr reference count */ + int (*ModRefCount)(PILInterface*eifinfo, int plusminus); + + /* Unregister us as a interface */ + void (*ForceUnRegister)(PILInterface *eifinfo); + + /* For each client */ + void (*ForEachClientDel)(PILInterface* manangerif + , gboolean(*f)(PILInterface* clientif, void * other) + , void* other); + +}; + +/* Interfaces exported by an InterfaceManager interface */ +struct PILInterfaceOps_s{ +/* + * These are the interfaces exported by an InterfaceManager to the + * interface management infrastructure. These are not imported + * by interfaces - only the interface management infrastructure. + */ + + /* RegisterInterface - register this interface */ + PIL_rc (*RegisterInterface)(PILInterface* newif + , void** imports); + + PIL_rc (*UnRegisterInterface)(PILInterface*ifinfo); /* Unregister IF*/ + /* And destroy PILInterface object */ +}; + +#endif /* ENABLE_PLUGIN_MANAGER_PRIVATE */ +#endif /* PILS_INTERFACE_H */ diff --git a/include/pils/plugin.h.in b/include/pils/plugin.h.in new file mode 100644 index 0000000..cb67d91 --- /dev/null +++ b/include/pils/plugin.h.in @@ -0,0 +1,736 @@ +/* + * Copyright (C) 2000 Alan Robertson + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PILS_PLUGIN_H +# define PILS_PLUGIN_H +# include +# include + +/* Glib headers generate warnings - so we make them go away */ + +#define time FOOtime +#define index FOOindex +#include +#undef index +#undef time + +/***************************************************************************** + * PILS - Universal Plugin and Interface loading system + ***************************************************************************** + * + * An Overview of PILS... + * + * PILS is fairly general and reasonably interesting plugin loading system. + * We manage both plugins and their interfaces + * + * This plugin / interface management system is quite general, and should be + * directly usable by basically any project on any platform on which it runs + * - which should be many, since everything is build with automake + * and libtool. + * + * Some terminology... + * + * There are two basic kinds of objects we deal with here: + * + * Plugins: dynamically loaded chunks of code which implement one or more + * interfaces. The system treats all plugins as the same. + * In UNIX, these are dynamically loaded ".so" files. + * + * Interface: A set of functions which implement a particular capability + * (or interface) + * Generally interfaces are registered as part of a plugin. + * The system treats all interfaces of the same type the same. + * It is common to have exactly one interface inside of each plugin. + * In this case, the interface name should match the plugin name. + * + * Each interface implementation exports certain functions for its clients + * to use. We refer to these those "Ops". Every interface of the same type + * "imports" the same interfaces from its interface manager, + * and exports the same "Ops". + * + * Each interface implementation is provided certain interfaces which it + * imports when it from its interface manager when it is registered. + * We refer to these as "Imports". Every interface of a given type + * imports the same interfaces. + * + * The story with plugins is a little different... + * + * Every plugin exports a certain set of interfaces, regardless of what type + * of interfaces is implemented by it. These are described in the + * PILPluginOps structure. + * + * Every plugin imports a certain set of interfaces, regardless of what type + * of interfaces it may implement. These are described by the + * PILPluginImports structure. + * + * In the function parameters below, the following notation will + * sometimes appear: + * + * (OP) == Output Parameter - a parameter which is modified by the + * function being called + * + * + ***************************************************************************** + * + * The basic structures we maintain about plugins are as follows: + * + * PILPlugin The data which represents a plugin. + * PILPluginType The data common to all plugins of a given type + * PILPluginUniv The set of all plugin types in the Universe + * (well... at least *this* universe) + * + * The basic structures we maintain about interfaces are as follows: + * PILInterface The data which represents a interface + * PILInterfaceType The data which is common to all + * interfaces of a given type + * PILPluginUniv The set of all interface types in the Universe + * (well... at least *this* universe) + * + * Regarding "Universe"s. It is our intent that a given program can deal + * with plugins in more than one universe. This might occur if you have two + * independent libraries each of which uses the plugin loading environment + * to manage their own independent interface components. There should be + * no restriction in creating a program which uses both of these libraries. + * At least that's what we hope ;-) + * + * + *************************************************************************** + * SOME MORE DETAILS ABOUT PLUGINS... + *************************************************************************** + * + * Going back to more detailed data structures about plugins... + * + * PILPluginImports The set of standard functions all plugins + * import. + * This includes: + * register_plugin() + * unregister_plugin() + * register_interface() + * unregister_interface() + * load_plugin() + * log() Preferred logging function + * + * PILPluginOps The set of standard operations all plugins + * export. + * This includes: + * pluginversion() + * pluginname() + * getdebuglevel() + * setdebuglevel() + * close() Prepare for unloading... + * + * Although we treat plugins pretty much the same, they are still + * categorized into "types" - one type per directory. These types + * generally correspond to interface types. + * + * One can only cause a plugin to be loaded - not a interface. But it is + * common to assume that loading a plugin named foo of type bar will + * cause a interface named foo of type bar to be registered. If one + * wants to implement automatic plugin loading in a given interface type, + * this assumption is necessary. + * + * The general way this works is... + * + * - A request is made to load a particular plugin of a particular type. + * + * - The plugin is loaded from the appropriate directory for plugins + * of that type. + * + * - The ml_plugin_init() function is called once when the plugin is + * loaded. + * + * The ml_plugin_init() function is passed a vector of functions which + * point to functions it can call to register itself, etc. + * (it's of type PILPluginImports) + * + * The ml_plugin_init function then uses this set of imported functions + * to register itself and its interfaces. + * + * The mechanism of registering a interface is largely the same for + * every interface. However, the semantics of registering a interfaces + * is determined by the interface manager for the particular type of + * interface being discussed. + * + *************************************************************************** + * SOME MORE DETAILS ABOUT PLUGINS... + *************************************************************************** + * + * There is only one built in type of interface. That's the Interface + * manager interface. + * The interface manager for the interface of type "InterfaceMgr", + * named "InterfaceMgr" inserts itself into the system in order + * to bootstrap things... + * + * When an attempt is made to register a interface of an unknown type, + * then the appropriate Interface manager is loaded automatically. + * + * The name of an interface manager determines the type of + * interface it manages. + * + * It handles requests for interfaces whose type is the same + * as its interface name. If the interface manager's interface name + * is foo, then it is the interface manager for all interfaces whose + * type is foo. + * + * Types associated with interfaces of type Interface + * + * PILInterfaceOps The set of interfaces that every interface + * manager exports + * PILInterfaceImports The set of interfaces which are supplied to + * (imported by) every interface of type + * Interface. (that is, every interface + * manager). + * + ***************************************************************************** + * + * Each plugin has only one entry point which is exported directly, regardless + * of what kind of interface(s) it may implement... + * + * This entrypoint is named ml_plugin_init() {more or less - see below} + * + * The ml_plugin_init() function is called once when the plugin is loaded. + * + * + * All other function pointers are registered (exported) through parameters + * passed to ml_plugin_init() + * + * It is the purpose of the Ml_plugin_init() to register the plugin, + * and all the interfaces which this plugin implements. A pointer to + * the registration function is in the parameters which are passed + * to ml_plugin_init(). + * + ***************************************************************************** + * + * THINGS IN THIS DESIGN WHICH ARE PROBABLY BROKEN... + * + * It may also be the case that the plugin loading environment needs + * to be able to have some kind of user_data passed to it which it can + * also pass along to any interface ... + * + * Maybe this should be handled by a sort of global user_data registration + * structure, so globals can be passed to interfaces when they're registered. + * + * A sort of "user_data" registry. One for each interface type and one + * for each interface... Or maybe it could be even more flexible... + * + * This is all so that these nice pristene, beautiful concepts can come out + * and work well in the real world where interfaces need to interact with + * some kind of global system view, and with each other... + * + * Probably need some better way of managing interface versions, etc. + * + **************************************************************************** + */ + +/* + * If you want to use this funky export stuff, then you need to #define + * PIL_PLUGINTYPE and PIL_PLUGIN *before* including this file. + * + * The way to use this stuff is to declare your primary entry point this way: + * + * This example is for an plugin of type "auth" named "sha1" + * + * #define PIL_PLUGINTYPE auth + * #define PIL_PLUGIN sha1 + * #include + * + * static const char* Ourpluginversion (void); + * static const char* Ourpluginname (void); + * static int Ourgetdebuglevel(void); + * static void Oursetdebuglevel(int); + * static void Ourclose (PILPlugin*); + * + * static struct PILPluginOps our_exported_plugin_operations = + * { Ourpluginversion, + * , Ourpluginname + * , Ourgetdebuglevel + * , Oursetdebuglevel + * , Ourclose + * }; + * + * static const PILPluginImports* PluginOps; + * static PILPlugin* OurPlugin; + * + * // Our plugin initialization and registration function + * // It gets called when the plugin gets loaded. + * PIL_rc + * PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) + * { + * PluginOps = imports; + * OurPlugin = us; + * + * // Register ourself as a plugin * / + * imports->register_plugin(us, &our_exported_plugin_operations); + * + * // Register our interfaces + * imports->register_interface(us, "interfacetype", "interfacename" + * // Be sure and define "OurExports" and OurImports + * // above... + * , &OurExports + * , &OurImports); + * // Repeat for all interfaces in this plugin... + * + * } + * + * Except for the PIL_PLUGINTYPE and the PIL_PLUGIN definitions, and changing + * the names of various static variables and functions, every single plugin is + * set up pretty much the same way + * + */ + +/* + * No doubt there is a fancy preprocessor trick for avoiding these + * duplications but I don't have time to figure it out. Patches are + * being accepted... + */ +#define mlINIT_FUNC _pil_plugin_init +#define mlINIT_FUNC_STR "_pil_plugin_init" +#define PIL_INSERT _LTX_ +#define PIL_INSERT_STR "_LTX_" + +/* + * snprintf-style format string for initialization entry point name: + * arguments are: (plugintype, pluginname) + */ +#define PIL_FUNC_FMT "%s" PIL_INSERT_STR "%s" mlINIT_FUNC_STR + +#ifdef __STDC__ +# define EXPORTHELPER1(plugintype, insert, pluginname, function) \ + plugintype##insert##pluginname##function +#else +# define EXPORTHELPER1(plugintype, insert, pluginname, function) \ + plugintype/**/insert/**/pluginname/**/function +#endif + +#define EXPORTHELPER2(a, b, c, d) EXPORTHELPER1(a, b, c, d) +#define PIL_PLUGIN_INIT \ + EXPORTHELPER2(PIL_PLUGINTYPE,PIL_INSERT,PIL_PLUGIN,mlINIT_FUNC) + +/* + * Plugin loading return codes. OK will always be zero. + * + * There are many ways to fail, but only one kind of success ;-) + */ + +typedef enum { + PIL_OK=0, /* Success */ + PIL_INVAL=1, /* Invalid Parameters */ + PIL_BADTYPE=2, /* Bad plugin/interface type */ + PIL_EXIST=3, /* Duplicate Plugin/Interface name */ + PIL_OOPS=4, /* Internal Error */ + PIL_NOPLUGIN=5 /* No such plugin or Interface */ +}PIL_rc; /* Return code from Plugin fns*/ + +const char * PIL_strerror(PIL_rc rc); + +typedef struct PILPluginImports_s PILPluginImports; +typedef struct PILPluginOps_s PILPluginOps; +typedef struct PILPlugin_s PILPlugin; +typedef struct PILPluginUniv_s PILPluginUniv; +typedef struct PILPluginType_s PILPluginType; + +typedef struct PILInterface_s PILInterface; +typedef struct PILInterfaceImports_s PILInterfaceImports; +typedef struct PILInterfaceUniv_s PILInterfaceUniv; +typedef struct PILInterfaceType_s PILInterfaceType; + +typedef PIL_rc(*PILInterfaceFun)(PILInterface*, void* ud_interface); + +#define PIL_MAGIC_PLUGIN 0xFEEDBEEFUL +#define PIL_MAGIC_PLUGINTYPE 0xFEEDCEEFUL +#define PIL_MAGIC_PLUGINUNIV 0xFEEDDEEFUL +#define PIL_MAGIC_INTERFACE 0xFEEDEEEFUL +#define PIL_MAGIC_INTERFACETYPE 0xFEEDFEEFUL +#define PIL_MAGIC_INTERFACEUNIV 0xFEED0EEFUL + +#define IS_PILPLUGIN(s) ((s)->MagicNum == PIL_MAGIC_PLUGIN) +#define IS_PILPLUGINTYPE(s) ((s)->MagicNum == PIL_MAGIC_PLUGINTYPE) +#define IS_PILPLUGINUNIV(s) ((s)->MagicNum == PIL_MAGIC_PLUGINUNIV) +#define IS_PILINTERFACE(s) ((s)->MagicNum == PIL_MAGIC_INTERFACE) +#define IS_PILINTERFACETYPE(s) ((s)->MagicNum == PIL_MAGIC_INTERFACETYPE) +#define IS_PILINTERFACEUNIV(s) ((s)->MagicNum == PIL_MAGIC_INTERFACEUNIV) + +/* The type of a Plugin Initialization Function */ +typedef PIL_rc (*PILPluginInitFun) (PILPlugin*us +, PILPluginImports* imports +, void* plugin_user_data); + +/* + * struct PILPluginOps_s (typedef PILPluginOps) defines the set of functions + * exported by all plugins... + */ +struct PILPluginOps_s { + const char* (*pluginversion) (void); + int (*getdebuglevel) (void); + void (*setdebuglevel) (int); + const char* (*license) (void); + const char* (*licenseurl) (void); + void (*close) (PILPlugin*); +}; + +/* + * Logging levels for the "standard" log interface. + */ + +typedef enum { + PIL_FATAL= 1, /* BOOM! Causes program to stop */ + PIL_CRIT = 2, /* Critical -- serious error */ + PIL_WARN = 3, /* Warning */ + PIL_INFO = 4, /* Informative message */ + PIL_DEBUG= 5 /* Debug message */ +}PILLogLevel; +typedef void (*PILLogFun)(PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(2,3); + +/* + * The size glib2 type du jour? + * (once, this used to be size_t, so this change could break + * distributions with older glib2 versions; if so, just add an + * #ifelse below) + */ +#if GLIB_MINOR_VERSION <= 14 + typedef gulong glib_size_t; +#else + typedef gsize glib_size_t; +#endif + +/* + * struct PILPluginImports_s (typedef PILPluginImports) defines + * the functions and capabilities that every plugin imports when it is loaded. + */ + + +struct PILPluginImports_s { + PIL_rc (*register_plugin)(PILPlugin* piinfo + , const PILPluginOps* commonops); + PIL_rc (*unregister_plugin)(PILPlugin* piinfo); +/* + * A little explanation of the close_func parameter to register_interface + * is in order. + * + * It is an exported operation function, just like the Ops structure. + * However, the Ops vector is exported to applications that + * are using the interface. Unlike the Ops structure, close_func is + * exported only to the interface system, since applications shouldn't + * call it directly, but should manage the reference counts for the + * interfaces instead. + * The generic interface system doesn't have any idea how to call + * any functions in the operations vector. So, it's a separate + * parameter for two good reasons. + */ + PIL_rc (*register_interface)(PILPlugin* piinfo + , const char * interfacetype /* Type of interface */ + , const char * interfacename /* Name of interface */ + , void* Ops /* Info (functions) exported + by this interface */ + /* Function to call to shut down this interface */ + , PILInterfaceFun close_func + + , PILInterface** interfaceid /* Interface id (OP) */ + , void** Imports + , void* ud_interface); /* interface user data */ + + PIL_rc (*unregister_interface)(PILInterface* interfaceid); + PIL_rc (*load_plugin)(PILPluginUniv* universe + , const char * plugintype, const char * pluginname + , void* plugin_private); + + void (*log) (PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(2,3); + gpointer (*alloc)(glib_size_t size); + gpointer (*mrealloc)(gpointer space, glib_size_t size); + void (*mfree)(gpointer space); + char* (*mstrdup)(const char *s); +}; + +/* + * Function for logging with the given logging function + * The reason why it's here is so we can get printf arg checking + * You can't get that when you call a function pointer directly. + */ +void PILCallLog(PILLogFun logfun, PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(3,4); + +/* + * EXPORTED INTERFACES... + */ + +/* Create a new plugin universe - start the plugin loading system up */ +PILPluginUniv* NewPILPluginUniv(const char * baseplugindirectory); + +/* Change memory allocation functions right after creating universe */ +void PilPluginUnivSetMemalloc(PILPluginUniv* +, gpointer (*alloc)(glib_size_t size) +, gpointer (*mrealloc)(gpointer, glib_size_t size) +, void (*mfree)(void* space) +, char* (*mstrdup)(const char *s)); + + +void PilPluginUnivSetLog(PILPluginUniv* +, void (*log) (PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(2,3)); + + +/* Delete a plugin universe - shut the plugin loading system down */ +/* Best if used carefully ;-) */ +void DelPILPluginUniv(PILPluginUniv*); + +/* Set the debug level for the plugin system itself */ +void PILpisysSetDebugLevel (int level); + +/* Return a list of plugins of the given type */ +char ** PILListPlugins(PILPluginUniv* u, const char *plugintype +, int* plugincount /*can be NULL*/); + +/* Free the plugin list returned by PILFreeListPlugins */ +void PILFreePluginList(char ** pluginlist); + +/* Load the requested plugin */ +PIL_rc PILLoadPlugin(PILPluginUniv* piuniv +, const char * plugintype +, const char * pluginname +, void * pi_private); + +/* Return PIL_OK if the given plugin exists */ +PIL_rc PILPluginExists(PILPluginUniv* piuniv +, const char * plugintype +, const char * pluginname); + +/* Either or both of pitype and piname may be NULL */ +void PILSetDebugLevel(PILPluginUniv*u, const char * pitype +, const char * piname +, int level); + +/* Neither pitype nor piname may be NULL */ +int PILGetDebugLevel(PILPluginUniv* u, const char * pitype +, const char * piname); + +PIL_rc PILIncrIFRefCount(PILPluginUniv* piuniv +, const char * interfacetype +, const char * interfacename +, int plusminus); + +int PILGetIFRefCount(PILPluginUniv* piuniv +, const char * interfacetype +, const char * interfacename); + +void PILLogMemStats(void); +/* The plugin/interface type of a interface manager */ + +#define PI_IFMANAGER "InterfaceMgr" +#define PI_IFMANAGER_TYPE InterfaceMgr + +/* + * These functions are standard exported functions for all plugins. + */ + +#define PIL_PLUGIN_BOILERPLATE_PROTOTYPES_GENERIC(PluginVersion, DebugName) \ +/* \ + * Prototypes for boilerplate functions \ + */ \ +static const char* Ourpluginversion(void); \ +static int GetOurDebugLevel(void); \ +static void SetOurDebugLevel(int); \ +static const char * ReturnOurLicense(void); \ +static const char * ReturnOurLicenseURL(void); + +#define PIL_PLUGIN_BOILERPLATE_FUNCS(PluginVersion, DebugName) \ +/* \ + * Definitions of boilerplate functions \ + */ \ +static const char* \ +Ourpluginversion(void) \ +{ return PluginVersion; } \ + \ +static int DebugName = 0; \ + \ +static int \ +GetOurDebugLevel(void) \ +{ return DebugName; } \ + \ +static void \ +SetOurDebugLevel(int level) \ +{ DebugName = level; } \ + \ +static const char * \ +ReturnOurLicense(void) \ +{ return PIL_PLUGINLICENSE; } \ + \ +static const char * \ +ReturnOurLicenseURL(void) \ +{ return PIL_PLUGINLICENSEURL; } + +#define PIL_PLUGIN_BOILERPLATE(PluginVersion, DebugName, CloseName) \ +PIL_PLUGIN_BOILERPLATE_PROTOTYPES_GENERIC(PluginVersion, DebugName) \ +static void CloseName(PILPlugin*); \ +/* \ + * Initialize Plugin Exports structure \ + */ \ +static PILPluginOps OurPIExports = \ +{ Ourpluginversion \ +, GetOurDebugLevel \ +, SetOurDebugLevel \ +, ReturnOurLicense \ +, ReturnOurLicenseURL \ +, CloseName \ +}; \ +PIL_PLUGIN_BOILERPLATE_FUNCS(PluginVersion, DebugName) + +#define PIL_PLUGIN_BOILERPLATE2(PluginVersion, DebugName) \ +PIL_PLUGIN_BOILERPLATE_PROTOTYPES_GENERIC(PluginVersion, DebugName) \ +/* \ + * Initialize Plugin Exports structure \ + */ \ +static PILPluginOps OurPIExports = \ +{ Ourpluginversion \ +, GetOurDebugLevel \ +, SetOurDebugLevel \ +, ReturnOurLicense \ +, ReturnOurLicenseURL \ +, NULL \ +}; \ +PIL_PLUGIN_BOILERPLATE_FUNCS(PluginVersion, DebugName) + + +/* A few sample licenses and URLs. We can easily add to this */ + +#define LICENSE_GPL "gpl" +#define URL_GPL "http://www.fsf.org/licenses/gpl.html" + +#define LICENSE_LGPL "lgpl" +#define URL_LGPL "http://www.fsf.org/licenses/lgpl.html" + +#define LICENSE_X11 "x11" +#define URL_X11 "http://www.x.org/terms.htm" + +#define LICENSE_PUBDOM "publicdomain" +#define URL_PUBDOM "file:///dev/null" + +#define LICENSE_MODBSD "modbsd" +#define URL_MODBSD "http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5" + +#define LICENSE_OLDBSD "origbsd" +#define URL_OLDBSD "http://www.xfree86.org/3.3.6/COPYRIGHT2.html#6" + +#define LICENSE_EXPAT "expat" +#define URL_EXPAT "http://www.jclark.com/xml/copying.txt" + +#define LICENSE_ZLIB "zlib" +#define URL_ZLIB "http://www.gzip.org/zlib/zlib_license.html" + +#define LICENSE_APACHE_10 "apache1_0" +#define URL_APACHE_10 "http://www.apache.org/LICENSE-1.0" + +#define LICENSE_APACHE_11 "apache1_1" +#define URL_APACHE_11 "http://www.apache.org/LICENSE-1.1" + +#define LICENSE_MPL "mpl" +#define URL_MPL "http://www.mozilla.org/MPL/MPL-1.1.html" + +#define LICENSE_PROP "proprietary" +#define URL_PROP "" + +#define LICENSE_IBMPL "ibmpl" +#define URL_IBMPL "http://oss.software.ibm.com/developerworks/opensource/license10.html" + +#ifdef ENABLE_PIL_DEFS_PRIVATE +/* Perhaps these should be moved to a different header file */ + +/* + * PILPluginType is the "class" for the basic plugin loading mechanism. + * + * To enable loading of plugins from a particular plugin type + * one calls NewPILPluginType with the plugin type name, the plugin + * base directory, and the set of functions to be imported to the plugin. + * + * + * The general idea of these structures is as follows: + * + * The PILPluginUniv object contains information about all plugins of + * all types. + * + * The PILPluginType object contains information about all the plugins of a + * specific type. + * + * Note: for plugins which implement a single interface, the plugin type name + * should be the same as the interface type name. + * + * For other plugins that implement more than one interface, one of + * the interface names should normally match the plugin name. + */ + + +/* + * struct PILPlugin_s (typedef PILPlugin) is the structure which + * represents/defines a plugin, and is used to identify which plugin is + * being referred to in various function calls. + * + * NOTE: It may be the case that this definition should be moved to + * another header file - since no one ought to be messing with them anyway ;-) + * + * I'm not sure that we're putting the right stuff in here, either... + */ + +struct PILPlugin_s { + unsigned long MagicNum; + char* plugin_name; + PILPluginType* plugintype; /* Parent structure */ + int refcnt; /* Reference count for this plugin */ + lt_dlhandle dlhandle; /* Reference to D.L. object */ + PILPluginInitFun dlinitfun; /* Initialization function */ + const PILPluginOps* pluginops; /* Exported plugin operations */ + + void* ud_plugin; /* Plugin-Private data */ + /* Other stuff goes here ... (?) */ +}; + +/* + * PILPluginType Information about all plugins of a given type. + * (i.e., in a given directory) + * (AKA struct PILPluginType_s) + */ + +struct PILPluginType_s { + unsigned long MagicNum; + char * plugintype; + PILPluginUniv* piuniv; /* The universe to which we belong */ + GHashTable* Plugins; + /* Key is plugin type, value is PILPlugin */ + + char** (*listplugins)(PILPluginType*, int* listlen); +}; + +/* + * PILPluginUniv (aka struct PILPluginUniv_s) is the structure which + * represents the universe of all PILPluginType objects. + * There is one PILPluginType object for each Plugin type. + */ + +struct PILPluginUniv_s { + unsigned long MagicNum; + char ** rootdirlist; + /* key is plugin type, data is PILPluginType* */ + GHashTable* PluginTypes; + struct PILInterfaceUniv_s*ifuniv; /* Universe of interfaces */ + PILPluginImports* imports; +}; + +# endif /* ENABLE_PIL_DEFS_PRIVATE */ +#endif /*PILS_PLUGIN_H */ diff --git a/include/replace_uuid.h b/include/replace_uuid.h new file mode 100644 index 0000000..d6bca89 --- /dev/null +++ b/include/replace_uuid.h @@ -0,0 +1,50 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * uuid: wrapper declarations. + * + * heartbeat originally used "uuid" functionality by calling directly, + * and only, onto the "e2fsprogs" implementation. + * + * The run-time usages in the code have since been abstracted, funnelled + * through a thin, common interface layer: a Good Thing. + * + * Similarly, the compile-time usages of "include " are + * replaced, being funnelled through a reference to this header file. + * + * This header file interfaces onto the actual underlying implementation. + * In the case of the "e2fsprogs" implementation, it is simply a stepping + * stone onto "". As other implementations are accommodated, + * so their header requirements can be accommodated here. + * + * Copyright (C) 2004 David Lee + */ + +#ifndef REPLACE_UUID_H +#define REPLACE_UUID_H + +typedef unsigned char uuid_t[16]; +void uuid_clear(uuid_t uu); +int uuid_compare(const uuid_t uu1, const uuid_t uu2); +void uuid_copy(uuid_t dst, const uuid_t src); +void uuid_generate(uuid_t out); +void uuid_generate_random(uuid_t out); +int uuid_is_null(const uuid_t uu); +int uuid_parse(const char *in, uuid_t uu); +void uuid_unparse(const uuid_t uu, char *out); + +#endif /* REPLACE_UUID_H */ diff --git a/include/stonith/Makefile.am b/include/stonith/Makefile.am new file mode 100644 index 0000000..9e67a2a --- /dev/null +++ b/include/stonith/Makefile.am @@ -0,0 +1,25 @@ +# +# linux-ha: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# This instance created by Horms +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +idir=$(includedir)/stonith + +i_HEADERS = expect.h stonith.h stonith_plugin.h st_ttylock.h diff --git a/include/stonith/expect.h b/include/stonith/expect.h new file mode 100644 index 0000000..6084ef1 --- /dev/null +++ b/include/stonith/expect.h @@ -0,0 +1,61 @@ +/* + * Expect simple tokens. Simple expect infrastructure for STONITH API + * + * Copyright (c) 2000 Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __EXPECT_H +# define __EXPECT_H +/* + * If we find any of the given tokens in the input stream, + * we return it's "toktype", so we can tell which one was + * found. + * + */ + +struct Etoken { + const char * string; /* The token to look for */ + int toktype; /* The type to return on match */ + int matchto; /* Modified during matches */ +}; + +int ExpectToken(int fd +, struct Etoken * toklist /* List of tokens to match against */ + /* Final token has NULL string */ +, int to_secs /* Timeout value in seconds */ +, char * buf /* If non-NULL, then all the text + * matched/skipped over by this match */ +, int maxline, +, int debug); /* debug level */ + + +/* + * A handy little routine. It runs the given process + * with it's standard output redirected into our *readfd, and + * its standard input redirected from our *writefd + * + * Doing this with all the pipes, etc. required for doing this + * is harder than it sounds :-) + */ + +int StartProcess(const char * cmd, int* readfd, int* writefd); + +#ifndef EOS +# define EOS '\0' +#endif +#endif /*__EXPECT_H*/ diff --git a/include/stonith/st_ttylock.h b/include/stonith/st_ttylock.h new file mode 100644 index 0000000..5b5c7fd --- /dev/null +++ b/include/stonith/st_ttylock.h @@ -0,0 +1,21 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __STONITH_ST_TTYLOCK_H +# define __STONITH_ST_TTYLOCK_H +int st_ttylock(const char *serial_device); +int st_ttyunlock(const char *serial_device); +#endif /*__STONITH_ST_TTYLOCK_H*/ diff --git a/include/stonith/stonith.h b/include/stonith/stonith.h new file mode 100644 index 0000000..93fbaac --- /dev/null +++ b/include/stonith/stonith.h @@ -0,0 +1,187 @@ +/* + * S hoot + * T he + * O ther + * N ode + * I n + * T he + * H ead + * + * Cause the other machine to reboot or die - now. + * + * We guarantee that when we report that the machine has been + * rebooted, then it has been (barring misconfiguration or hardware + * errors) + * + * A machine which we have STONITHed won't do anything more to its + * peripherials etc. until it goes through the reboot cycle. + */ + +/* + * + * Copyright (c) 2000 Alan Robertson + * Copyright (c) 2004 International Business Machines, Inc. + * + * Author: Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __STONITH_H +# define __STONITH_H +#include +#include + +#include +#define STONITH_VERS 2 + +/* + * Return codes from "Stonith" operations + */ + +#define S_OK 0 /* Machine correctly reset */ +#define S_BADCONFIG 1 /* Bad config info given */ +#define S_ACCESS 2 /* Can't access STONITH device */ + /* (login/passwd problem?) */ +#define S_INVAL 3 /* Bad/illegal argument */ +#define S_BADHOST 4 /* Bad/illegal host/node name */ +#define S_RESETFAIL 5 /* Reset failed */ +#define S_TIMEOUT 6 /* Timed out in the dialogues */ +#define S_ISOFF 7 /* Can't reboot: Outlet is off */ +#define S_OOPS 8 /* Something strange happened */ + +typedef struct stonith { + char * stype; +}Stonith; + +/* An array of StonithNVpairs is terminated by a NULL s_name */ +typedef struct { + char * s_name; + char * s_value; +}StonithNVpair; + +/* + * Operation requested by reset_req() + */ +#define ST_GENERIC_RESET 1 /* Reset the machine any way you can */ +#define ST_POWERON 2 /* Power the node on */ +#define ST_POWEROFF 3 /* Power the node off */ +/* + * Type of information requested by the get_info() call + */ +#define ST_CONF_XML 1 /* XML config info */ +#define ST_DEVICEID 2 /* Device Type Identification */ +#define ST_DEVICENAME 3 /* Unique Individual Device Identification */ + /* (only after stonith_set_config() call) */ +#define ST_DEVICEDESCR 4 /* Device Description text */ +#define ST_DEVICEURL 5 /* Manufacturer/Device URL */ + +extern PILPluginUniv *StonithPIsys; + +char ** stonith_types(void); /* NULL-terminated list */ + /* valid until next call of stonith_types() */ +Stonith*stonith_new(const char * type); +void stonith_delete(Stonith *); + +const char * const * stonith_get_confignames (Stonith* s); + /* static/global return */ + /* Return number and list of valid s_names */ + +const char* /* static/global return - lots of things! */ + stonith_get_info (Stonith* s, int infotype); + +void stonith_set_debug (Stonith* s, int debuglevel); +void stonith_set_log (Stonith* s + , PILLogFun); + +int stonith_set_config (Stonith* s, StonithNVpair* list); +int stonith_set_config_file(Stonith* s, const char * configname); + /* uses get_confignames to determine which + * names to look for in file configname, which + * is passed in by the -F option */ +int stonith_set_config_info(Stonith* s, const char * info); + /* uses get_confignames to determine which + * names to look for in string info, which + * is passed in by the -p option */ + /* + * Must call stonith_set_config() before calling functions below... + */ +char** stonith_get_hostlist (Stonith* s); +void stonith_free_hostlist (char** hostlist); +int stonith_get_status (Stonith* s); +int stonith_req_reset (Stonith* s, int operation, const char* node); + +StonithNVpair* stonith_env_to_NVpair(Stonith* s); + +/* Stonith 1 compatibility: Convert string to an NVpair set */ +StonithNVpair* + stonith1_compat_string_to_NVpair(Stonith* s, const char * str); +StonithNVpair* + stonith_ghash_to_NVpair(GHashTable* stringtable); +void free_NVpair(StonithNVpair*); /* Free result from above 2 functions */ +void strdown(char *str); /* simple replacement for g_strdown */ + +/* + * The ST_DEVICEID info call is intended to return the type of the Stonith + * device. Note that it may return a different result once it has attempted + * to talk to the device (like after a status() call). This is because + * a given STONITH module may be able to talk to more than one kind of + * model of STONITH device, and can't tell which type is out there + * to until it talks to it. For example, Baytech 3, Baytech 5 and + * Baytech 5a are all supported by one module, and this module actually + * captures the particular model number after it talks to it. + * + * The ST_DEVICEDESCR info call is intended to return information identifying + * the type of STONITH device supported by this STONITH object. This is so + * users can tell if they have this kind of device or not. + * + * SHOULD THIS BE IN THE XML SO IT CAN BE SUPPLIED IN SEVERAL LANGUAGES?? + * But, this would mean the STONITH command would have to parse XML. + * Sigh... I'd rather not... Or maybe it can be supplied duplicately + * in the XML if that is thought to be desirable... + * + * The ST_DEVICEURL info call is intended to return the URL of a web site + * related to the device in question. This might be the manufacturer, + * a pointer to the product line, or the individual product itself. + * + * A good way for a GUI to work which configures STONITH devices would be to + * use the result of the stonith_types() call in a pulldown menu. + * + * Once the type is selected, create a Stonith object of the selected type. + * One can then create a dialog box to create the configuration info for the + * device using return from the ST_CONF_XML info call to direct the + * GUI in what information to ask for to fill up the StonithNVpair + * argument to the stonith_set_config() call. This information would then + * be prompted for according to the XML information, and then put into + * a NULL-terminated array of StonithNVpair objects. + * + * Once this has been done, it can be tested for syntactic + * validity with stonith_set_config(). + * + * If it passes set_config(), it can be further validated using status() + * which will then actually try and talk to the STONITH device. If status() + * returns S_OK, then communication with the device was successfully + * established. + * + * Normally that would mean that logins, passwords, device names, and IP + * addresses, etc. have been validated as required by the particular device. + * + * At this point, you can ask the device which machines it knows how to reset + * using the stonith_get_hostlist() function. + * + */ + +#endif /*__STONITH_H*/ diff --git a/include/stonith/stonith_plugin.h b/include/stonith/stonith_plugin.h new file mode 100644 index 0000000..9091a6e --- /dev/null +++ b/include/stonith/stonith_plugin.h @@ -0,0 +1,125 @@ +/* + * S hoot + * T he + * O ther + * N ode + * I n + * T he + * H ead + * + * Cause the other machine to reboot or die - now. + * + * We guarantee that when we report that the machine has been + * rebooted, then it has been (barring misconfiguration or hardware errors) + * + * A machine which we have STONITHed won't do anything more to its + * peripherials etc. until it goes through the reboot cycle. + */ + +/* + * + * Copyright (c) 2004 International Business Machines, Inc. + * + * Author: Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __STONITH_PLUGIN_H +# define __STONITH_PLUGIN_H + +#include +#include + +typedef struct stonith_plugin StonithPlugin; + +#define NUM_STONITH_FNS 7 + +struct stonith_ops { + StonithPlugin * (*new) (const char*); /* mini-Constructor */ + void (*destroy) (StonithPlugin*); /*(full) Destructor */ + + const char* (*get_info) (StonithPlugin*, int infotype); + const char * const * (*get_confignames) (StonithPlugin*); + int (*set_config) (StonithPlugin*, StonithNVpair* list); + /* Finishes construction */ + /* + * Must call set_config before calling any of + * the member functions below... + */ + + int (*get_status) (StonithPlugin*s); + int (*req_reset) (StonithPlugin*, int op, const char* node); + + + char** (*get_hostlist) (StonithPlugin*); + /* Returns list of hosts it supports */ +}; + +struct stonith_plugin { + Stonith s; + struct stonith_ops* s_ops; + gboolean isconfigured; +}; + +#define STONITH_TYPE stonith2 +#define STONITH_TYPE_S "stonith2" +typedef struct StonithImports_s StonithImports; + +struct Etoken { + const char * string; /* The token to look for */ + int toktype; /* The type to return on match */ + int matchto; /* Modified during matches */ +}; + +/* An array of StonithNamesToGet is terminated by a NULL s_name */ +typedef struct { + const char * s_name; + char * s_value; +}StonithNamesToGet; + +#define TELNET_PORT 23 +#define TELNET_SERVICE "telnet" + +struct StonithImports_s { + int (*ExpectToken)(int fd, struct Etoken * toklist, int to_secs + , char * buf, int maxline, int debug); + int (*StartProcess)(const char * cmd, int * readfd, int * writefd); + int (*OpenStreamSocket) (const char * host, int port + , const char * service); + /* Service can be NULL, port can be <= 0, but not both... */ + const char* (*GetValue)(StonithNVpair*, const char * name); + int (*CopyAllValues) (StonithNamesToGet* out, StonithNVpair* in); + char **(*StringToHostList)(const char * hlstring); + char **(*CopyHostList)(const char * const * hlstring); + void (*FreeHostList)(char** hostlist); + int (*TtyLock)(const char* tty); + int (*TtyUnlock)(const char* tty); +}; + + +/* + * A few standardized parameter names + */ + +#define ST_HOSTLIST "hostlist" +#define ST_IPADDR "ipaddr" +#define ST_LOGIN "login" +#define ST_PASSWD "password" +#define ST_COMMUNITY "community" /* SNMP community */ +#define ST_TTYDEV "ttydev" /* TTY device name */ + +#endif /*__STONITH__PLUGIN_H*/ diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..5047e1d --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,20 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = pils clplumbing lrm stonith plugins diff --git a/lib/clplumbing/GSource.c b/lib/clplumbing/GSource.c new file mode 100644 index 0000000..48bb198 --- /dev/null +++ b/lib/clplumbing/GSource.c @@ -0,0 +1,1864 @@ +/* + * Copyright (c) 2002 Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef events +# undef events +#endif +#ifdef revents +# undef revents +#endif + +#define DEFAULT_MAXDISPATCH 0 +#define DEFAULT_MAXDELAY 0 +#define OTHER_MAXDELAY 100 + +/* + * On architectures with alignment constraints, our casting between + * "(GSource*)" and "(GFDSource_s*)" etc. causes trouble, because of + * the massive alignment requirements of "longclock_t". + * + * Use the following to store and fetch. + */ +static +void +lc_store(char *destptr, longclock_t value) { + longclock_t _ltt; + _ltt = value; + memcpy((destptr), &_ltt, sizeof(longclock_t)); +} + +static +longclock_t +lc_fetch(char *ptr) { + longclock_t _ltt; + memcpy(&_ltt, (ptr), sizeof(longclock_t)); + return _ltt; +} + +#define ERR_EVENTS (G_IO_ERR|G_IO_NVAL) +#define INPUT_EVENTS (G_IO_IN|G_IO_PRI|G_IO_HUP) +#define OUTPUT_EVENTS (G_IO_OUT) +#define DEF_EVENTS (INPUT_EVENTS|ERR_EVENTS) + +#define WARN_DELAY(ms, mx, input) cl_log(LOG_WARNING \ + , "%s: Dispatch function for %s was delayed" \ + " %lu ms (> %lu ms) before being called (GSource: 0x%lx)" \ + , __FUNCTION__, (input)->description, ms, mx \ + , POINTER_TO_ULONG(input)) + +#define EXPLAINDELAY(started, detected) cl_log(LOG_INFO \ + , "%s: started at %llu should have started at %llu" \ + , __FUNCTION__, (unsigned long long)started \ + , (unsigned long long)detected) + + +#define WARN_TOOLONG(ms, mx, input) cl_log(LOG_WARNING \ + , "%s: Dispatch function for %s took too long to execute" \ + ": %lu ms (> %lu ms) (GSource: 0x%lx)" \ + , __FUNCTION__, (input)->description, ms, mx \ + , POINTER_TO_ULONG(input)) + +#define CHECK_DISPATCH_DELAY(i) { \ + unsigned long ms; \ + longclock_t dettime; \ + dispstart = time_longclock(); \ + dettime = lc_fetch((i)->detecttime); \ + ms = longclockto_ms(sub_longclock(dispstart,dettime)); \ + if ((i)->maxdispatchdelayms > 0 \ + && ms > (i)->maxdispatchdelayms) { \ + WARN_DELAY(ms, (i)->maxdispatchdelayms, (i)); \ + EXPLAINDELAY(dispstart, dettime); \ + } \ +} + +#define CHECK_DISPATCH_TIME(i) { \ + unsigned long ms; \ + longclock_t dispend = time_longclock(); \ + ms = longclockto_ms(sub_longclock(dispend, dispstart)); \ + if ((i)->maxdispatchms > 0 && ms > (i)->maxdispatchms) { \ + WARN_TOOLONG(ms, (i)->maxdispatchms, (i)); \ + } \ + lc_store(((i)->detecttime), zero_longclock); \ +} + +#define WARN_TOOMUCH(ms, mx, input) cl_log(LOG_WARNING \ + , "%s: working on %s took %ld ms (> %ld ms)" \ + , __FUNCTION__, (input)->description, ms, mx); + +#define SAVESTART {funstart = time_longclock();} + +#define CHECKEND(input) { \ + longclock_t funend = time_longclock(); \ + long ms; \ + ms = longclockto_ms(sub_longclock(funend, funstart)); \ + if (ms > OTHER_MAXDELAY){ \ + WARN_TOOMUCH(ms, ((long) OTHER_MAXDELAY), input); \ + } \ +} \ + + +#ifndef _NSIG +# define _NSIG 2*NSIG +#endif + +static gboolean G_fd_prepare(GSource* source, + gint* timeout); +static gboolean G_fd_check(GSource* source); +static gboolean G_fd_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_fd_destroy(GSource* source); + +static GSourceFuncs G_fd_SourceFuncs = { + G_fd_prepare, + G_fd_check, + G_fd_dispatch, + G_fd_destroy, +}; + +GSource* +G_main_add_input(int priority, + gboolean can_recurse, + GSourceFuncs* funcs) +{ + GSource * input_source = g_source_new(funcs, sizeof(GSource)); + if (input_source == NULL){ + cl_log(LOG_ERR, "create glib source for input failed!"); + }else { + g_source_set_priority(input_source, priority); + g_source_set_can_recurse(input_source, can_recurse); + if(g_source_attach(input_source, NULL) == 0){ + cl_log(LOG_ERR, "attaching input_source to main context" + " failed!! "); + } + } + + return input_source; +} + + +/* + * Add the given file descriptor to the gmainloop world. + */ + + +GFDSource* +G_main_add_fd(int priority, int fd, gboolean can_recurse +, gboolean (*dispatch)(int fd, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify) +{ + + GSource* source = g_source_new(&G_fd_SourceFuncs, + sizeof(GFDSource)); + GFDSource* ret = (GFDSource*)source; + + ret->magno = MAG_GFDSOURCE; + ret->maxdispatchdelayms = DEFAULT_MAXDELAY; + ret->maxdispatchms = DEFAULT_MAXDISPATCH; + ret->udata = userdata; + ret->dispatch = dispatch; + ret->gpfd.fd = fd; + ret->gpfd.events = DEF_EVENTS; + ret->gpfd.revents = 0; + ret->dnotify = notify; + lc_store((ret->detecttime), zero_longclock); + + g_source_add_poll(source, &ret->gpfd); + + + g_source_set_priority(source, priority); + + g_source_set_can_recurse(source, can_recurse); + + ret->gsourceid = g_source_attach(source, NULL); + ret->description = "file descriptor"; + + if (ret->gsourceid == 0) { + g_source_remove_poll(source, &ret->gpfd); + memset(ret, 0, sizeof(GFDSource)); + g_source_unref(source); + source = NULL; + ret = NULL; + } + return ret; +} + +gboolean +G_main_del_fd(GFDSource* fdp) +{ + GSource * source = (GSource*) fdp; + + + if (fdp->gsourceid <= 0) { + return FALSE; + } + + g_source_remove_poll(source, &fdp->gpfd); + g_source_remove(fdp->gsourceid); + fdp->gsourceid = 0; + g_source_unref(source); + + return TRUE; + +} + +void +g_main_output_is_blocked(GFDSource* fdp) +{ + fdp->gpfd.events |= OUTPUT_EVENTS; +} + + +/* + * For pure file descriptor events, return FALSE because we + * have to poll to get events. + * + * Note that we don't modify 'timeout' either. + */ +static gboolean +G_fd_prepare(GSource* source, + gint* timeout) +{ + GFDSource* fdp = (GFDSource*)source; + g_assert(IS_FDSOURCE(fdp)); + return FALSE; +} + +/* + * Did we notice any I/O events? + */ + +static gboolean +G_fd_check(GSource* source) + +{ + GFDSource* fdp = (GFDSource*)source; + g_assert(IS_FDSOURCE(fdp)); + if (fdp->gpfd.revents) { + lc_store((fdp->detecttime), time_longclock()); + return TRUE; + } + return FALSE; +} + +/* + * Some kind of event occurred - notify the user. + */ +static gboolean +G_fd_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data) +{ + + GFDSource* fdp = (GFDSource*)source; + longclock_t dispstart; + g_assert(IS_FDSOURCE(fdp)); + CHECK_DISPATCH_DELAY(fdp); + + + /* + * Is output now unblocked? + * + * If so, turn off OUTPUT_EVENTS to avoid going into + * a tight poll(2) loop. + */ + if (fdp->gpfd.revents & OUTPUT_EVENTS) { + fdp->gpfd.events &= ~OUTPUT_EVENTS; + } + + if(fdp->dispatch) { + if(!(fdp->dispatch(fdp->gpfd.fd, fdp->udata))){ + g_source_remove_poll(source,&fdp->gpfd); + g_source_unref(source); + CHECK_DISPATCH_TIME(fdp); + return FALSE; + } + CHECK_DISPATCH_TIME(fdp); + } + + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_fd_destroy(GSource* source) +{ + GFDSource* fdp = (GFDSource*)source; + g_assert(IS_FDSOURCE(fdp)); + fdp->gsourceid = 0; + if (fdp->dnotify) { + fdp->dnotify(fdp->udata); + } +} + + +/************************************************************ + * Functions for IPC_Channels + ***********************************************************/ +gboolean G_CH_prepare_int(GSource* source, + gint* timeout); +gboolean G_CH_check_int(GSource* source); + +gboolean G_CH_dispatch_int(GSource* source, + GSourceFunc callback, + gpointer user_data); +void G_CH_destroy_int(GSource* source); + + +static GSourceFuncs G_CH_SourceFuncs = { + G_CH_prepare_int, + G_CH_check_int, + G_CH_dispatch_int, + G_CH_destroy_int, +}; + + + + +void +set_IPC_Channel_dnotify(GCHSource* chp, + GDestroyNotify notify){ + chp->dnotify = notify; +} + +/* + * Add an IPC_channel to the gmainloop world... + */ +GCHSource* +G_main_IPC_Channel_constructor(GSource* source, IPC_Channel* ch + , gpointer userdata + , GDestroyNotify notify) +{ + int rfd, wfd; + GCHSource* chp; + + if( !source ) { + cl_log(LOG_WARNING, "%s:%d: got null source", __FUNCTION__,__LINE__); + return NULL; + } + if( !ch ) { + cl_log(LOG_WARNING, "%s:%d: got null channel", __FUNCTION__,__LINE__); + return NULL; + } + chp = (GCHSource*)source; + + chp->magno = MAG_GCHSOURCE; + chp->maxdispatchdelayms = DEFAULT_MAXDELAY; + chp->maxdispatchms = DEFAULT_MAXDISPATCH; + lc_store((chp->detecttime), zero_longclock); + ch->refcount++; + chp->ch = ch; + chp->udata=userdata; + chp->dnotify = notify; + chp->dontread = FALSE; + + rfd = ch->ops->get_recv_select_fd(ch); + wfd = ch->ops->get_send_select_fd(ch); + + chp->fd_fdx = (rfd == wfd); + + if (debug_level > 1) { + cl_log(LOG_DEBUG, "%s(sock=%d,%d)",__FUNCTION__, rfd,wfd); + } + chp->infd.fd = rfd; + chp->infd.events = DEF_EVENTS; + g_source_add_poll(source, &chp->infd); + if (!chp->fd_fdx) { + chp->outfd.fd = wfd; + chp->outfd.events = DEF_EVENTS; + g_source_add_poll(source, &chp->outfd); + } + chp->dispatch = NULL; + chp->description = "IPC channel(base)"; + chp->gsourceid = 0; + return chp; +} + +GCHSource* +G_main_add_IPC_Channel(int priority, IPC_Channel* ch + , gboolean can_recurse + , gboolean (*dispatch)(IPC_Channel* source_data, + gpointer user_data) + , gpointer userdata + , GDestroyNotify notify) +{ + GCHSource *chp; + GSource *source; + + if( !ch ) { + cl_log(LOG_WARNING, "%s:%d: got null channel", __FUNCTION__,__LINE__); + return NULL; + } + source = g_source_new(&G_CH_SourceFuncs, + sizeof(GCHSource)); + G_main_IPC_Channel_constructor(source,ch,userdata,notify); + + chp = (GCHSource*)source; + chp->dispatch = dispatch; + + g_source_set_priority(source, priority); + g_source_set_can_recurse(source, can_recurse); + + chp->gsourceid = g_source_attach(source, NULL); + chp->description = "IPC channel"; + + + if (chp->gsourceid == 0) { + g_source_remove_poll(source, &chp->infd); + if (!chp->fd_fdx) { + g_source_remove_poll(source, &chp->outfd); + } + g_source_unref(source); + source = NULL; + chp = NULL; + } + return chp; +} + + +void /* Suspend reading from far end writer (flow control) */ +G_main_IPC_Channel_pause(GCHSource* chp) +{ + if (chp == NULL){ + cl_log(LOG_ERR, "%s: invalid input", __FUNCTION__); + return; + } + + chp->dontread = TRUE; + return; +} + + +void /* Resume reading from far end writer (un-flow-control) */ +G_main_IPC_Channel_resume(GCHSource* chp) +{ + if (chp == NULL){ + cl_log(LOG_ERR, "%s: invalid input", __FUNCTION__); + return; + } + + chp->dontread = FALSE; + return; + +} + +/* + * Delete an IPC_channel from the gmainloop world... + */ +gboolean +G_main_del_IPC_Channel(GCHSource* chp) +{ + GSource* source = (GSource*) chp; + + if (chp == NULL || chp->gsourceid <= 0) { + return FALSE; + } + + if (debug_level > 1) { + cl_log(LOG_DEBUG, "%s(sock=%d)",__FUNCTION__, chp->infd.fd); + } + g_source_remove(chp->gsourceid); + chp->gsourceid = 0; + /* chp should (may) now be undefined */ + g_source_unref(source); + + return TRUE; +} + +/* + * For IPC_CHANNEL events, enable output checking when needed + * and note when unread input is already queued. + * + * Note that we don't modify 'timeout' either. + */ +gboolean +G_CH_prepare_int(GSource* source, + gint* timeout) +{ + GCHSource* chp = (GCHSource*)source; + longclock_t funstart; + gboolean ret; + + g_assert(IS_CHSOURCE(chp)); + SAVESTART; + + + if (chp->ch->ops->is_sending_blocked(chp->ch)) { + if (chp->fd_fdx) { + chp->infd.events |= OUTPUT_EVENTS; + }else{ + chp->outfd.events |= OUTPUT_EVENTS; + } + } + + if (chp->ch->recv_queue->current_qlen < chp->ch->recv_queue->max_qlen) { + chp->infd.events |= INPUT_EVENTS; + }else{ + /* + * This also disables EOF events - until we + * read some of the packets we've already gotten + * This prevents a tight loop in poll(2). + */ + chp->infd.events &= ~INPUT_EVENTS; + } + + if (chp->dontread){ + return FALSE; + } + ret = chp->ch->ops->is_message_pending(chp->ch); + if (ret) { + lc_store((chp->detecttime), time_longclock()); + } + CHECKEND(chp); + return ret; +} + +/* + * Did we notice any I/O events? + */ + +gboolean +G_CH_check_int(GSource* source) +{ + + GCHSource* chp = (GCHSource*)source; + gboolean ret; + longclock_t funstart; + + g_assert(IS_CHSOURCE(chp)); + SAVESTART; + + + if (chp->dontread){ + /* Make sure output gets unblocked */ + chp->ch->ops->resume_io(chp->ch); + return FALSE; + } + + ret = (chp->infd.revents != 0 + || (!chp->fd_fdx && chp->outfd.revents != 0) + || chp->ch->ops->is_message_pending(chp->ch)); + if (ret) { + lc_store((chp->detecttime), time_longclock()); + } + CHECKEND(chp); + return ret; +} + +/* + * Some kind of event occurred - notify the user. + */ +gboolean +G_CH_dispatch_int(GSource * source, + GSourceFunc callback, + gpointer user_data) +{ + GCHSource* chp = (GCHSource*)source; + longclock_t dispstart; + longclock_t resume_start = zero_longclock; + + g_assert(IS_CHSOURCE(chp)); + CHECK_DISPATCH_DELAY(chp); + + + if (chp->dontread){ + return TRUE; + } + + /* Is output now unblocked? + * + * If so, turn off OUTPUT_EVENTS to avoid going into + * a tight poll(2) loop. + */ + if (chp->fd_fdx) { + if (chp->infd.revents & OUTPUT_EVENTS) { + chp->infd.events &= ~OUTPUT_EVENTS; + } + }else if (chp->outfd.revents & OUTPUT_EVENTS) { + chp->outfd.events &= ~OUTPUT_EVENTS; + } + + if (ANYDEBUG) { + resume_start = time_longclock(); + } + + chp->ch->ops->resume_io(chp->ch); + + if (ANYDEBUG) { + longclock_t resume_end = time_longclock(); + unsigned long ms; + ms = longclockto_ms(sub_longclock(resume_end + , resume_start)); + if (ms > 10) { + cl_log(LOG_WARNING + , "%s: resume_io() for %s took %lu ms" + , __FUNCTION__ + , chp->description, ms); + } + } + + + if(chp->dispatch && chp->ch->ops->is_message_pending(chp->ch)) { + if(!(chp->dispatch(chp->ch, chp->udata))){ + g_source_remove_poll(source, &chp->infd); + if (!chp->fd_fdx) { + g_source_remove_poll(source, &chp->outfd); + } + CHECK_DISPATCH_TIME(chp); + g_source_unref(source); + return FALSE; + } + } + CHECK_DISPATCH_TIME(chp); + + if (chp->ch->ch_status == IPC_DISCONNECT){ + return FALSE; + } + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +void +G_CH_destroy_int(GSource* source) +{ + GCHSource* chp = (GCHSource*)source; + + g_assert(IS_CHSOURCE(chp)); + if (debug_level > 1) { + cl_log(LOG_DEBUG, "%s(chp=0x%lx, sock=%d) {", __FUNCTION__ + , (unsigned long)chp, chp->infd.fd); + } + + if (chp->dnotify) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling dnotify(sock=%d, arg=0x%lx) function" + , __FUNCTION__, chp->infd.fd, (unsigned long)chp->udata); + } + chp->dnotify(chp->udata); + }else{ + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: NOT calling dnotify(sock=%d) function" + , __FUNCTION__, chp->infd.fd); + } + } + if (chp->ch) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: calling IPC destroy (chp->ch=0x%lx, sock=%d)" + , __FUNCTION__ , (unsigned long)chp->ch, chp->infd.fd); + } + chp->ch->ops->destroy(chp->ch); + chp->ch = NULL; + } + /*chp->gsourceid = 0; ?*/ + if (debug_level > 1) { + cl_log(LOG_DEBUG, "}/*%s(sock=%d)*/", __FUNCTION__, chp->infd.fd); + } +} + + +/************************************************************ + * Functions for IPC_WaitConnections + ***********************************************************/ +static gboolean G_WC_prepare(GSource * source, + gint* timeout); +static gboolean G_WC_check(GSource* source); +static gboolean G_WC_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_WC_destroy(GSource* source); + +static GSourceFuncs G_WC_SourceFuncs = { + G_WC_prepare, + G_WC_check, + G_WC_dispatch, + G_WC_destroy, +}; + + +/* + * Add an IPC_WaitConnection to the gmainloop world... + */ +GWCSource* +G_main_add_IPC_WaitConnection(int priority +, IPC_WaitConnection* wch +, IPC_Auth* auth_info +, gboolean can_recurse +, gboolean (*dispatch)(IPC_Channel* wch +, gpointer user_data) +, gpointer userdata +, GDestroyNotify notify) +{ + + GWCSource* wcp; + GSource * source = g_source_new(&G_WC_SourceFuncs, + sizeof(GWCSource)); + + wcp = (GWCSource*)source; + + wcp->magno = MAG_GWCSOURCE; + wcp->maxdispatchdelayms = DEFAULT_MAXDELAY; + wcp->maxdispatchms = DEFAULT_MAXDISPATCH; + lc_store((wcp->detecttime), zero_longclock); + wcp->udata = userdata; + wcp->gpfd.fd = wch->ops->get_select_fd(wch); + wcp->gpfd.events = DEF_EVENTS; + wcp->gpfd.revents = 0; + wcp->wch = wch; + wcp->dnotify = notify; + wcp->auth_info = auth_info; + wcp->dispatch = dispatch; + + g_source_add_poll(source, &wcp->gpfd); + + g_source_set_priority(source, priority); + + g_source_set_can_recurse(source, can_recurse); + + wcp->gsourceid = g_source_attach(source, NULL); + wcp->description = "IPC wait for connection"; + + if (wcp->gsourceid == 0) { + g_source_remove_poll(source, &wcp->gpfd); + g_source_unref(source); + source = NULL; + wcp = NULL; + } + return wcp; +} + + +/* Delete the given IPC_WaitConnection from the gmainloop world */ +gboolean +G_main_del_IPC_WaitConnection(GWCSource* wcp) +{ + + GSource* source = (GSource*) wcp; + + + if (wcp->gsourceid <= 0) { + return FALSE; + } + + g_source_remove(wcp->gsourceid); + wcp->gsourceid = 0; + g_source_unref(source); + + return TRUE; +} + + + +/* + * For IPC_WaitConnection events, return FALSE because we + * have to poll to get events. + * + * We don't modify 'timeout' either. + */ +static gboolean +G_WC_prepare(GSource* source, + gint* timeout) +{ + GWCSource* wcp = (GWCSource*)source; + g_assert(IS_WCSOURCE(wcp)); + return FALSE; +} + +/* + * Did we notice any I/O (connection pending) events? + */ + +static gboolean +G_WC_check(GSource * source) +{ + GWCSource* wcp = (GWCSource*)source; + g_assert(IS_WCSOURCE(wcp)); + + if (wcp->gpfd.revents != 0) { + lc_store((wcp->detecttime), time_longclock()); + return TRUE; + } + return FALSE; +} + +/* + * Someone is trying to connect. + * Try to accept the connection and notify the user. + */ +static gboolean +G_WC_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data) +{ + GWCSource* wcp = (GWCSource*)source; + IPC_Channel* ch; + gboolean rc = TRUE; + int count = 0; + longclock_t dispstart; + + g_assert(IS_WCSOURCE(wcp)); + CHECK_DISPATCH_DELAY(wcp); + + while(1) { + ch = wcp->wch->ops->accept_connection(wcp->wch, wcp->auth_info); + if (ch == NULL) { + if (errno == EBADF) { + cl_perror("%s: Stopping accepting connections(socket=%d)!!" + , __FUNCTION__, wcp->gpfd.fd); + rc = FALSE; + } + break; + } + ++count; + + if(!wcp->dispatch) { + continue; + } + + rc = wcp->dispatch(ch, wcp->udata); + if(!rc) { + g_source_remove_poll(source, &wcp->gpfd); + g_source_unref(source); + break; + } + } + CHECK_DISPATCH_TIME(wcp); + return rc; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_WC_destroy(GSource* source) +{ + + GWCSource* wcp = (GWCSource*)source; + wcp->gsourceid = 0; + g_assert(IS_WCSOURCE(wcp)); + wcp->wch->ops->destroy(wcp->wch); + if (wcp->dnotify) { + wcp->dnotify(wcp->udata); + } +} + + +/************************************************************ + * Functions for Signals + ***********************************************************/ +static gboolean G_SIG_prepare(GSource* source, + gint* timeout); +static gboolean G_SIG_check(GSource* source); + +static gboolean G_SIG_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_SIG_destroy(GSource* source); + +static void G_main_signal_handler(int nsig); + +static GSourceFuncs G_SIG_SourceFuncs = { + G_SIG_prepare, + G_SIG_check, + G_SIG_dispatch, + G_SIG_destroy, +}; + +static GSIGSource *G_main_signal_list[_NSIG]; + +void +set_SignalHandler_dnotify(GSIGSource* sig_src, GDestroyNotify notify) +{ + sig_src->dnotify = notify; +} + +/* + * Add an Signal to the gmainloop world... + */ +GSIGSource* +G_main_add_SignalHandler(int priority, int signal, + gboolean (*dispatch)(int nsig, gpointer user_data), + gpointer userdata, GDestroyNotify notify) +{ + GSIGSource* sig_src; + GSource * source = g_source_new(&G_SIG_SourceFuncs, sizeof(GSIGSource)); + gboolean failed = FALSE; + + sig_src = (GSIGSource*)source; + + sig_src->magno = MAG_GSIGSOURCE; + sig_src->maxdispatchdelayms = DEFAULT_MAXDELAY; + sig_src->maxdispatchms = DEFAULT_MAXDISPATCH; + sig_src->signal = signal; + sig_src->dispatch = dispatch; + sig_src->udata = userdata; + sig_src->dnotify = notify; + + sig_src->signal_triggered = FALSE; + + g_source_set_priority(source, priority); + g_source_set_can_recurse(source, FALSE); + + if(G_main_signal_list[signal] != NULL) { + cl_log(LOG_ERR + , "%s: Handler already present for signal %d" + , __FUNCTION__, signal); + failed = TRUE; + } + if(!failed) { + sig_src->gsourceid = g_source_attach(source, NULL); + sig_src->description = "signal"; + if (sig_src->gsourceid < 1) { + cl_log(LOG_ERR + , "%s: Could not attach source for signal %d (%d)" + , __FUNCTION__ + , signal, sig_src->gsourceid); + failed = TRUE; + } + } + + if(failed) { + cl_log(LOG_ERR + , "%s: Signal handler for signal %d NOT added" + , __FUNCTION__, signal); + g_source_remove(sig_src->gsourceid); + g_source_unref(source); + source = NULL; + sig_src = NULL; + } else { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Added signal handler for signal %d" + , __FUNCTION__, signal); + } + G_main_signal_list[signal] = sig_src; + CL_SIGNAL(signal, G_main_signal_handler); + /* + * If we don't set this on, then the mainloop poll(2) call + * will never be interrupted by this signal - which sort of + * defeats the whole purpose of a signal handler in a + * mainloop program + */ + cl_signal_set_interrupt(signal, TRUE); + } + return sig_src; +} + + +/* + * Delete a Signal from the gmainloop world... + */ +gboolean +G_main_del_SignalHandler(GSIGSource* sig_src) +{ + GSource* source = (GSource*) sig_src; + + if (sig_src->gsourceid <= 0) { + return FALSE; + } + if(_NSIG <= sig_src->signal) { + g_assert(_NSIG > sig_src->signal); + return FALSE; + } + + CL_SIGNAL(sig_src->signal, NULL); + + sig_src->signal_triggered = FALSE; + g_source_remove(sig_src->gsourceid); + G_main_signal_list[sig_src->signal] = NULL; + sig_src->gsourceid = 0; + g_source_unref(source); + + return TRUE; +} + +static gboolean +G_SIG_prepare(GSource* source, gint* timeoutms) +{ + GSIGSource* sig_src = (GSIGSource*)source; + + g_assert(IS_SIGSOURCE(sig_src)); + + /* Don't let a timing window keep us in poll() forever + * + * The timing window in question looks like this: + * No signal has occurred up to the point of prepare being called. + * Signal comes in _after_ prepare was called, but _before_ poll. + * signal_detected gets set, but no one checks it before going into poll + * We wait in poll forever... It's not a pretty sight :-(. + */ + *timeoutms = 1000; /* Sigh... */ + + if (sig_src->signal_triggered) { + clock_t now; + clock_t diff; + + /* detecttime is reset in the dispatch function */ + if (cmp_longclock(lc_fetch(sig_src->detecttime), zero_longclock) != 0) { + cl_log(LOG_ERR, "%s: detecttime already set?", __FUNCTION__); + return TRUE; + } + /* Otherwise, this is when it was first detected */ + now = cl_times(); + diff = now - sig_src->sh_detecttime; /* How long since signal occurred? */ + lc_store( + sig_src->detecttime, + sub_longclock(time_longclock(), (longclock_t)diff) + ); + return TRUE; + } + return FALSE; +} + +/* + * Did we notice any I/O events? + */ + +static gboolean +G_SIG_check(GSource* source) +{ + + GSIGSource* sig_src = (GSIGSource*)source; + + g_assert(IS_SIGSOURCE(sig_src)); + + if (sig_src->signal_triggered) { + clock_t now; + clock_t diff; + if (cmp_longclock(lc_fetch(sig_src->detecttime), zero_longclock) != 0){ + return TRUE; + } + /* Otherwise, this is when it was first detected */ + now = cl_times(); + diff = now - sig_src->sh_detecttime; + lc_store( + sig_src->detecttime, + sub_longclock(time_longclock(), (longclock_t)diff) + ); + return TRUE; + } + return FALSE; +} + +/* + * Some kind of event occurred - notify the user. + */ +static gboolean +G_SIG_dispatch(GSource * source, + GSourceFunc callback, + gpointer user_data) +{ + GSIGSource* sig_src = (GSIGSource*)source; + longclock_t dispstart; + + g_assert(IS_SIGSOURCE(sig_src)); + CHECK_DISPATCH_DELAY(sig_src); + + sig_src->sh_detecttime = 0UL; + sig_src->signal_triggered = FALSE; + + if(sig_src->dispatch) { + if(!(sig_src->dispatch(sig_src->signal, sig_src->udata))){ + G_main_del_SignalHandler(sig_src); + CHECK_DISPATCH_TIME(sig_src); + return FALSE; + } + } + CHECK_DISPATCH_TIME(sig_src); + + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_SIG_destroy(GSource* source) +{ + GSIGSource* sig_src = (GSIGSource*)source; + + g_assert(IS_SIGSOURCE(sig_src)); + sig_src->gsourceid = 0; + + if (sig_src->dnotify) { + sig_src->dnotify(sig_src->udata); + } +} + +/* Find and set the correct mainloop input */ + +static void +G_main_signal_handler(int nsig) +{ + GSIGSource* sig_src = NULL; + + if(G_main_signal_list == NULL) { + g_assert(G_main_signal_list != NULL); + return; + } + if(_NSIG <= nsig) { + g_assert(_NSIG > nsig); + return; + } + + sig_src = G_main_signal_list[nsig]; + + if(sig_src == NULL) { + /* cl_log(LOG_CRIT, "No handler for signal -%d", nsig); */ + return; + } + + g_assert(IS_SIGSOURCE(sig_src)); + /* Time from first occurance of signal */ + if (!sig_src->signal_triggered) { + /* Avoid calling longclock_time() on a signal */ + sig_src->sh_detecttime=cl_times(); + } + sig_src->signal_triggered = TRUE; +} + +/* + * Functions to handle child process + */ + +#define WAITALARM 5000L /* milliseconds */ + +static int alarm_count = 0; +static void +G_main_alarm_helper(int nsig) +{ + ++alarm_count; +} + +static gboolean +child_death_dispatch(int sig, gpointer notused) +{ + int status; + pid_t pid; + const int waitflags = WNOHANG; + struct sigaction saveaction; + int childcount = 0; + + /* + * wait3(WNOHANG) isn't _supposed_ to hang + * Unfortunately, it seems to do just that on some OSes. + * + * The workaround is to set an alarm. I don't think for this purpose + * that it matters if siginterrupt(SIGALRM) is set TRUE or FALSE since + * the tiniest little excuse seems to cause the wait3() to finish. + */ + + memset(&saveaction, 0, sizeof(saveaction)); + cl_signal_set_simple_handler(SIGALRM, G_main_alarm_helper, &saveaction); + + alarm_count = 0; + cl_signal_set_interrupt(SIGALRM, TRUE); + setmsrepeattimer(WAITALARM); /* Might as well be persistent ;-) */ + while((pid=wait3(&status, waitflags, NULL)) > 0 + || (pid < 0 && errno == EINTR)) { + cancelmstimer(); + if (pid > 0) { + ++childcount; + ReportProcHasDied(pid, status); + } + setmsrepeattimer(WAITALARM); /* Let's be persistent ;-) */ + } + cancelmstimer(); + cl_signal_set_simple_handler(SIGALRM, saveaction.sa_handler, &saveaction); + + if (pid < 0 && errno != ECHILD) { + cl_perror("%s: wait3() failed" + , __FUNCTION__); + } +#if defined(DEBUG) + if (childcount < 1) { + /* + * This happens when we receive a SIGCHLD after we clear + * 'sig_src->signal_triggered' in G_SIG_dispatch() but + * before the last wait3() call returns no child above. + */ + cl_log(LOG_DEBUG, "NOTE: %s called without children to wait on" + , __FUNCTION__); + } +#endif + if (alarm_count) { + cl_log(LOG_ERR + , "%s: wait3() call hung %d times. childcount = %d" + , __FUNCTION__, alarm_count, childcount); + alarm_count = 0; + } + return TRUE; +} + +void +set_sigchld_proctrack(int priority, unsigned long maxdisptime) +{ + GSIGSource* src = G_main_add_SignalHandler(priority, SIGCHLD + , child_death_dispatch, NULL, NULL); + + G_main_setmaxdispatchdelay((GSource*) src, 100); + G_main_setmaxdispatchtime((GSource*) src, maxdisptime); + G_main_setdescription((GSource*)src, "SIGCHLD"); + return; +} + + +/************************************************************ + * Functions for Trigger inputs + ***********************************************************/ +static gboolean G_TRIG_prepare(GSource* source, + gint* timeout); +static gboolean G_TRIG_check(GSource* source); + +static gboolean G_TRIG_dispatch(GSource* source, + GSourceFunc callback, + gpointer user_data); +static void G_TRIG_destroy(GSource* source); + +static GSourceFuncs G_TRIG_SourceFuncs = { + G_TRIG_prepare, + G_TRIG_check, + G_TRIG_dispatch, + G_TRIG_destroy +}; + +void +set_TriggerHandler_dnotify(GTRIGSource* trig_src, GDestroyNotify notify) +{ + trig_src->dnotify = notify; +} + +/* + * Add an Trigger to the gmainloop world... + */ +GTRIGSource* +G_main_add_TriggerHandler(int priority, + gboolean (*dispatch)(gpointer user_data), + gpointer userdata, GDestroyNotify notify) +{ + GTRIGSource* trig_src = NULL; + GSource * source = g_source_new(&G_TRIG_SourceFuncs, sizeof(GTRIGSource)); + gboolean failed = FALSE; + + trig_src = (GTRIGSource*)source; + + trig_src->magno = MAG_GTRIGSOURCE; + trig_src->maxdispatchdelayms = DEFAULT_MAXDELAY; + trig_src->maxdispatchms = DEFAULT_MAXDISPATCH; + trig_src->dispatch = dispatch; + trig_src->udata = userdata; + trig_src->dnotify = notify; + lc_store((trig_src->detecttime), zero_longclock); + + trig_src->manual_trigger = FALSE; + + g_source_set_priority(source, priority); + g_source_set_can_recurse(source, FALSE); + + if(!failed) { + trig_src->gsourceid = g_source_attach(source, NULL); + trig_src->description = "trigger"; + if (trig_src->gsourceid < 1) { + cl_log(LOG_ERR, "G_main_add_TriggerHandler: Could not attach new source (%d)", + trig_src->gsourceid); + failed = TRUE; + } + } + + if(failed) { + cl_log(LOG_ERR, "G_main_add_TriggerHandler: Trigger handler NOT added"); + g_source_remove(trig_src->gsourceid); + g_source_unref(source); + source = NULL; + trig_src = NULL; + } else { + if (debug_level > 1) { + cl_log(LOG_DEBUG, "G_main_add_TriggerHandler: Added signal manual handler"); + } + } + + return trig_src; +} + +void +G_main_set_trigger(GTRIGSource* source) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + + trig_src->manual_trigger = TRUE; + lc_store((trig_src->detecttime), time_longclock()); +} + + +/* + * Delete a Trigger from the gmainloop world... + */ +gboolean +G_main_del_TriggerHandler(GTRIGSource* trig_src) +{ + GSource* source = (GSource*) trig_src; + + if (trig_src->gsourceid <= 0) { + return FALSE; + } + trig_src->gsourceid = 0; + trig_src->manual_trigger = FALSE; + g_source_remove(trig_src->gsourceid); + g_source_unref(source); + + return TRUE; +} + +static gboolean +G_TRIG_prepare(GSource* source, gint* timeout) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + + + if (trig_src->manual_trigger + && cmp_longclock(lc_fetch(trig_src->detecttime), zero_longclock) == 0) { + lc_store((trig_src->detecttime), time_longclock()); + } + return trig_src->manual_trigger; +} + +/* + * Did we notice any I/O events? + */ + +static gboolean +G_TRIG_check(GSource* source) +{ + + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + if (trig_src->manual_trigger + && cmp_longclock(lc_fetch(trig_src->detecttime), zero_longclock) == 0) { + lc_store((trig_src->detecttime), time_longclock()); + } + return trig_src->manual_trigger; +} + +/* + * Some kind of event occurred - notify the user. + */ +static gboolean +G_TRIG_dispatch(GSource * source, + GSourceFunc callback, + gpointer user_data) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + longclock_t dispstart; + + g_assert(IS_TRIGSOURCE(trig_src)); + CHECK_DISPATCH_DELAY(trig_src); + + trig_src->manual_trigger = FALSE; + + if(trig_src->dispatch) { + if(!(trig_src->dispatch(trig_src->udata))){ + G_main_del_TriggerHandler(trig_src); + CHECK_DISPATCH_TIME(trig_src); + return FALSE; + } + CHECK_DISPATCH_TIME(trig_src); + } + lc_store((trig_src->detecttime), zero_longclock); + + return TRUE; +} + +/* + * Free up our data, and notify the user process... + */ +static void +G_TRIG_destroy(GSource* source) +{ + GTRIGSource* trig_src = (GTRIGSource*)source; + + g_assert(IS_TRIGSOURCE(trig_src)); + trig_src->gsourceid = 0; + + if (trig_src->dnotify) { + trig_src->dnotify(trig_src->udata); + } +} +/* + * Glib mainloop timeout handling code. + * + * These functions work correctly even if someone resets the + * time-of-day clock. The g_main_timeout_add() function does not have + * this property, since it relies on gettimeofday(). + * + * Our functions have the same semantics - except they always work ;-) + * + * This is because we use longclock_t for our time values. + * + */ + + +static gboolean +Gmain_timeout_prepare(GSource* src, gint* timeout); + +static gboolean +Gmain_timeout_check(GSource* src); + +static gboolean +Gmain_timeout_dispatch(GSource* src, GSourceFunc func, gpointer user_data); + +static GSourceFuncs Gmain_timeout_funcs = { + Gmain_timeout_prepare, + Gmain_timeout_check, + Gmain_timeout_dispatch, +}; + + +struct GTimeoutAppend { + COMMON_STRUCTSTART; + longclock_t nexttime; + guint interval; +}; + +#define GTIMEOUT(GS) ((struct GTimeoutAppend*)((void*)(GS))) + +guint +Gmain_timeout_add(guint interval +, GSourceFunc function +, gpointer data) +{ + return Gmain_timeout_add_full(G_PRIORITY_DEFAULT + , interval, function, data, NULL); +} + +guint +Gmain_timeout_add_full(gint priority +, guint interval +, GSourceFunc function +, gpointer data +, GDestroyNotify notify) +{ + + struct GTimeoutAppend* append; + + GSource* source = g_source_new( &Gmain_timeout_funcs, + sizeof(struct GTimeoutAppend)); + + append = GTIMEOUT(source); + append->magno = MAG_GTIMEOUTSRC; + append->maxdispatchms = DEFAULT_MAXDISPATCH; + append->maxdispatchdelayms = DEFAULT_MAXDELAY; + append->description = "(timeout)"; + lc_store((append->detecttime), zero_longclock); + append->udata = NULL; + + append->nexttime = add_longclock(time_longclock() + , msto_longclock(interval)); + append->interval = interval; + + g_source_set_priority(source, priority); + + g_source_set_can_recurse(source, FALSE); + + g_source_set_callback(source, function, data, notify); + + append->gsourceid = g_source_attach(source, NULL); + g_source_unref(source); + return append->gsourceid; + +} + +void +Gmain_timeout_remove(guint tag) +{ + GSource* source = g_main_context_find_source_by_id(NULL,tag); + struct GTimeoutAppend* append = GTIMEOUT(source); + + if (source == NULL){ + cl_log(LOG_ERR, "Attempt to remove timeout (%u)" + " with NULL source", tag); + }else{ + g_assert(IS_TIMEOUTSRC(append)); + g_source_remove(tag); + } + + return; +} + +/* g_main_loop-style prepare function */ +static gboolean +Gmain_timeout_prepare(GSource* src, gint* timeout) +{ + + struct GTimeoutAppend* append = GTIMEOUT(src); + longclock_t lnow = time_longclock(); + longclock_t remain; + + g_assert(IS_TIMEOUTSRC(append)); + if (cmp_longclock(lnow, append->nexttime) >= 0) { + *timeout = 0L; + return TRUE; + } + /* This is safe - we will always have a positive result */ + remain = sub_longclock(append->nexttime, lnow); + /* This is also safe - we started out in 'ms' */ + *timeout = longclockto_ms(remain); + return ((*timeout) == 0); +} + +/* g_main_loop-style check function */ +static gboolean +Gmain_timeout_check (GSource* src) +{ + struct GTimeoutAppend* append = GTIMEOUT(src); + longclock_t lnow = time_longclock(); + + g_assert(IS_TIMEOUTSRC(append)); + if (cmp_longclock(lnow, append->nexttime) >= 0) { + return TRUE; + } + return FALSE; +} + +/* g_main_loop-style dispatch function */ +static gboolean +Gmain_timeout_dispatch(GSource* src, GSourceFunc func, gpointer user_data) +{ + struct GTimeoutAppend* append = GTIMEOUT(src); + longclock_t dispstart; + gboolean ret; + + g_assert(IS_TIMEOUTSRC(append)); + lc_store(append->detecttime, append->nexttime); + CHECK_DISPATCH_DELAY(append); + + + /* Schedule our next dispatch */ + append->nexttime = add_longclock(time_longclock() + , msto_longclock(append->interval)); + + /* Then call the user function */ + ret = func(user_data); + + CHECK_DISPATCH_TIME(append); + return ret; +} +void +G_main_setmaxdispatchdelay(GSource* s, unsigned long delayms) +{ + GFDSource* fdp = (GFDSource*)s; + if (!IS_ONEOFOURS(fdp)) { + cl_log(LOG_ERR + , "Attempt to set max dispatch delay on wrong object"); + return; + } + fdp->maxdispatchdelayms = delayms; +} +void +G_main_setmaxdispatchtime(GSource* s, unsigned long dispatchms) +{ + GFDSource* fdp = (GFDSource*)s; + if (!IS_ONEOFOURS(fdp)) { + cl_log(LOG_ERR + , "Attempt to set max dispatch time on wrong object"); + return; + } + fdp->maxdispatchms = dispatchms; +} +void +G_main_setdescription(GSource* s, const char * description) +{ + GFDSource* fdp = (GFDSource*)s; + if (!IS_ONEOFOURS(fdp)) { + cl_log(LOG_ERR + , "Attempt to set max dispatch time on wrong object"); + return; + } + fdp->description = description; +} +void +G_main_setmaxdispatchdelay_id(guint id, unsigned long delayms) +{ + GSource* source = g_main_context_find_source_by_id(NULL,id); + + if (source) { + G_main_setmaxdispatchdelay(source, delayms); + } +} +void +G_main_setmaxdispatchtime_id(guint id, unsigned long dispatchms) +{ + GSource* source = g_main_context_find_source_by_id(NULL,id); + + if (source) { + G_main_setmaxdispatchtime(source, dispatchms); + } +} +void +G_main_setdescription_id(guint id, const char * description) +{ + GSource* source = g_main_context_find_source_by_id(NULL,id); + + if (source) { + G_main_setdescription(source, description); + } +} +void +G_main_setall_id(guint id, const char * description, unsigned long delay +, unsigned long elapsed) +{ + G_main_setdescription_id(id, description); + G_main_setmaxdispatchdelay_id(id, delay); + G_main_setmaxdispatchtime_id(id, elapsed); +} + +static void TempProcessRegistered(ProcTrack* p); +static void TempProcessDied(ProcTrack* p, int status, int signo +, int exitcode, int waslogged); +static const char* TempProcessName(ProcTrack* p); + +/*********************************************************************** + * Track our temporary child processes... + * + * We run no more than one of each type at once. + * If we need to run some and one is still running we run another one + * when this one exits. + * + * Requests to run a child process don't add up. So, 3 requests to run + * a child while one is running only cause it to be run once more, not + * three times. + * + * The only guarantee is that a new child process will run after a request + * was made. + * + * To create the possibility of running a particular type of child process + * call G_main_add_tempproc_trigger(). + * + * To cause it to be run, call G_main_set_trigger(). + * + ***********************************************************************/ + +static ProcTrack_ops TempProcessTrackOps = { + TempProcessDied, + TempProcessRegistered, + TempProcessName +}; + +/* + * Information for tracking our generic temporary child processes. + */ +struct tempproc_track { + const char * procname; /* name of the process*/ + GTRIGSource* trigger; /* Trigger for this event */ + int (*fun)(gpointer userdata); /* Function to call + * in child process */ + void (*prefork)(gpointer userdata);/* Call before fork */ + void (*postfork)(gpointer userdata);/* Call after fork */ + void (*complete)(gpointer userdata, int status, int signo, int exitcode);/* Call after complete */ + gpointer userdata; /* Info to pass 'fun' */ + gboolean isrunning; /* TRUE if child is running */ + gboolean runagain; /* TRUE if we need to run + * again after child process + * finishes. + */ +}; +static void +TempProcessRegistered(ProcTrack* p) +{ + return; /* Don't need to do much here... */ +} + +static void +TempProcessDied(ProcTrack* p, int status, int signo, int exitcode +, int waslogged) +{ + struct tempproc_track * pt = p->privatedata; + + if (pt->complete) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling 'complete' for temp process %s" + , __FUNCTION__, pt->procname); + } + pt->complete(pt->userdata, status, signo, exitcode); + } + + pt->isrunning=FALSE; + if (pt->runagain) { + pt->runagain=FALSE; + + /* Do it again, Sam! */ + G_main_set_trigger(pt->trigger); + + /* Note that we set the trigger for this, we don't + * fork or call the function now. + * + * This allows the mainloop scheduler to decide + * when the fork should happen according to the priority + * of this trigger event - NOT according to the priority + * of general SIGCHLD handling. + */ + } + p->privatedata = NULL; /* Don't free until trigger is destroyed */ + return; +} + +static const char * +TempProcessName(ProcTrack* p) +{ + struct tempproc_track * pt = p->privatedata; + return pt->procname; +} +/* + * Make sure only one copy is running at a time... + */ +static gboolean +TempProcessTrigger(gpointer ginfo) +{ + struct tempproc_track* info = ginfo; + int pid; + + /* Make sure only one copy is running at a time. */ + /* This avoids concurrency problems. */ + if (info->isrunning) { + info->runagain = TRUE; + return TRUE; + } + info->isrunning = TRUE; + + if (info->prefork) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling prefork for temp process %s" + , __FUNCTION__, info->procname); + } + info->prefork(info->userdata); + } + if (ANYDEBUG) { + cl_log(LOG_DEBUG, "Forking temp process %s", info->procname); + } + switch ((pid=fork())) { + int rc; + case -1: cl_perror("%s: Can't fork temporary child" + " process [%s]!", __FUNCTION__ + , info->procname); + info->isrunning = FALSE; + break; + + case 0: /* Child */ + if ((rc=info->fun(info->userdata)) == HA_OK) { + exit(0); + } + cl_log(LOG_WARNING + , "%s: %s returns %d", __FUNCTION__ + , info->procname, rc); + exit(1); + break; + default: + /* Fall out */; + + } + if (pid > 0) { + NewTrackedProc(pid, 0, (ANYDEBUG? PT_LOGVERBOSE : PT_LOGNORMAL) + , ginfo, &TempProcessTrackOps); + if (info->postfork) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: Calling postfork for temp process %s" + , __FUNCTION__, info->procname); + } + info->postfork(info->userdata); + } + } + return TRUE; +} + +static void +tempproc_destroy_notify(gpointer userdata) +{ + if (userdata != NULL) { + free(userdata); + userdata = NULL; + } +} + +GTRIGSource* +G_main_add_tempproc_trigger(int priority +, int (*triggerfun) (gpointer p) +, const char * procname +, gpointer userdata +, void (*prefork)(gpointer p) +, void (*postfork)(gpointer p) +, void (*complete)(gpointer userdata, int status, int signo, int exitcode)) +{ + + struct tempproc_track* p; + GTRIGSource* ret; + + p = (struct tempproc_track *) malloc(sizeof(struct tempproc_track)); + if (p == NULL) { + return NULL; + } + + memset(p, 0, sizeof(*p)); + p->procname = procname; + p->fun = triggerfun; + p->userdata = userdata; + p->prefork = prefork; + p->postfork = postfork; + p->complete = complete; + + ret = G_main_add_TriggerHandler(priority + , TempProcessTrigger, p, tempproc_destroy_notify); + + if (ret == NULL) { + free(p); + p = NULL; + }else{ + p->trigger = ret; + } + return ret; +} diff --git a/lib/clplumbing/Makefile.am b/lib/clplumbing/Makefile.am new file mode 100644 index 0000000..1b504fc --- /dev/null +++ b/lib/clplumbing/Makefile.am @@ -0,0 +1,99 @@ +# +# plumbing: OCF general plumbing libraries +# +# Copyright (C) 2002 Alan Robertson, International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + + +halibdir = $(libdir)/@HB_PKG@ + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +## libraries + +lib_LTLIBRARIES = libplumb.la libplumbgpl.la + +libplumb_la_SOURCES = \ + base64.c \ + cl_compress.c \ + cl_log.c \ + cl_misc.c \ + cl_msg.c \ + cl_msg_types.c \ + cl_netstring.c \ + cl_pidfile.c \ + cl_poll.c \ + cl_random.c \ + cl_signal.c \ + cl_syslog.c \ + cl_uuid.c \ + cl_plugin.c \ + cl_reboot.c \ + coredumps.c \ + cpulimits.c \ + GSource.c \ + ipcsocket.c \ + longclock.c \ + md5.c \ + mkstemp_mode.c \ + ocf_ipc.c \ + proctrack.c \ + realtime.c \ + replytrack.c \ + timers.c \ + uids.c + +libplumb_la_LIBADD = $(top_builddir)/replace/libreplace.la \ + $(top_builddir)/lib/pils/libpils.la +libplumb_la_LDFLAGS = -version-info 3:0:1 + +libplumbgpl_la_SOURCES = setproctitle.c +libplumbgpl_la_LIBADD = $(top_builddir)/replace/libreplace.la \ + $(top_builddir)/lib/pils/libpils.la +libplumbgpl_la_LDFLAGS = -version-info 2:0:0 + +testdir = $(libdir)/@HB_PKG@ +test_PROGRAMS = ipctest ipctransientclient ipctransientserver base64_md5_test +test_SCRIPTS = transient-test.sh + +ipctest_SOURCES = ipctest.c +ipctest_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) \ + $(top_builddir)/lib/pils/libpils.la + +noinst_HEADERS = ipctransient.h + +#ipctransient_SOURCES = ipctransient.c +#ipctransient_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(top_builddir)/heartbeat/libhbclient.la $(GLIBLIB) + +ipctransientclient_SOURCES = ipctransientclient.c ipctransientlib.c +ipctransientclient_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) \ + $(top_builddir)/lib/pils/libpils.la + +ipctransientserver_SOURCES = ipctransientserver.c ipctransientlib.c +ipctransientserver_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) \ + $(top_builddir)/lib/pils/libpils.la + +#netstring_test_SOURCES = netstring_test.c +#netstring_test_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la libhbclient.la $(gliblib) + +base64_md5_test_SOURCES = base64_md5_test.c +base64_md5_test_LDADD = libplumb.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +EXTRA_DIST = $(test_SCRIPTS) diff --git a/lib/clplumbing/base64.c b/lib/clplumbing/base64.c new file mode 100644 index 0000000..c8ad325 --- /dev/null +++ b/lib/clplumbing/base64.c @@ -0,0 +1,422 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include "clplumbing/base64.h" + +/* + * + * Base64 conversion functions. + * They convert from a binary array into a single string + * in base 64. This is almost (but not quite) like section 5.2 of RFC 1341 + * The only difference is that we don't care about line lengths. + * We do use their encoding algorithm. + * + */ + + +static char b64chars[] += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define EQUALS '=' +#define MASK6 (077) +#define MASK24 (077777777) + + +/* Convert from binary to a base64 string (~ according to RFC1341) */ +int +binary_to_base64(const void * data, int nbytes, char * output, int outlen) +{ + int requiredlen = B64_stringlen(nbytes)+1; /* EOS */ + char * outptr; + const unsigned char * inmax; + const unsigned char * inlast; + const unsigned char * inptr; + int bytesleft; + + if (outlen < requiredlen) { + syslog(LOG_ERR, "binary_to_base64: output area too small."); + return -1; + } + + inptr = data; + /* Location of last whole 3-byte chunk */ + inmax = inptr + ((nbytes / B64inunit)*B64inunit); + inlast = inptr + nbytes; + outptr = output; + + + /* Convert whole 3-byte chunks */ + for (;inptr < inmax; inptr += B64inunit) { + unsigned long chunk; + unsigned int sixbits; + + chunk = ((*inptr) << 16 + | ((*(inptr+1)) << 8) + | (*(inptr+2))) & MASK24; + + sixbits = (chunk >> 18) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + sixbits = (chunk >> 12) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + sixbits = (chunk >> 6) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + sixbits = (chunk & MASK6); + *outptr = b64chars[sixbits]; ++outptr; + } + + /* Do we have anything left over? */ + + bytesleft = inlast - inptr; + if (bytesleft > 0) { + /* bytesleft can only be 1 or 2 */ + + unsigned long chunk; + unsigned int sixbits; + + + /* Grab first byte */ + chunk = (*inptr) << 16; + + if (bytesleft == 2) { + /* Grab second byte */ + chunk |= ((*(inptr+1)) << 8); + } + chunk &= MASK24; + + /* OK, now we have our chunk... */ + sixbits = (chunk >> 18) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + sixbits = (chunk >> 12) & MASK6; + *outptr = b64chars[sixbits]; ++outptr; + + if (bytesleft == 2) { + sixbits = (chunk >> 6) & MASK6; + *outptr = b64chars[sixbits]; + }else{ + *outptr = EQUALS; + } + ++outptr; + + *outptr = EQUALS; ++outptr; + } + *outptr = EOS; /* Don't increment */ + return (outptr - output); +} + + +/* This macro is only usable by the base64_to_binary() function. + * + * There are faster ways of doing this, but I didn't spend the time + * to implement one of them. If we have an array of six bit values, + * sized by 256 or so, then we could look it up. + * FIXME: This is how it really ought to be done... + */ + +static unsigned char b64values [256]; +#define BADVALUE 0xff + +static void +init_b64_values(void) +{ + int j; + memset(b64values, BADVALUE, sizeof(b64values)); + + for (j=0; b64chars[j] != EOS; ++j) { + b64values[(int)b64chars[j]] = (unsigned char)j; + } +} + + +#define Char2SixBits(in, out) { \ + unsigned char ch; \ + ch = b64values[(unsigned int)in]; \ + if (ch == BADVALUE) { \ + syslog(LOG_ERR \ + , "base64_to_binary: invalid input [%c]!" \ + , in); \ + return -1; \ + } \ + out = ch; \ + } \ + + +/* Convert from a base64 string (~ according to RFC1341) to binary */ +int +base64_to_binary(const char * in, int inlen, void * output, int outlen) +{ + int maxbinlen = B64_maxbytelen(inlen); /* Worst case size */ + const char * input = in; + const char * lastinput = in + inlen - B64outunit; + int equalcount = 0; + unsigned char * startout; + unsigned char * out; + unsigned sixbits1; + unsigned sixbits2; + unsigned sixbits3; + unsigned sixbits4; + unsigned long chunk; + static int inityet = 0; + + if (!inityet) { + inityet=1; + init_b64_values(); + } + + /* Make sure we have enough room */ + if (outlen < maxbinlen) { + int residue = maxbinlen - outlen; + + if (residue > 2 + || input[inlen-1] != EQUALS + || (residue == 2 && input[inlen-2] != EQUALS)) { + syslog(LOG_ERR + , "base64_to_binary: output area too small."); + return -1; + } + } + if ((inlen % 4) != 0) { + syslog(LOG_ERR + , "base64_to_binary: input length invalid."); + return -1; + } + + if (inlen == 0) { + return 0; + } + + /* We have enough space. We are happy :-) */ + + startout = out = (unsigned char *)output; + + while (input < lastinput) { + Char2SixBits(*input, sixbits1); ++input; + Char2SixBits(*input, sixbits2); ++input; + Char2SixBits(*input, sixbits3); ++input; + Char2SixBits(*input, sixbits4); ++input; + + chunk = (sixbits1 << 18) + | (sixbits2 << 12) | (sixbits3 << 6) | sixbits4; + + *out = ((chunk >> 16) & 0xff); ++out; + *out = ((chunk >> 8) & 0xff); ++out; + *out = (chunk & 0xff); ++out; + } + + /* Process last 4 chars of input (1 to 3 bytes of output) */ + + /* The first two input chars must be normal chars */ + Char2SixBits(*input, sixbits1); ++input; + Char2SixBits(*input, sixbits2); ++input; + + /* We should find one of: (char,char) (char,=) or (=,=) */ + /* We then output: (3 bytes) (2 bytes) (1 byte) */ + + if (*input == EQUALS) { + /* The (=,=): 1 byte case */ + equalcount=2; + sixbits3 = 0; + sixbits4 = 0; + /* We assume the 2nd char is an = sign :-) */ + }else{ + /* We have either the (char,char) or (char,=) case */ + Char2SixBits(*input, sixbits3); ++input; + if (*input == EQUALS) { + /* The (char, =): 2 bytes case */ + equalcount=1; + sixbits4 = 0; + }else{ + /* The (char, char): 3 bytes case */ + Char2SixBits(*input, sixbits4); ++input; + equalcount=0; + } + } + + chunk = (sixbits1 << 18) + | (sixbits2 << 12) | (sixbits3 << 6) | sixbits4; + + /* We always have one more char to output... */ + *out = ((chunk >> 16) & 0xff); ++out; + + if (equalcount < 2) { + /* Zero or one equal signs: total of 2 or 3 bytes output */ + *out = ((chunk >> 8) & 0xff); ++out; + + if (equalcount < 1) { + /* No equal signs: total of 3 bytes output */ + *out = (chunk & 0xff); ++out; + } + } + + return out - startout; +} + +#if 0 +#define RAND(upb) (rand()%(upb)) + +void dumpbin(void * Bin, int length); +void randbin(void * Bin, int length); + +void +dumpbin(void * Bin, int length) +{ + unsigned char * bin = Bin; + + int j; + + for (j=0; j < length; ++j) { + fprintf(stderr, "%02x ", bin[j]); + if ((j % 32) == 31) { + fprintf(stderr, "\n"); + } + } + fprintf(stderr, "\n"); +} + +void +randbin(void * Bin, int length) +{ + unsigned char * bin = Bin; + int j; + + for (j=0; j < length; ++j) { + bin[j] = (unsigned char)RAND(256); + } + +} + +#define MAXLEN 320 +#define MAXSTRING B64_stringlen(MAXLEN)+1 +#define MAXITER 300000 +int +main(int argc, char ** argv) +{ + int errcount = 0; + char origbin[MAXLEN+1]; + char sourcebin[MAXLEN+1]; + char destbin[MAXLEN+1]; + char deststr[MAXSTRING]; + int maxiter = MAXITER; + int j; + + for (j=0; j < maxiter; ++j) { + int iterlen = RAND(MAXLEN+1); + int slen; + int blen; + + if ((j%100) == 99) { + fprintf(stderr, "+"); + } + + memset(origbin, 0, MAXLEN+1); + memset(sourcebin, 0, MAXLEN+1); + memset(destbin, 0, MAXLEN+1); + randbin(origbin, iterlen); + origbin[iterlen] = 0x1; + memcpy(sourcebin, origbin, iterlen); + sourcebin[iterlen] = 0x2; + slen = binary_to_base64(sourcebin, iterlen, deststr, MAXSTRING); + if (slen < 0) { + fprintf(stderr + , "binary_to_base64 failure: length %d\n" + , iterlen); + ++errcount; + continue; + } + if (strlen(deststr) != slen) { + fprintf(stderr + , "binary_to_base64 failure: length was %d (strlen) vs %d (ret value)\n" + , strlen(deststr), slen); + fprintf(stderr, "binlen: %d, deststr: [%s]\n" + , iterlen, deststr); + continue; + ++errcount; + } + destbin[iterlen] = 0x3; + blen = base64_to_binary(deststr, slen, destbin, iterlen); + + if (blen != iterlen) { + fprintf(stderr + , "base64_to_binary failure: length was %d vs %d\n" + , blen, iterlen); + dumpbin(origbin, iterlen); + fprintf(stderr + , "Base64 intermediate: [%s]\n", deststr); + ++errcount; + continue; + } + if (memcmp(destbin, origbin, iterlen) != 0) { + fprintf(stderr + , "base64_to_binary mismatch. Orig:\n"); + dumpbin(origbin, iterlen); + fprintf(stderr, "Dest:\n"); + dumpbin(destbin, iterlen); + fprintf(stderr + , "Base64 intermediate: [%s]\n", deststr); + ++errcount; + } + if (destbin[iterlen] != 0x3) { + fprintf(stderr + , "base64_to_binary corruption. dest byte: 0x%02x\n" + , destbin[iterlen]); + ++errcount; + } + + if (sourcebin[iterlen] != 0x2) { + fprintf(stderr + , "base64_to_binary corruption. source byte: 0x%02x\n" + , sourcebin[iterlen]); + ++errcount; + } + sourcebin[iterlen] = 0x0; + origbin[iterlen] = 0x0; + if (memcmp(sourcebin, origbin, MAXLEN+1) != 0) { + fprintf(stderr + , "base64_to_binary corruption. origbin:\n"); + dumpbin(origbin, MAXLEN+1); + fprintf(stderr, "sourcebin:\n"); + dumpbin(sourcebin, MAXLEN+1); + ++errcount; + } + + } + + fprintf(stderr, "\n%d iterations, %d errors.\n" + , maxiter, errcount); + + return errcount; +} +/* HA-logging function */ +void +ha_log(int priority, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + + va_start(ap, fmt); + vsnprintf(buf, MAXLINE, fmt, ap); + va_end(ap); + + fprintf(stderr, "%s\n", buf); + +} +#endif diff --git a/lib/clplumbing/base64_md5_test.c b/lib/clplumbing/base64_md5_test.c new file mode 100644 index 0000000..d536776 --- /dev/null +++ b/lib/clplumbing/base64_md5_test.c @@ -0,0 +1,113 @@ +/* File: base64_md5_test.c + * Description: base64 and md5 algorithm tests + * + * Author: Sun Jiang Dong + * Copyright (c) 2005 International Business Machines + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +#define MD5LEN 16 /* md5 buffer */ +#define BASE64_BUF_LEN 32 + +/* gcc -o base64_md5_test base64_md5_test.c -lplumb */ +int main(void) +{ + int error_count = 0; + + const char base64_encode[] = "YWJjZGVmZ2g="; + const char raw_data[] = "abcdefgh"; + + /* A test case from + * RFC 1321 - The MD5 Message-Digest Algorithm + */ + const char * data1 = "message digest"; + const char digest_rfc1321[(MD5LEN+1)*2+1] + = "f96b697d7cb7938d525a2f31aaf161d0"; + + /* A test case from + * RFC 2104 - HMAC: Keyed-Hashing for Message Authentication + */ + const char *key = "Jefe"; + const char *data2 = "what do ya want for nothing?"; + const char digest_rfc2104[(MD5LEN+1)*2+1] + = "750c783e6ab0b503eaa86e310a5db738"; + + char buffer_tmp[BASE64_BUF_LEN]; + + char md[(MD5LEN+1)*2+1]; + unsigned char digest[MD5LEN]; + char * md_tmp; + int rc,i; + + /* base64 encode test */ + binary_to_base64(raw_data, strlen(raw_data), buffer_tmp + , BASE64_BUF_LEN); + /* printf("base64_encode = %s\n", buffer_tmp); */ + if (0 != strncmp(buffer_tmp, base64_encode, strlen(buffer_tmp)) ) { + cl_log(LOG_ERR, "binary_to_base64 works bad."); + error_count++; + } + + /* base64 decode test */ + memset(buffer_tmp, 0, BASE64_BUF_LEN); + base64_to_binary(base64_encode, strlen(base64_encode) + , buffer_tmp, BASE64_BUF_LEN); + /* printf("decoded data= %s\n", buffer_tmp); */ + if (0 != strncmp(buffer_tmp, raw_data, strlen(buffer_tmp)) ) { + cl_log(LOG_ERR, "base64_to_binary works bad."); + error_count++; + } + + rc = MD5((const unsigned char *)data1, strlen(data1), digest); + + md_tmp = md; + for (i = 0; i < MD5LEN; i++) { + snprintf(md_tmp, sizeof(md), "%02x", digest[i]); + md_tmp += 2; + } + *md_tmp = '\0'; + /* printf("rc=%d MD5=%s\n", rc, md); */ + + if (0 != strncmp(md, digest_rfc1321, MD5LEN*2) ) { + cl_log(LOG_ERR, "The md5-rfc1321 algorithm works bad."); + error_count++; + } + + rc = HMAC((const unsigned char *)key, strlen(key) + , (const unsigned char *)data2, strlen(data2), digest); + md_tmp = md; + for (i = 0; i < MD5LEN; i++) { + sprintf(md_tmp,"%02x", digest[i]); + md_tmp += 2; + } + *md_tmp = '\0'; + /* printf("rc=%d HMAC=%s\n", rc, md); */ + + if (0 != strncmp(md, digest_rfc2104, MD5LEN*2) ) { + cl_log(LOG_ERR, "The md5-rfc2104 algorithm works bad."); + error_count++; + } + + (void) rc; /* Suppress -Werror=unused-but-set-variable */ + return error_count; +} diff --git a/lib/clplumbing/cl_compress.c b/lib/clplumbing/cl_compress.c new file mode 100644 index 0000000..6b56ad6 --- /dev/null +++ b/lib/clplumbing/cl_compress.c @@ -0,0 +1,500 @@ + +/* + * compress.c: Compression functions for Linux-HA + * + * Copyright (C) 2005 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Compression is designed to handle big messages, right now with 4 nodes + * cib message can go up to 64 KB or more. I expect much larger messages + * when the number of node increase. This makes message compression necessary. + * + * + * Compression is handled in field level. One can add a struct field using + * ha_msg_addstruct() -- the field will not get compressed, or using + * ha_msg_addstruct_compress(), and the field will get compressed when + * the message is converted to wire format, i.e. when msg2wirefmt() is called. + * The compressed field will stay compressed until it reached the desination. + * It will finally decompressed when the user start to get the field value. + * It is designed this way so that the compression/decompression only happens + * in end users so that heartbeat itself can save cpu cycle and memory. + * (more info about compression can be found in cl_msg_types.c about FT_COMPRESS + * FT_UNCOMPRESS types) + * + * compression has another legacy mode, which is there so it can be compatible + * to old ways of compression. In the old way, no field is compressed individually + * and the messages is compressed before it is sent out, and it will be decompressed + * in the receiver side immediately. So in each IPC channel, the message is compressed + * and decompressed once. This way will cost a lot of cpu time and memory and it is + * discouraged. + * + * If use_traditional_compression is true, then it is using the legacy mode, otherwise + * it is using the new compression. For back compatibility, the default is legacy mode. + * + * The real compression work is done by compression plugins. There are two plugins right + * now: zlib and bz2, they are in lib/plugins/compress + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define COMPRESSED_FIELD "_compressed_payload" +#define COMPRESS_NAME "_compression_algorithm" +#define HACOMPRESSNAME "HA_COMPRESSION" +#define DFLT_COMPRESS_PLUGIN "bz2" + +static struct hb_compress_fns* msg_compress_fns = NULL; +static char* compress_name = NULL; +GHashTable* CompressFuncs = NULL; + +static PILGenericIfMgmtRqst Reqs[] = + { + {"compress", &CompressFuncs, NULL, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} + }; + +static PILPluginUniv* CompressPIsys = NULL; + +static int +init_pluginsys(void){ + + if (CompressPIsys) { + return TRUE; + } + + CompressPIsys = NewPILPluginUniv(HA_PLUGIN_DIR); + + if (CompressPIsys) { + if (PILLoadPlugin(CompressPIsys, PI_IFMANAGER, "generic", Reqs) + != PIL_OK){ + cl_log(LOG_ERR, "generic plugin load failed\n"); + DelPILPluginUniv(CompressPIsys); + CompressPIsys = NULL; + } + }else{ + cl_log(LOG_ERR, "pi univ creation failed\n"); + } + return CompressPIsys != NULL; + +} + +int +cl_compress_remove_plugin(const char* pluginname) +{ + return HA_OK; +} + +int +cl_compress_load_plugin(const char* pluginname) +{ + struct hb_compress_fns* funcs = NULL; + + if (!init_pluginsys()){ + return HA_FAIL; + } + + if ((funcs = g_hash_table_lookup(CompressFuncs, pluginname)) + == NULL){ + if (PILPluginExists(CompressPIsys, HB_COMPRESS_TYPE_S, + pluginname) == PIL_OK){ + PIL_rc rc; + if ((rc = PILLoadPlugin(CompressPIsys, + HB_COMPRESS_TYPE_S, + pluginname, + NULL))!= PIL_OK){ + cl_log(LOG_ERR, + "Cannot load compress plugin %s[%s]", + pluginname, + PIL_strerror(rc)); + return HA_FAIL; + } + funcs = g_hash_table_lookup(CompressFuncs, + pluginname); + } + + } + if (funcs == NULL){ + cl_log(LOG_ERR, "Compression module(%s) not found", pluginname); + return HA_FAIL; + } + + /* set the environment variable so that later programs can + * load the appropriate plugin + */ + setenv(HACOMPRESSNAME,pluginname,1); + msg_compress_fns = funcs; + + return HA_OK; +} + +int +cl_set_compress_fns(const char* pluginname) +{ + /* this function was unnecessary duplication of the + * code in cl_compress_load_plugin + */ + return cl_compress_load_plugin(pluginname); +} + +struct hb_compress_fns* +cl_get_compress_fns(void) +{ + static int try_dflt = 1; + + if (try_dflt && !msg_compress_fns) { + try_dflt = 0; + cl_log(LOG_INFO, "%s: user didn't set compression type, " + "loading %s plugin", + __FUNCTION__, DFLT_COMPRESS_PLUGIN); + cl_compress_load_plugin(DFLT_COMPRESS_PLUGIN); + } + return msg_compress_fns; +} + +static struct hb_compress_fns* +get_compress_fns(const char* pluginname) +{ + struct hb_compress_fns* funcs = NULL; + + if (cl_compress_load_plugin(pluginname) != HA_OK){ + cl_log(LOG_ERR, "%s: loading compression module" + "(%s) failed", + __FUNCTION__, pluginname); + return NULL; + } + + funcs = g_hash_table_lookup(CompressFuncs, pluginname); + return funcs; +} + +void cl_realtime_malloc_check(void); + +char* +cl_compressmsg(struct ha_msg* m, size_t* len) +{ + char* src; + char* dest; + size_t destlen; + int rc; + char* ret = NULL; + struct ha_msg* tmpmsg; + size_t datalen; + + destlen = MAXMSG; + + dest = malloc(destlen); + if (!dest) { + cl_log(LOG_ERR, "%s: failed to allocate destination buffer", + __FUNCTION__); + return NULL; + } + + if (msg_compress_fns == NULL){ + cl_log(LOG_ERR, "%s: msg_compress_fns is NULL!", + __FUNCTION__); + goto out; + } + if ( get_netstringlen(m) > MAXUNCOMPRESSED + || get_stringlen(m) > MAXUNCOMPRESSED){ + cl_log(LOG_ERR, "%s: msg too big(stringlen=%d," + "netstringlen=%d)", + __FUNCTION__, + get_stringlen(m), + get_netstringlen(m)); + goto out; + } + + + if ((src = msg2wirefmt_noac(m, &datalen)) == NULL){ + cl_log(LOG_ERR,"%s: converting msg" + " to wirefmt failed", __FUNCTION__); + goto out; + } + + rc = msg_compress_fns->compress(dest, &destlen, + src, datalen); + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + goto out; + } + + free(src); + + tmpmsg =ha_msg_new(0); + rc = ha_msg_addbin(tmpmsg, COMPRESSED_FIELD, dest, destlen)/*discouraged function*/; + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: adding binary to msg failed", + __FUNCTION__); + goto out; + } + + rc = ha_msg_add(tmpmsg, COMPRESS_NAME, + msg_compress_fns->getname()); + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: adding compress name to msg failed", + __FUNCTION__); + goto out; + } + + + ret = msg2netstring(tmpmsg, len); + ha_msg_del(tmpmsg); + +#if 0 + cl_log(LOG_INFO, "------original stringlen=%d, netstringlen=%d," + "compressed_datalen=%d,current len=%d", + get_stringlen(m), get_netstringlen(m),(int)destlen, (int)*len); + +#endif + +out: + if (dest) { + free(dest); + } + + return ret; +} + + +gboolean +is_compressed_msg(struct ha_msg* m) +{ + if( cl_get_binary(m, COMPRESSED_FIELD, NULL) /*discouraged function*/ + != NULL){ + return TRUE; + } + + return FALSE; + +} + +/* the decompressmsg function is not exactly the reverse + * operation of compressmsg, it starts when the prorgram + * detects there is compressed_field in a msg + */ + +struct ha_msg* +cl_decompressmsg(struct ha_msg* m) +{ + const char* src; + size_t srclen; + char *dest = NULL; + size_t destlen = MAXUNCOMPRESSED; + int rc; + struct ha_msg* ret = NULL; + const char* decompress_name; + struct hb_compress_fns* funcs = NULL; + + dest = malloc(destlen); + + if (!dest) { + cl_log(LOG_ERR, "%s: Failed to allocate buffer.", __FUNCTION__); + return NULL; + } + + if (m == NULL){ + cl_log(LOG_ERR, "%s: NULL message", __FUNCTION__); + goto out; + } + src = cl_get_binary(m, COMPRESSED_FIELD, &srclen)/*discouraged function*/; + if (src == NULL){ + cl_log(LOG_ERR, "%s: compressed-field is NULL", + __FUNCTION__); + goto out; + } + + if (srclen > MAXMSG){ + cl_log(LOG_ERR, "%s: field too long(%d)", + __FUNCTION__, (int)srclen); + goto out; + } + + decompress_name = ha_msg_value(m, COMPRESS_NAME); + if (decompress_name == NULL){ + cl_log(LOG_ERR, "compress name not found"); + goto out; + } + + + funcs = get_compress_fns(decompress_name); + + if (funcs == NULL){ + cl_log(LOG_ERR, "%s: compress method(%s) is not" + " supported in this machine", + __FUNCTION__, decompress_name); + goto out; + } + + rc = funcs->decompress(dest, &destlen, src, srclen); + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + goto out; + } + + ret = wirefmt2msg(dest, destlen, 0); + +#if 0 + cl_log(LOG_INFO, "%s: srclen =%d, destlen=%d", + __FUNCTION__, + srclen, destlen); +#endif + +out: + if (dest) { + free(dest); + } + + return ret; +} + + +int +cl_decompress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen) +{ + char* value; + int vallen; + int rc; + const char* decompress_name; + struct hb_compress_fns* funcs; + + if ( msg == NULL|| index >= msg->nfields){ + cl_log(LOG_ERR, "%s: wrong argument", + __FUNCTION__); + return HA_FAIL; + } + + value = msg->values[index]; + vallen = msg->vlens[index]; + + decompress_name = ha_msg_value(msg, COMPRESS_NAME); + if (decompress_name == NULL){ + cl_log(LOG_ERR, "compress name not found"); + return HA_FAIL; + } + + + funcs = get_compress_fns(decompress_name); + + if (funcs == NULL){ + cl_log(LOG_ERR, "%s: compress method(%s) is not" + " supported in this machine", + __FUNCTION__, decompress_name); + return HA_FAIL; + } + + rc = funcs->decompress(buf, buflen, value, vallen); + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + return HA_FAIL; + } + + return HA_OK; +} + + +int +cl_compress_field(struct ha_msg* msg, int index, char* buf, size_t* buflen) +{ + char* src; + size_t srclen; + int rc; + + if ( msg == NULL|| index >= msg->nfields + || msg->types[index] != FT_UNCOMPRESS){ + cl_log(LOG_ERR, "%s: wrong argument", + __FUNCTION__); + return HA_FAIL; + } + + if (msg_compress_fns == NULL){ + if (compress_name == NULL){ + compress_name = getenv(HACOMPRESSNAME); + } + + if (compress_name == NULL){ + cl_log(LOG_ERR, "%s: no compression module name found", + __FUNCTION__); + return HA_FAIL; + } + + if(cl_set_compress_fns(compress_name) != HA_OK){ + cl_log(LOG_ERR, "%s: loading compression module failed", + __FUNCTION__); + return HA_FAIL; + } + } + + if (msg_compress_fns == NULL){ + cl_log(LOG_ERR, "%s: msg_compress_fns is NULL!", + __FUNCTION__); + return HA_FAIL; + } + + src = msg2wirefmt_noac(msg->values[index], &srclen); + if (src == NULL){ + cl_log(LOG_ERR,"%s: converting msg" + " to wirefmt failed", __FUNCTION__); + return HA_FAIL; + } + + rc = msg_compress_fns->compress(buf, buflen, + src, srclen); + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + return HA_FAIL; + } + + + rc = ha_msg_mod(msg, COMPRESS_NAME, + msg_compress_fns->getname()); + + if (rc != HA_OK){ + cl_log(LOG_ERR, "%s: adding compress name to msg failed", + __FUNCTION__); + return HA_FAIL;; + } + + free(src); + src = NULL; + + return HA_OK; + +} diff --git a/lib/clplumbing/cl_log.c b/lib/clplumbing/cl_log.c new file mode 100644 index 0000000..213e760 --- /dev/null +++ b/lib/clplumbing/cl_log.c @@ -0,0 +1,1261 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MAXLINE +# define MAXLINE 512 +#endif +/* + * might not contain LOG_PRI... + * So, we define it ourselves, or error out if we can't... + */ + +#ifndef LOG_PRI +# ifdef LOG_PRIMASK + /* David Lee reports this works on Solaris */ +# define LOG_PRI(p) ((p) & LOG_PRIMASK) +# else +# error "Syslog.h does not define either LOG_PRI or LOG_PRIMASK." +# endif +#endif + +#define DFLT_ENTITY "cluster" +#define DFLT_PREFIX "" +#define NULLTIME 0 +#define QUEUE_SATURATION_FUZZ 10 + +static IPC_Channel* logging_daemon_chan = NULL; +static gboolean syslogformatfile = TRUE; +/* + * If true, then output messages more or less like this... + * Jul 14 21:45:18 beam logd: [1056]: info: setting log file to /dev/null + */ + +int LogToDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); + +static int LogToLoggingDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); +static IPC_Message* ChildLogIPCMessage(int priority, const char *buf, int bstrlen, + gboolean use_priority_str, IPC_Channel* ch); +static void FreeChildLogIPCMessage(IPC_Message* msg); +static gboolean send_dropped_message(gboolean use_pri_str, IPC_Channel *chan); +static int cl_set_logging_wqueue_maxlen(int qlen); + +static int use_logging_daemon = FALSE; +static int conn_logd_time = 0; +static char cl_log_entity[MAXENTITY]= DFLT_ENTITY; +static char cl_log_syslogprefix[MAXENTITY] = DFLT_PREFIX; +static char common_log_entity[MAXENTITY]= DFLT_ENTITY; +static int cl_log_facility = LOG_USER; +static int use_buffered_io = 0; + +static void cl_opensyslog(void); +static int syslog_enabled = 0; +static int stderr_enabled = 0; +static int stdout_enabled = 0; +static const char* logfile_name = NULL; +static const char* debugfile_name = NULL; +static int cl_process_pid = -1; +int debug_level = 0; +static GDestroyNotify destroy_logging_channel_callback; +static void (*create_logging_channel_callback)(IPC_Channel* chan); +static gboolean logging_chan_in_main_loop = FALSE; + +/*********************** + *debug use only, do not use this function in your program + */ +IPC_Channel * get_log_chan(void); + +IPC_Channel* get_log_chan(void){ + return logging_daemon_chan; +} +/*************************/ + +/************************** + * check if the fd is in use for logging + **************************/ +int +cl_log_is_logd_fd(int fd) +{ + return logging_daemon_chan && ( + fd == logging_daemon_chan->ops->get_send_select_fd(logging_daemon_chan) + || + fd == logging_daemon_chan->ops->get_recv_select_fd(logging_daemon_chan) + ); +} + +void +cl_log_enable_stderr(int truefalse) +{ + stderr_enabled = truefalse; +} + +void +cl_log_enable_stdout(int truefalse) +{ + stdout_enabled = truefalse; +} + +void +cl_log_set_uselogd(int truefalse) +{ + use_logging_daemon = truefalse; +} +void +cl_log_enable_syslog_filefmt(int truefalse) +{ + syslogformatfile = (gboolean)truefalse; +} + +gboolean +cl_log_get_uselogd(void) +{ + return use_logging_daemon; +} + + +int +cl_log_get_logdtime(void) +{ + return conn_logd_time; + +} + +void +cl_log_set_logdtime(int logdtime) +{ + conn_logd_time = logdtime; + return; +} + +void +cl_log_use_buffered_io(int truefalse) +{ + use_buffered_io = truefalse; + cl_log_close_log_files(); +} + +#define ENVPRE "HA_" + +#define ENV_HADEBUGVAL "HA_debug" +#define ENV_LOGFENV "HA_logfile" /* well-formed log file :-) */ +#define ENV_DEBUGFENV "HA_debugfile" /* Debug log file */ +#define ENV_LOGFACILITY "HA_logfacility"/* Facility to use for logger */ +#define ENV_SYSLOGFMT "HA_syslogmsgfmt"/* TRUE if we should use syslog message formatting */ +#define ENV_LOGDAEMON "HA_use_logd" +#define ENV_CONNINTVAL "HA_conn_logd_time" +#define TRADITIONAL_COMPRESSION "HA_traditional_compression" +#define COMPRESSION "HA_compression" + +static void +inherit_compress(void) +{ + char* inherit_env = NULL; + + inherit_env = getenv(TRADITIONAL_COMPRESSION); + if (inherit_env != NULL && *inherit_env != EOS) { + gboolean value; + + if (cl_str_to_boolean(inherit_env, &value)!= HA_OK){ + cl_log(LOG_ERR, "inherit traditional_compression failed"); + }else{ + cl_set_traditional_compression(value); + } + } + +} + +void +cl_inherit_logging_environment(int logqueuemax) +{ + char * inherit_env = NULL; + + /* Donnot need to free the return pointer from getenv */ + inherit_env = getenv(ENV_HADEBUGVAL); + if (inherit_env != NULL && atoi(inherit_env) != 0 ) { + debug_level = atoi(inherit_env); + inherit_env = NULL; + } + + inherit_env = getenv(ENV_LOGFENV); + if (inherit_env != NULL && *inherit_env != EOS) { + cl_log_set_logfile(inherit_env); + inherit_env = NULL; + } + + inherit_env = getenv(ENV_DEBUGFENV); + if (inherit_env != NULL && *inherit_env != EOS) { + cl_log_set_debugfile(inherit_env); + inherit_env = NULL; + } + + inherit_env = getenv(ENV_LOGFACILITY); + if (inherit_env != NULL && *inherit_env != EOS) { + int facility = -1; + facility = cl_syslogfac_str2int(inherit_env); + if ( facility >= 0 ) { + cl_log_set_facility(facility); + } + inherit_env = NULL; + } + + inherit_env = getenv(ENV_SYSLOGFMT); + if (inherit_env != NULL && *inherit_env != EOS) { + gboolean truefalse; + if (cl_str_to_boolean(inherit_env, &truefalse) == HA_OK) { + cl_log_enable_syslog_filefmt(truefalse); + } + } + + inherit_env = getenv(ENV_LOGDAEMON); + if (inherit_env != NULL && *inherit_env != EOS) { + gboolean uselogd; + cl_str_to_boolean(inherit_env, &uselogd); + cl_log_set_uselogd(uselogd); + if (uselogd) { + if (logqueuemax > 0) { + cl_set_logging_wqueue_maxlen(logqueuemax); + } + } + } + + inherit_env = getenv(ENV_CONNINTVAL); + if (inherit_env != NULL && *inherit_env != EOS) { + int logdtime; + logdtime = cl_get_msec(inherit_env); + cl_log_set_logdtime(logdtime); + } + + inherit_compress(); + return; +} + + +static void +add_logging_channel_mainloop(IPC_Channel* chan) +{ + GCHSource* chp= + G_main_add_IPC_Channel( G_PRIORITY_DEFAULT, + chan, + FALSE, + NULL, + NULL, + destroy_logging_channel_callback); + + if (chp == NULL){ + cl_log(LOG_INFO, "adding logging channel to mainloop failed"); + } + + logging_chan_in_main_loop = TRUE; + + + return; +} + +static void +remove_logging_channel_mainloop(gpointer userdata) +{ + logging_chan_in_main_loop = FALSE; + + return; +} + + +static IPC_Channel* +create_logging_channel(void) +{ + GHashTable* attrs; + char path[] = IPC_PATH_ATTR; + char sockpath[] = HA_LOGDAEMON_IPC; + IPC_Channel* chan; + static gboolean complained_yet = FALSE; + + attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(attrs, path, sockpath); + + chan =ipc_channel_constructor(IPC_ANYTYPE, attrs); + + g_hash_table_destroy(attrs); + + if (chan == NULL) { + cl_log(LOG_ERR, "create_logging_channel:" + "contructing ipc channel failed"); + return NULL; + } + + if (chan->ops->initiate_connection(chan) != IPC_OK) { + if (!complained_yet) { + complained_yet = TRUE; + cl_log(LOG_WARNING, "Initializing connection" + " to logging daemon failed." + " Logging daemon may not be running"); + } + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + + return NULL; + } + complained_yet = FALSE; + + if (create_logging_channel_callback){ + create_logging_channel_callback(chan); + } + + + return chan; + +} + +gboolean +cl_log_test_logd(void) +{ + IPC_Channel* chan = logging_daemon_chan; + + if (chan && chan->ops->get_chan_status(chan) == IPC_CONNECT){ + return TRUE; + } + if (chan ){ + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = chan = NULL; + } + + logging_daemon_chan = chan = create_logging_channel(); + + if (chan == NULL){ + return FALSE; + } + + if(chan->ops->get_chan_status(chan) != IPC_CONNECT){ + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = chan = NULL; + return FALSE; + } + + return TRUE; + +} + +/* FIXME: This is way too ugly to bear */ + +void +cl_log_set_facility(int facility) +{ + if (syslog_enabled && facility == cl_log_facility) { + return; + } + cl_log_facility = facility; + closelog(); + syslog_enabled = 0; + if (facility > 0) { + cl_opensyslog(); + } +} + +void +cl_log_set_entity(const char * entity) +{ + if (entity == NULL) { + entity = DFLT_ENTITY; + } + strncpy(cl_log_entity, entity, MAXENTITY); + cl_log_entity[MAXENTITY-1] = '\0'; + if (syslog_enabled) { + syslog_enabled = 0; + cl_opensyslog(); + } +} + +void +cl_log_set_syslogprefix(const char *prefix) +{ + if (prefix == NULL) { + prefix = DFLT_PREFIX; + } + strncpy(cl_log_syslogprefix, prefix, MAXENTITY); + cl_log_syslogprefix[MAXENTITY-1] = '\0'; + if (syslog_enabled) { + syslog_enabled = 0; + cl_opensyslog(); + } +} + +void +cl_log_set_logfile(const char * path) +{ + if(path != NULL && strcasecmp("/dev/null", path) == 0) { + path = NULL; + } + logfile_name = path; + cl_log_close_log_files(); +} +void +cl_log_set_debugfile(const char * path) +{ + if(path != NULL && strcasecmp("/dev/null", path) == 0) { + path = NULL; + } + debugfile_name = path; + cl_log_close_log_files(); +} + + +/* + * This function sets two callback functions. + * One for creating a channel and + * the other for destroying a channel* + */ +int +cl_log_set_logd_channel_source( void (*create_callback)(IPC_Channel* chan), + GDestroyNotify destroy_callback) +{ + IPC_Channel* chan = logging_daemon_chan ; + + if (destroy_callback == NULL){ + destroy_logging_channel_callback = remove_logging_channel_mainloop; + }else{ + destroy_logging_channel_callback = destroy_callback; + } + + if (create_callback == NULL){ + create_logging_channel_callback = add_logging_channel_mainloop; + }else{ + create_logging_channel_callback = create_callback; + } + + if (chan != NULL + && chan->ops->get_chan_status(chan) == IPC_CONNECT){ + add_logging_channel_mainloop(chan); + } + + return 0; +} + +const char * +prio2str(int priority) +{ + static const char *log_prio[8] = { + "EMERG", + "ALERT", + "CRIT", + "ERROR", + "WARN", + "notice", + "info", + "debug" + }; + int logpri; + + logpri = LOG_PRI(priority); + + return (logpri < 0 || logpri >= DIMOF(log_prio)) ? + "(undef)" : log_prio[logpri]; +} + +/* print log line to a FILE *f */ +#define print_logline(fp,entity,entity_pid,ts,pristr,buf) { \ + fprintf(fp, "%s[%d]: %s ",entity,entity_pid,ha_timestamp(ts)); \ + if (pristr) \ + fprintf(fp,"%s: %s\n",pristr,buf); \ + else \ + fprintf(fp,"%s\n",buf); \ + } + +static char * syslog_timestamp(TIME_T t); +static void cl_limit_log_update(struct msg_ctrl *ml, time_t ts); + +static void +append_log(FILE * fp, const char * entity, int entity_pid +, TIME_T timestamp, const char * pristr, const char * msg) +{ + static int got_uname = FALSE; + static struct utsname un; + + if (!syslogformatfile) { + print_logline(fp, entity, entity_pid, timestamp, pristr, msg); + return; + } + if (!got_uname) { + uname(&un); + } + /* + * Jul 14 21:45:18 beam logd: [1056]: info: setting log file to /dev/null + */ + fprintf(fp, "%s %s %s: [%d]: %s%s%s\n" + , syslog_timestamp(timestamp) + , un.nodename, entity, entity_pid + , (pristr ? pristr : "") + , (pristr ? ": " : "") + , msg); +} + +/* As performance optimization we try to keep the file descriptor + * open all the time, but as logrotation needs to work, the calling + * program actually needs a signal handler. + * + * To be able to keep files open even without signal handler, + * we remember the stat info, and close/reopen if the inode changed. + * We keep the number of stat() calls to one per file per minute. + * logrotate should be configured for delayed compression, if any. + */ + +struct log_file_context { + FILE *fp; + struct stat stat_buf; +}; + +static struct log_file_context log_file, debug_file; + +static void close_log_file(struct log_file_context *lfc) +{ + /* ignore errors, we cannot do anything about them anyways */ + fflush(lfc->fp); + fsync(fileno(lfc->fp)); + fclose(lfc->fp); + lfc->fp = NULL; +} + +void cl_log_close_log_files(void) +{ + if (log_file.fp) + close_log_file(&log_file); + if (debug_file.fp) + close_log_file(&debug_file); +} + +static void maybe_close_log_file(const char *fname, struct log_file_context *lfc) +{ + struct stat buf; + if (!lfc->fp) + return; + if (stat(fname, &buf) || buf.st_ino != lfc->stat_buf.st_ino) { + close_log_file(lfc); + cl_log(LOG_INFO, "log-rotate detected on logfile %s", fname); + } +} + +/* Default to unbuffered IO. logd or others can use cl_log_use_buffered_io(1) + * to enable fully buffered mode, and then use fflush appropriately. + */ +static void open_log_file(const char *fname, struct log_file_context *lfc) +{ + lfc->fp = fopen(fname ,"a"); + if (!lfc->fp) { + syslog(LOG_ERR, "Failed to open log file %s: %s\n" , + fname, strerror(errno)); + } else { + setvbuf(lfc->fp, NULL, + use_buffered_io ? _IOFBF : _IONBF, + BUFSIZ); + fstat(fileno(lfc->fp), &lfc->stat_buf); + } +} + +static void maybe_reopen_log_files(const char *log_fname, const char *debug_fname) +{ + static TIME_T last_stat_time; + + if (log_file.fp || debug_file.fp) { + TIME_T now = time(NULL); + if (now - last_stat_time > 59) { + /* Don't use an exact minute, have it jitter around a + * bit against cron or others. Note that, if there + * is no new log message, it can take much longer + * than this to notice logrotation and actually close + * our file handle on the possibly already rotated, + * or even deleted. + * + * As long as at least one minute pases between + * renaming the log file, and further processing, + * no message will be lost, so this should do fine: + * (mv ha-log ha-log.1; sleep 60; gzip ha-log.1) + */ + maybe_close_log_file(log_fname, &log_file); + maybe_close_log_file(debug_fname, &debug_file); + last_stat_time = now; + } + } + + if (log_fname && !log_file.fp) + open_log_file(log_fname, &log_file); + + if (debug_fname && !debug_file.fp) + open_log_file(debug_fname, &debug_file); +} + +/* + * This function can cost us realtime unless use_logging_daemon + * is enabled. Then we log everything through a child process using + * non-blocking IPC. + */ + +/* Cluster logging function */ +void +cl_direct_log(int priority, const char* buf, gboolean use_priority_str, + const char* entity, int entity_pid, TIME_T ts) +{ + const char * pristr; + int needprivs = !cl_have_full_privs(); + + pristr = use_priority_str ? prio2str(priority) : NULL; + + if (!entity) + entity = *cl_log_entity ? cl_log_entity : DFLT_ENTITY; + + if (needprivs) { + return_to_orig_privs(); + } + + if (syslog_enabled) { + snprintf(common_log_entity, MAXENTITY, "%s", + *cl_log_syslogprefix ? cl_log_syslogprefix : entity); + + /* The extra trailing '\0' is supposed to work around some + * "known syslog bug that ends up concatinating entries". + * Knowledge about which syslog package, version, platform and + * what exactly the bug was has been lost, but leaving it in + * won't do any harm either. */ + syslog(priority, "%s[%d]: %s%s%s%c", + *cl_log_syslogprefix ? entity : "", + entity_pid, + pristr ?: "", pristr ? ": " : "", + buf, 0); + } + + maybe_reopen_log_files(logfile_name, debugfile_name); + + if (debug_file.fp) + append_log(debug_file.fp, entity, entity_pid, ts, pristr, buf); + + if (priority != LOG_DEBUG && log_file.fp) + append_log(log_file.fp, entity, entity_pid, ts, pristr, buf); + + if (needprivs) { + return_to_dropped_privs(); + } + return; +} + +void cl_log_do_fflush(int do_fsync) +{ + if (log_file.fp) { + fflush(log_file.fp); + if (do_fsync) + fsync(fileno(log_file.fp)); + } + if (debug_file.fp) { + fflush(debug_file.fp); + if (do_fsync) + fsync(fileno(debug_file.fp)); + } +} + +/* + * This function can cost us realtime unless use_logging_daemon + * is enabled. Then we log everything through a child process using + * non-blocking IPC. + */ + +static int cl_log_depth = 0; + +/* Cluster logging function */ +void +cl_log(int priority, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + ssize_t nbytes; + + cl_process_pid = (int)getpid(); + + cl_log_depth++; + + buf[MAXLINE-1] = EOS; + va_start(ap, fmt); + nbytes=vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + if (nbytes >= (ssize_t)sizeof(buf)){ + nbytes = sizeof(buf) -1 ; + } + + if (stderr_enabled) { + append_log(stderr, cl_log_entity,cl_process_pid, + NULLTIME, prio2str(priority), buf); + } + + if (stdout_enabled) { + append_log(stdout, cl_log_entity,cl_process_pid, + NULLTIME, prio2str(priority), buf); + } + + if (use_logging_daemon && cl_log_depth <= 1) { + LogToLoggingDaemon(priority, buf, nbytes, TRUE); + }else{ + /* this may cause blocking... maybe should make it optional? */ + cl_direct_log(priority, buf, TRUE, NULL, cl_process_pid, NULLTIME); + } + + cl_log_depth--; + return; +} + +/* + * Log a message only if there were not too many messages of this + * kind recently. This is too prevent log spamming in case a + * condition persists over a long period of time. The maximum + * number of messages for the timeframe and other details are + * provided in struct logspam (see cl_log.h). + * + * Implementation details: + * - max number of time_t slots is allocated; slots keep time + * stamps of previous max number of messages + * - we check if the difference between now (i.e. new message just + * arrived) and the oldest message is _less_ than the window + * timeframe + * - it's up to the user to do cl_limit_log_new and afterwards + * cl_limit_log_destroy, though the latter is usually not + * necessary; the memory allocated with cl_limit_log_new stays + * constant during the lifetime of the process + * + * NB on Thu Aug 4 15:26:49 CEST 2011: + * This interface is very new, use with caution and report bugs. + */ + +struct msg_ctrl * +cl_limit_log_new(struct logspam *lspam) +{ + struct msg_ctrl *ml; + + ml = (struct msg_ctrl *)malloc(sizeof(struct msg_ctrl)); + if (!ml) { + cl_log(LOG_ERR, "%s:%d: out of memory" + , __FUNCTION__, __LINE__); + return NULL; + } + ml->msg_slots = (time_t *)calloc(lspam->max, sizeof(time_t)); + if (!ml->msg_slots) { + cl_log(LOG_ERR, "%s:%d: out of memory" + , __FUNCTION__, __LINE__); + return NULL; + } + ml->lspam = lspam; + cl_limit_log_reset(ml); + return ml; /* to be passed later to cl_limit_log() */ +} + +void +cl_limit_log_destroy(struct msg_ctrl *ml) +{ + if (!ml) + return; + g_free(ml->msg_slots); + g_free(ml); +} + +void +cl_limit_log_reset(struct msg_ctrl *ml) +{ + ml->last = -1; + ml->cnt = 0; + ml->suppress_t = (time_t)0; + memset(ml->msg_slots, 0, ml->lspam->max * sizeof(time_t)); +} + +static void +cl_limit_log_update(struct msg_ctrl *ml, time_t ts) +{ + ml->last = (ml->last + 1) % ml->lspam->max; + *(ml->msg_slots + ml->last) = ts; + if (ml->cnt < ml->lspam->max) + ml->cnt++; +} + +void +cl_limit_log(struct msg_ctrl *ml, int priority, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + time_t last_ts, now = time(NULL); + + if (!ml) + goto log_msg; + if (ml->suppress_t) { + if ((now - ml->suppress_t) < ml->lspam->reset_time) + return; + /* message blocking expired */ + cl_limit_log_reset(ml); + } + last_ts = ml->last != -1 ? *(ml->msg_slots + ml->last) : (time_t)0; + if ( + ml->cnt < ml->lspam->max || /* not so many messages logged */ + (now - last_ts) > ml->lspam->window /* messages far apart */ + ) { + cl_limit_log_update(ml, now); + goto log_msg; + } else { + cl_log(LOG_INFO + , "'%s' messages logged too often, " + "suppressing messages of this kind for %ld seconds" + , ml->lspam->id, ml->lspam->reset_time); + cl_log(priority, "%s", ml->lspam->advice); + ml->suppress_t = now; + return; + } + +log_msg: + va_start(ap, fmt); + vsnprintf(buf, MAXLINE, fmt, ap); + va_end(ap); + cl_log(priority, "%s", buf); +} + +void +cl_perror(const char * fmt, ...) +{ + const char * err; + + va_list ap; + char buf[MAXLINE]; + + err = strerror(errno); + va_start(ap, fmt); + vsnprintf(buf, MAXLINE, fmt, ap); + va_end(ap); + + cl_log(LOG_ERR, "%s: %s", buf, err); + +} +void +cl_glib_msg_handler(const gchar *log_domain, GLogLevelFlags log_level +, const gchar *message, gpointer user_data) +{ + GLogLevelFlags level = (log_level & G_LOG_LEVEL_MASK); + int ha_level; + + switch(level) { + case G_LOG_LEVEL_ERROR: ha_level = LOG_ERR; break; + case G_LOG_LEVEL_CRITICAL: ha_level = LOG_ERR; break; + case G_LOG_LEVEL_WARNING: ha_level = LOG_WARNING; break; + case G_LOG_LEVEL_MESSAGE: ha_level = LOG_NOTICE; break; + case G_LOG_LEVEL_INFO: ha_level = LOG_INFO; break; + case G_LOG_LEVEL_DEBUG: ha_level = LOG_DEBUG; break; + + default: ha_level = LOG_WARNING; break; + } + + + cl_log(ha_level, "glib: %s", message); +} +static char * +syslog_timestamp(TIME_T t) +{ + static char ts[64]; + struct tm* ttm; + TIME_T now; + time_t nowtt; + static const char* monthstr [12] = { + "Jan", "Feb", "Mar", + "Apr", "May", "Jun", + "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec" + }; + + /* Work around various weridnesses in different OSes and time_t definitions */ + if (t == 0){ + now = time(NULL); + }else{ + now = t; + } + + nowtt = (time_t)now; + ttm = localtime(&nowtt); + + snprintf(ts, sizeof(ts), "%3s %02d %02d:%02d:%02d" + , monthstr[ttm->tm_mon], ttm->tm_mday + , ttm->tm_hour, ttm->tm_min, ttm->tm_sec); + return(ts); +} + + + +char * +ha_timestamp(TIME_T t) +{ + static char ts[64]; + struct tm* ttm; + TIME_T now; + time_t nowtt; + + /* Work around various weridnesses in different OSes and time_t definitions */ + if (t == 0){ + now = time(NULL); + }else{ + now = t; + } + + nowtt = (time_t)now; + ttm = localtime(&nowtt); + + snprintf(ts, sizeof(ts), "%04d/%02d/%02d_%02d:%02d:%02d" + , ttm->tm_year+1900, ttm->tm_mon+1, ttm->tm_mday + , ttm->tm_hour, ttm->tm_min, ttm->tm_sec); + return(ts); +} + + +static int +cl_set_logging_wqueue_maxlen(int qlen) +{ + int sendrc; + IPC_Channel* chan = logging_daemon_chan; + + if (chan == NULL){ + chan = logging_daemon_chan = create_logging_channel(); + } + + if (chan == NULL){ + return HA_FAIL; + } + + if (chan->ch_status != IPC_CONNECT){ + cl_log(LOG_ERR, "cl_set_logging_wqueue_maxle:" + "channel is not connected"); + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = NULL; + return HA_FAIL; + } + + sendrc = chan->ops->set_send_qlen(logging_daemon_chan, qlen); + + if (sendrc == IPC_OK) { + return HA_OK; + }else { + return HA_FAIL; + } +} + + + +int +LogToDaemon(int priority, const char * buf, + int bufstrlen, gboolean use_pri_str) +{ + int rc; + + cl_log_depth++; + + rc= LogToLoggingDaemon(priority, buf, bufstrlen, use_pri_str); + + cl_log_depth--; + + return rc; +} + +static int drop_msg_num = 0; + +void +cl_flush_logs(void) +{ + if(logging_daemon_chan == NULL) { + return; + } + logging_daemon_chan->ops->waitout(logging_daemon_chan); +} + +static int +LogToLoggingDaemon(int priority, const char * buf, + int bufstrlen, gboolean use_pri_str) +{ + IPC_Channel* chan = logging_daemon_chan; + static longclock_t nexttime = 0; + IPC_Message* msg; + int sendrc = IPC_FAIL; + int intval = conn_logd_time; + + /* make sure we don't hold file descriptors open + * we don't intend to use again */ + cl_log_close_log_files(); + + if (chan == NULL) { + longclock_t lnow = time_longclock(); + + if (cmp_longclock(lnow, nexttime) >= 0){ + nexttime = add_longclock( + lnow, msto_longclock(intval)); + + logging_daemon_chan = chan = create_logging_channel(); + } + } + + if (chan == NULL){ + cl_direct_log( + priority, buf, TRUE, NULL, cl_process_pid, NULLTIME); + return HA_FAIL; + } + + msg = ChildLogIPCMessage(priority, buf, bufstrlen, use_pri_str, chan); + if (msg == NULL) { + drop_msg_num++; + return HA_FAIL; + } + + if (chan->ch_status == IPC_CONNECT){ + + if (chan->ops->is_sending_blocked(chan)) { + chan->ops->resume_io(chan); + } + /* Make sure there is room for the drop message _and_ the + * one we wish to log. Otherwise there is no point. + * + * Try to avoid bouncing on the limit by additionally + * waiting until there is room for QUEUE_SATURATION_FUZZ + * messages. + */ + if (drop_msg_num > 0 + && chan->send_queue->current_qlen + < (chan->send_queue->max_qlen -1 -QUEUE_SATURATION_FUZZ)) + { + /* have to send it this way so the order is correct */ + send_dropped_message(use_pri_str, chan); + } + + /* Don't log a debug message if we're + * approaching the queue limit and already + * dropped a message + */ + if (drop_msg_num == 0 + || chan->send_queue->current_qlen < + (chan->send_queue->max_qlen -1 -QUEUE_SATURATION_FUZZ) + || priority != LOG_DEBUG ) + { + sendrc = chan->ops->send(chan, msg); + } + } + + if (sendrc == IPC_OK) { + return HA_OK; + + } else { + + if (chan->ops->get_chan_status(chan) != IPC_CONNECT) { + if (!logging_chan_in_main_loop){ + chan->ops->destroy(chan); + } + logging_daemon_chan = NULL; + cl_direct_log(priority, buf, TRUE, NULL, cl_process_pid, NULLTIME); + + if (drop_msg_num > 0){ + /* Direct logging here is ok since we're + * switching to that for everything + * "for a while" + */ + cl_log(LOG_ERR, + "cl_log: %d messages were dropped" + " : channel destroyed", drop_msg_num); + } + + drop_msg_num=0; + FreeChildLogIPCMessage(msg); + return HA_FAIL; + } + + drop_msg_num++; + + } + + FreeChildLogIPCMessage(msg); + return HA_FAIL; +} + + +static gboolean +send_dropped_message(gboolean use_pri_str, IPC_Channel *chan) +{ + int sendrc; + char buf[64]; + int buf_len = 0; + IPC_Message *drop_msg = NULL; + + memset(buf, 0, 64); + snprintf(buf, 64, "cl_log: %d messages were dropped", drop_msg_num); + buf_len = strlen(buf)+1; + drop_msg = ChildLogIPCMessage(LOG_ERR, buf, buf_len, use_pri_str, chan); + + if(drop_msg == NULL || drop_msg->msg_len == 0) { + return FALSE; + } + + sendrc = chan->ops->send(chan, drop_msg); + + if(sendrc == IPC_OK) { + drop_msg_num = 0; + }else{ + FreeChildLogIPCMessage(drop_msg); + } + return sendrc == IPC_OK; +} + + +static IPC_Message* +ChildLogIPCMessage(int priority, const char *buf, int bufstrlen, + gboolean use_prio_str, IPC_Channel* ch) +{ + IPC_Message* ret; + LogDaemonMsgHdr logbuf; + int msglen; + char* bodybuf; + + + if (ch->msgpad > MAX_MSGPAD){ + cl_log(LOG_ERR, "ChildLogIPCMessage: invalid msgpad(%d)", + ch->msgpad); + return NULL; + } + + + ret = (IPC_Message*)malloc(sizeof(IPC_Message)); + + if (ret == NULL) { + return ret; + } + + memset(ret, 0, sizeof(IPC_Message)); + + /* Compute msg len: including room for the EOS byte */ + msglen = sizeof(LogDaemonMsgHdr)+bufstrlen + 1; + bodybuf = malloc(msglen + ch->msgpad); + if (bodybuf == NULL) { + free(ret); + return NULL; + } + + memset(bodybuf, 0, msglen + ch->msgpad); + memset(&logbuf, 0, sizeof(logbuf)); + logbuf.msgtype = LD_LOGIT; + logbuf.facility = cl_log_facility; + logbuf.priority = priority; + logbuf.use_pri_str = use_prio_str; + logbuf.entity_pid = getpid(); + logbuf.timestamp = time(NULL); + if (*cl_log_entity){ + strncpy(logbuf.entity,cl_log_entity,MAXENTITY); + }else { + strncpy(logbuf.entity,DFLT_ENTITY,MAXENTITY); + } + + logbuf.msglen = bufstrlen + 1; + memcpy(bodybuf + ch->msgpad, &logbuf, sizeof(logbuf)); + memcpy(bodybuf + ch->msgpad + sizeof(logbuf), + buf, + bufstrlen); + + ret->msg_len = msglen; + ret->msg_buf = bodybuf; + ret->msg_body = bodybuf + ch->msgpad; + ret->msg_done = FreeChildLogIPCMessage; + ret->msg_ch = ch; + + return ret; +} + + +static void +FreeChildLogIPCMessage(IPC_Message* msg) +{ + if (msg == NULL) { + return; + } + memset(msg->msg_body, 0, msg->msg_len); + free(msg->msg_buf); + + memset(msg, 0, sizeof (*msg)); + free(msg); + + return; + +} + + + +static void +cl_opensyslog(void) +{ + if (*cl_log_entity == '\0' || cl_log_facility < 0) { + return; + } + syslog_enabled = 1; + strncpy(common_log_entity, cl_log_entity, MAXENTITY); + openlog(common_log_entity, LOG_CONS, cl_log_facility); +} + + +void +cl_log_args(int argc, char **argv) +{ + int lpc = 0; + int len = 0; + int existing_len = 0; + char *arg_string = NULL; + + if(argc == 0 || argv == NULL) { + return; + } + + for(;lpc < argc; lpc++) { + if(argv[lpc] == NULL) { + break; + } + + len = 2 + strlen(argv[lpc]); /* +1 space, +1 EOS */ + if(arg_string) { + existing_len = strlen(arg_string); + } + + arg_string = realloc(arg_string, len + existing_len); + sprintf(arg_string + existing_len, "%s ", argv[lpc]); + } + cl_log(LOG_INFO, "Invoked: %s", arg_string); + free(arg_string); +} diff --git a/lib/clplumbing/cl_malloc.c b/lib/clplumbing/cl_malloc.c new file mode 100644 index 0000000..ca6dc0b --- /dev/null +++ b/lib/clplumbing/cl_malloc.c @@ -0,0 +1,1044 @@ +/* + * Copyright (C) 2000 Alan Robertson + * + * This software licensed under the GNU LGPL. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define HA_MALLOC_ORIGINAL +#include +#include +#include +#include +#ifdef HAVE_STDINT_H +#include +#endif /* HAVE_STDINT_H */ +#include +#include +#ifndef BSD +#ifdef HAVE_MALLOC_H +# include +#endif +#endif +#include +#include +#include + +#include + +#ifndef _CLPLUMBING_CLMALLOC_NATIVE_H +static cl_mem_stats_t default_memstats; +static volatile cl_mem_stats_t * memstats = &default_memstats; + +/* + * Compile time malloc debugging switches: + * + * MARK_PRISTINE - puts known byte pattern in freed memory + * Good at finding "use after free" cases + * Cheap in memory, but expensive in CPU + * + * MAKE_GUARD - puts a known pattern *after* allocated memory + * Good at finding overrun problems after the fact + * Cheap in CPU, adds a few bytes to each malloc item + * + */ + +#define MARK_PRISTINE 1 /* Expensive in CPU time */ +#undef MARK_PRISTINE +#define MAKE_GUARD 1 /* Adds 'n' bytes memory - cheap in CPU*/ +#define USE_ASSERTS 1 +#define DUMPONERR 1 +#define RETURN_TO_MALLOC 1 +#undef RETURN_TO_MALLOC + +#ifndef DUMPONERR +# define DUMPIFASKED() /* nothing */ +#else +# define DUMPIFASKED() {abort();} +#endif + + +/* + * + * Malloc wrapper functions + * + * I wrote these so we can better track memory leaks, etc. and verify + * that the system is stable in terms of memory usage. + * + * For our purposes, these functions are a somewhat faster than using + * malloc directly (although they use a bit more memory) + * + * The general strategy is loosely related to the buddy system, + * except very simple, well-suited to our continuous running + * nature, and the constancy of the requests and messages. + * + * We keep an array of linked lists, each for a different size + * buffer. If we need a buffer larger than the largest one provided + * by the list, we go directly to malloc. + * + * Otherwise, we keep return them to the appropriate linked list + * when we're done with them, and reuse them from the list. + * + * We never coalesce buffers on our lists, and we never free them. + * + * It's very simple. We get usage stats. It makes me happy. + */ + +#define HA_MALLOC_MAGIC 0xFEEDBEEFUL +#define HA_FREE_MAGIC 0xDEADBEEFUL + + +/* + * We put a struct cl_mhdr in front of every malloc item. + * This means each malloc item is at least 12 bytes bigger than it theoretically + * needs to be. But, it allows this code to be fast and recognize + * multiple free attempts, and memory corruption *before* the object + * + * It's probably possible to combine these fields a bit, + * since bucket and reqsize are only needed for allocated items, + * both are bounded in value, and fairly strong integrity checks apply + * to them. But then we wouldn't be able to tell *quite* as reliably + * if someone gave us an item to free that we didn't allocate... + * + * Could even make the bucket and reqsize objects into 16-bit ints... + * + * The idea of getting it all down into 32-bits of overhead is + * an interesting thought... + * + * But some architectures have alignment constraints. For instance, sparc + * requires that double-word accesses be aligned on double-word boundaries. + * Thus if the requested space is bigger than a double-word, then cl_mhdr + * should, for safety, be a double-word multiple (minimum 8bytes, 64bits). + +*/ + +#ifdef HA_MALLOC_TRACK +# define HA_MALLOC_OWNER 64 +struct cl_bucket; +#endif + +struct cl_mhdr { +# ifdef HA_MALLOC_MAGIC + unsigned long magic; /* Must match HA_*_MAGIC */ +#endif +# ifdef HA_MALLOC_TRACK + char owner[HA_MALLOC_OWNER]; + struct cl_bucket * left; + struct cl_bucket * right; + int dumped; + longclock_t mtime; +#endif + size_t reqsize; + int bucket; +}; + +struct cl_bucket { + struct cl_mhdr hdr; + struct cl_bucket * next; +}; + +#define NUMBUCKS 12 +#define NOBUCKET (NUMBUCKS) + +static struct cl_bucket* cl_malloc_buckets[NUMBUCKS]; +static size_t cl_bucket_sizes[NUMBUCKS]; +static size_t buckminpow2 = 0L; + +static int cl_malloc_inityet = 0; +static size_t cl_malloc_hdr_offset = sizeof(struct cl_mhdr); + +static void* cl_new_mem(size_t size, int numbuck); +static void cl_malloc_init(void); +static void cl_dump_item(const struct cl_bucket*b); + +#ifdef MARK_PRISTINE +# define PRISTVALUE 0xff + static int cl_check_is_pristine(const void* v, unsigned size); + static void cl_mark_pristine(void* v, unsigned size); + static int pristoff; +#endif + +#define BHDR(p) ((struct cl_bucket*)(void*)(((char*)p)-cl_malloc_hdr_offset)) +#define CBHDR(p) ((const struct cl_bucket*)(const void*)(((const char*)p)-cl_malloc_hdr_offset)) +#define MEMORYSIZE(p)(CBHDR(p)->hdr.reqsize) + +#define MALLOCSIZE(allocsize) ((allocsize) + cl_malloc_hdr_offset + GUARDSIZE) +#define MAXMALLOC (SIZE_MAX-(MALLOCSIZE(0)+1)) + +#ifdef MAKE_GUARD +# define GUARDLEN 4 + static const unsigned char cl_malloc_guard[] = +#if GUARDLEN == 1 + {0xA5}; +#endif +#if GUARDLEN == 2 + {0x5A, 0xA5}; +#endif +#if GUARDLEN == 4 + {0x5A, 0xA5, 0x5A, 0xA5}; +#endif +# define GUARDSIZE sizeof(cl_malloc_guard) +# define ADD_GUARD(cp) (memcpy((((char*)cp)+MEMORYSIZE(cp)), cl_malloc_guard, sizeof(cl_malloc_guard))) +# define GUARD_IS_OK(cp) (memcmp((((const char*)cp)+MEMORYSIZE(cp)), \ + cl_malloc_guard, sizeof(cl_malloc_guard)) == 0) +# define CHECK_GUARD_BYTES(cp, msg) { \ + if (!GUARD_IS_OK(cp)) { \ + cl_log(LOG_ERR, "%s: guard corrupted at 0x%lx", msg \ + , (unsigned long)cp); \ + cl_dump_item(CBHDR(cp)); \ + DUMPIFASKED(); \ + } \ + } +#else +# define GUARDSIZE 0 +# define ADD_GUARD(cp) /* */ +# define GUARD_IS_OK(cp) (1) +# define CHECK_GUARD_BYTES(cp, msg) /* */ +#endif + +#define MALLOCROUND 4096 /* Round big mallocs up to a multiple of this size */ + + +#ifdef HA_MALLOC_TRACK + +static struct cl_bucket * cl_malloc_track_root = NULL; + +static void +cl_ptr_tag(void *ptr, const char *file, const char *function, const int line) +{ + struct cl_bucket* bhdr = BHDR(ptr); + snprintf(bhdr->hdr.owner, HA_MALLOC_OWNER, "%s:%s:%d", + file, function, line); +} + +static void +cl_ptr_track(void *ptr) +{ + struct cl_bucket* bhdr = BHDR(ptr); + +#if defined(USE_ASSERTS) + g_assert(bhdr->hdr.left == NULL); + g_assert(bhdr->hdr.right == NULL); + g_assert((cl_malloc_track_root == NULL) || (cl_malloc_track_root->hdr.left == NULL)); +#endif + + bhdr->hdr.dumped = 0; + bhdr->hdr.mtime = time_longclock(); + + if (cl_malloc_track_root == NULL) { + cl_malloc_track_root = bhdr; + } else { + bhdr->hdr.right = cl_malloc_track_root; + cl_malloc_track_root->hdr.left = bhdr; + cl_malloc_track_root = bhdr; + } +} + +static void +cl_ptr_release(void *ptr) +{ + struct cl_bucket* bhdr = BHDR(ptr); + +/* cl_log(LOG_DEBUG, "cl_free: Freeing memory belonging to %s" + , bhdr->hdr.owner); */ + +#if defined(USE_ASSERTS) + g_assert(cl_malloc_track_root != NULL); + g_assert(cl_malloc_track_root->hdr.left == NULL); +#endif + + if (bhdr->hdr.left != NULL) { + bhdr->hdr.left->hdr.right=bhdr->hdr.right; + } + + if (bhdr->hdr.right != NULL) { + bhdr->hdr.right->hdr.left=bhdr->hdr.left; + } + + if (cl_malloc_track_root == bhdr) { + cl_malloc_track_root=bhdr->hdr.right; + } + + bhdr->hdr.left = NULL; + bhdr->hdr.right = NULL; +} + +static void +cl_ptr_init(void) +{ + cl_malloc_track_root = NULL; +} + +int +cl_malloc_dump_allocated(int log_level, gboolean filter) +{ + int lpc = 0; + struct cl_bucket* cursor = cl_malloc_track_root; + longclock_t time_diff; + + cl_log(LOG_INFO, "Dumping allocated memory buffers:"); + + while (cursor != NULL) { + if(filter && cursor->hdr.dumped) { + + } else if(log_level > LOG_DEBUG) { + } else if(filter) { + lpc++; + cl_log(log_level, "cl_malloc_dump: %p owner %s, size %d" + , cursor+cl_malloc_hdr_offset + , cursor->hdr.owner + , (int)cursor->hdr.reqsize); + } else { + lpc++; + time_diff = sub_longclock(time_longclock(), cursor->hdr.mtime); + cl_log(log_level, "cl_malloc_dump: %p owner %s, size %d, dumped %d, age %lu ms" + , cursor+cl_malloc_hdr_offset + , cursor->hdr.owner + , (int)cursor->hdr.reqsize + , cursor->hdr.dumped + , longclockto_long(time_diff)); + } + cursor->hdr.dumped = 1; + cursor = cursor->hdr.right; + } + + cl_log(LOG_INFO, "End dump."); + return lpc; +} +#endif +static const int LogTable256[] = +{ + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +}; +#define POW2BYTE(b) (LogTable256[b]) +#define BYTE3(i) (((i)&0xFF000000)>>24) +#define BYTE2(i) (((i)&0x00FF0000)>>16) +#define BYTE1(i) (((i)&0x0000FF00)>>8) +#define BYTE0(i) ((i)&0x000000FF) + +/* Works for malloc bucket sizes up to 2^8 */ +#define POW21BYTE(i) (POW2BYTE(BYTE0(i))) + +/* Works for malloc bucket sizes up to 2^16 */ +#define POW22BYTE(i) ((BYTE1(i) != 0x00)? (POW2BYTE(BYTE1(i))+8) \ + : (POW21BYTE(i))) + +/* Works for malloc bucket sizes up to 2^24 */ +#define POW23BYTE(i) ((BYTE2(i) != 0x00)? (POW2BYTE(BYTE2(i))+16) \ + : POW22BYTE(i)) + +/* Works for malloc bucket sizes up to 2^32 */ +#define POW24BYTE(i) ((BYTE3(i) != 0x00)? (POW2BYTE(BYTE3(i))+24) \ + : POW23BYTE(i)) + +/* #define INT2POW2(i) POW24BYTE(i) / * This would allow 2G in our largest malloc chain */ + /* which I don't think we need */ +#define INT2POW2(i) POW23BYTE(i) /* This allows up to about 16 Mbytes in our largest malloc chain */ + /* and it's a little faster than the one above */ + + +/* + * cl_malloc: malloc clone + */ + +void * +cl_malloc(size_t size) +{ +#if 0 + int j; +#endif + int numbuck = NOBUCKET; + struct cl_bucket* buckptr = NULL; + void* ret; + + if(!size) { + cl_log(LOG_ERR + , "%s: refusing to allocate zero sized block" + , __FUNCTION__ + ); + return NULL; + } + if (size > MAXMALLOC) { + return NULL; + } + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + +#if 1 + /* + * NOTE: This restricts bucket sizes to be powers of two + * - which is OK with me - and how the code has always worked :-D + */ + numbuck = INT2POW2(size-1)-buckminpow2; + numbuck = MAX(0, numbuck); + if (numbuck < NUMBUCKS) { + if (size <= cl_bucket_sizes[numbuck] + || (numbuck > 0 && size <= (cl_bucket_sizes[numbuck]/2))) { + buckptr = cl_malloc_buckets[numbuck]; + }else{ + cl_log(LOG_ERR + , "%s: bucket size bug: %lu bytes in %lu byte bucket #%d" + , __FUNCTION__ + , (unsigned long)size + , (unsigned long)cl_bucket_sizes[numbuck] + , numbuck); + + } + } +#else + /* + * Find which bucket would have buffers of the requested size + */ + for (j=0; j < NUMBUCKS; ++j) { + if (size <= cl_bucket_sizes[j]) { + numbuck = j; + buckptr = cl_malloc_buckets[numbuck]; + break; + } + } +#endif + + /* + * Pull it out of the linked list of free buffers if we can... + */ + + if (buckptr == NULL) { + ret = cl_new_mem(size, numbuck); + }else{ + cl_malloc_buckets[numbuck] = buckptr->next; + buckptr->hdr.reqsize = size; + ret = (((char*)buckptr)+cl_malloc_hdr_offset); + +#ifdef MARK_PRISTINE + { + int bucksize = cl_bucket_sizes[numbuck]; + if (!cl_check_is_pristine(ret, bucksize)) { + cl_log(LOG_ERR + , "attempt to allocate memory" + " which is not pristine."); + cl_dump_item(buckptr); + DUMPIFASKED(); + } + } +#endif + +#ifdef HA_MALLOC_MAGIC + switch (buckptr->hdr.magic) { + + case HA_FREE_MAGIC: + break; + + case HA_MALLOC_MAGIC: + cl_log(LOG_ERR + , "attempt to allocate memory" + " already allocated at 0x%lx" + , (unsigned long)ret); + cl_dump_item(buckptr); + DUMPIFASKED(); + ret=NULL; + break; + + default: + cl_log(LOG_ERR + , "corrupt malloc buffer at 0x%lx" + , (unsigned long)ret); + cl_dump_item(buckptr); + DUMPIFASKED(); + ret=NULL; + break; + } + buckptr->hdr.magic = HA_MALLOC_MAGIC; +#endif /* HA_MALLOC_MAGIC */ + if (memstats) { + memstats->nbytes_req += size; + memstats->nbytes_alloc + += MALLOCSIZE(cl_bucket_sizes[numbuck]); + } + + } + + if (ret && memstats) { +#if 0 && defined(HAVE_MALLINFO) + /* mallinfo is too expensive to use :-( */ + struct mallinfo i = mallinfo(); + memstats->arena = i.arena; +#endif + memstats->numalloc++; + } + if (ret) { +#ifdef HA_MALLOC_TRACK + /* If we were _always_ called via the wrapper functions, + * this wouldn't be necessary, but we aren't, some use + * function pointers directly to cl_malloc() */ + cl_ptr_track(ret); + cl_ptr_tag(ret, "cl_malloc.c", "cl_malloc", 0); +#endif + ADD_GUARD(ret); + } + return(ret); +} + +int +cl_is_allocated(const void *ptr) +{ +#ifdef HA_MALLOC_MAGIC + if (NULL == ptr || CBHDR(ptr)->hdr.magic != HA_MALLOC_MAGIC) { + return FALSE; + }else if (GUARD_IS_OK(ptr)) { + return TRUE; + } + cl_log(LOG_ERR + , "cl_is_allocated: supplied storage is guard-corrupted at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(CBHDR(ptr)); + DUMPIFASKED(); + return FALSE; +#else + return (ptr != NULL); +#endif +} + +/* + * cl_free: "free" clone + */ + +void +cl_free(void *ptr) +{ + int bucket; + struct cl_bucket* bhdr; + + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + + if (ptr == NULL) { + cl_log(LOG_ERR, "attempt to free NULL pointer in cl_free()"); + DUMPIFASKED(); + return; + } + + /* Find the beginning of our "hidden" structure */ + + bhdr = BHDR(ptr); + +#ifdef HA_MALLOC_MAGIC + switch (bhdr->hdr.magic) { + case HA_MALLOC_MAGIC: + break; + + case HA_FREE_MAGIC: + cl_log(LOG_ERR + , "cl_free: attempt to free already-freed" + " object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return; + break; + default: + cl_log(LOG_ERR, "cl_free: Bad magic number" + " in object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return; + break; + } +#endif + if (!GUARD_IS_OK(ptr)) { + cl_log(LOG_ERR + , "cl_free: attempt to free guard-corrupted" + " object at 0x%lx", (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return; + } +#ifdef HA_MALLOC_TRACK + cl_ptr_release(ptr); +#endif + bucket = bhdr->hdr.bucket; +#ifdef HA_MALLOC_MAGIC + bhdr->hdr.magic = HA_FREE_MAGIC; +#endif + + /* + * Return it to the appropriate bucket (linked list), or just free + * it if it didn't come from one of our lists... + */ + +#ifndef RETURN_TO_MALLOC + if (bucket >= NUMBUCKS) { +#endif +#ifdef MARK_PRISTINE + /* Is this size right? */ + cl_mark_pristine(ptr, bhdr->hdr.reqsize); +#endif + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_alloc -= MALLOCSIZE(bhdr->hdr.reqsize); + memstats->mallocbytes -= MALLOCSIZE(bhdr->hdr.reqsize); + } + free(bhdr); +#ifndef RETURN_TO_MALLOC + }else{ + int bucksize = cl_bucket_sizes[bucket]; +#if defined(USE_ASSERTS) + g_assert(bhdr->hdr.reqsize <= cl_bucket_sizes[bucket]); +# endif + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_alloc -= MALLOCSIZE(bucksize); + } + bhdr->next = cl_malloc_buckets[bucket]; + cl_malloc_buckets[bucket] = bhdr; +#ifdef MARK_PRISTINE + cl_mark_pristine(ptr, bucksize); +# endif + } +#endif /* RETURN_TO_MALLOC */ + if (memstats) { + memstats->numfree++; + } +} + +void* +cl_realloc(void *ptr, size_t newsize) +{ + struct cl_bucket* bhdr; + int bucket; + size_t bucksize; + + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + + if (memstats) { + memstats->numrealloc++; + } + if (ptr == NULL) { + /* NULL is a legal 'ptr' value for realloc... */ + return cl_malloc(newsize); + } + if (newsize == 0) { + /* realloc() is the most redundant interface ever */ + cl_free(ptr); + return NULL; + } + + /* Find the beginning of our "hidden" structure */ + + bhdr = BHDR(ptr); + +#ifdef HA_MALLOC_MAGIC + switch (bhdr->hdr.magic) { + case HA_MALLOC_MAGIC: + break; + + case HA_FREE_MAGIC: + cl_log(LOG_ERR + , "cl_realloc: attempt to realloc already-freed" + " object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return NULL; + break; + default: + cl_log(LOG_ERR, "cl_realloc: Bad magic number" + " in object at 0x%lx" + , (unsigned long)ptr); + cl_dump_item(bhdr); + DUMPIFASKED(); + return NULL; + break; + } +#endif + CHECK_GUARD_BYTES(ptr, "cl_realloc"); + + bucket = bhdr->hdr.bucket; + + /* + * Figure out which bucket it came from... If any... + */ + + if (bucket >= NUMBUCKS) { + /* Not from our bucket-area... Call realloc... */ + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_alloc -= MALLOCSIZE(bhdr->hdr.reqsize); + memstats->mallocbytes -= MALLOCSIZE(bhdr->hdr.reqsize); + memstats->nbytes_req += newsize; + memstats->nbytes_alloc += MALLOCSIZE(newsize); + memstats->mallocbytes += MALLOCSIZE(newsize); + } +#ifdef HA_MALLOC_TRACK + cl_ptr_release(ptr); +#endif + bhdr = realloc(bhdr, newsize + cl_malloc_hdr_offset + GUARDSIZE); + if (!bhdr) { + return NULL; + } +#ifdef HA_MALLOC_TRACK + cl_ptr_track(ptr); + cl_ptr_tag(ptr, "cl_malloc.c", "realloc", 0); +#endif + bhdr->hdr.reqsize = newsize; + ptr = (((char*)bhdr)+cl_malloc_hdr_offset); + ADD_GUARD(ptr); + CHECK_GUARD_BYTES(ptr, "cl_realloc - real realloc return value"); + /* Not really a memory leak... BEAM thinks so though... */ + return ptr; /*memory leak*/ + } + bucksize = cl_bucket_sizes[bucket]; +#if defined(USE_ASSERTS) + g_assert(bhdr->hdr.reqsize <= bucksize); +#endif + if (newsize > bucksize) { + /* Need to allocate new space for it */ + void* newret = cl_malloc(newsize); + if (newret != NULL) { + memcpy(newret, ptr, bhdr->hdr.reqsize); + CHECK_GUARD_BYTES(newret, "cl_realloc - cl_malloc case"); + } + cl_free(ptr); + return newret; + } + + /* Amazing! It fits into the space previously allocated for it! */ + bhdr->hdr.reqsize = newsize; + if (memstats) { + memstats->nbytes_req -= bhdr->hdr.reqsize; + memstats->nbytes_req += newsize; + } + ADD_GUARD(ptr); + CHECK_GUARD_BYTES(ptr, "cl_realloc - fits in existing space"); + return ptr; +} + +/* + * cl_new_mem: use the real malloc to allocate some new memory + */ + +static void* +cl_new_mem(size_t size, int numbuck) +{ + struct cl_bucket* hdrret; + size_t allocsize; + size_t mallocsize; + + if (numbuck < NUMBUCKS) { + allocsize = cl_bucket_sizes[numbuck]; + }else{ + allocsize = size; + } + + mallocsize = MALLOCSIZE(allocsize); + if (numbuck == NOBUCKET) { + mallocsize = (((mallocsize + (MALLOCROUND-1))/MALLOCROUND)*MALLOCROUND); + } + + if ((hdrret = malloc(mallocsize)) == NULL) { + return NULL; + } + + hdrret->hdr.reqsize = size; + hdrret->hdr.bucket = numbuck; +#ifdef HA_MALLOC_MAGIC + hdrret->hdr.magic = HA_MALLOC_MAGIC; +#endif +#ifdef HA_MALLOC_TRACK + hdrret->hdr.left = NULL; + hdrret->hdr.right = NULL; + hdrret->hdr.owner[0] = '\0'; + hdrret->hdr.dumped = 0; +#endif + + if (memstats) { + memstats->nbytes_alloc += mallocsize; + memstats->nbytes_req += size; + memstats->mallocbytes += mallocsize; + } + /* BEAM BUG -- this is NOT a leak */ + return(((char*)hdrret)+cl_malloc_hdr_offset); /*memory leak*/ +} + + +/* + * cl_calloc: calloc clone + */ + +void * +cl_calloc(size_t nmemb, size_t size) +{ + void * ret = cl_malloc(nmemb*size); + + if (ret != NULL) { + memset(ret, 0, nmemb*size); +#ifdef HA_MALLOC_TRACK + cl_ptr_tag(ret, "cl_malloc.c", "cl_calloc", 0); +#endif + } + + return(ret); +} + +#ifdef HA_MALLOC_TRACK +void * +cl_calloc_track(size_t nmemb, size_t size, + const char *file, const char *function, const int line) +{ + void* ret; + + ret = cl_calloc(nmemb, size); + + if (ret) { + cl_ptr_tag(ret, file, function, line); + } + + return ret; +} + +void* +cl_realloc_track(void *ptr, size_t newsize, + const char *file, const char *function, const int line) +{ + void* ret; + + ret = cl_realloc(ptr, newsize); + + if (ret) { + cl_ptr_tag(ret, file, function, line); + } + + return ret; +} + +void * +cl_malloc_track(size_t size, + const char *file, const char *function, const int line) +{ + void* ret; + + ret = cl_malloc(size); + if (ret) { + /* Retag with the proper owner. */ + cl_ptr_tag(ret, file, function, line); + } + + return ret; +} + +#endif + +/* + * cl_strdup: strdup clone + */ + +char * +cl_strdup(const char *s) +{ + void * ret; + + if (!s) { + cl_log(LOG_ERR, "cl_strdup(NULL)"); + return(NULL); + } + ret = cl_malloc((strlen(s) + 1) * sizeof(char)); + + if (ret) { + strcpy(ret, s); + } + + return(ret); +} + + +/* + * cl_malloc_init(): initialize our malloc wrapper things + */ + +static void +cl_malloc_init() +{ + int j; + size_t cursize = 32; + int llcount = 1; + + cl_malloc_inityet = 1; + + /* cl_malloc_hdr_offset should be a double-word multiple */ + while (cl_malloc_hdr_offset > (llcount * sizeof(long long))) { + llcount++; + } + cl_malloc_hdr_offset = llcount * sizeof(long long); + + + for (j=0; j < NUMBUCKS; ++j) { + cl_malloc_buckets[j] = NULL; + + cl_bucket_sizes[j] = cursize; + cursize <<= 1; + } + buckminpow2 = INT2POW2(cl_bucket_sizes[0]-1); +#ifdef MARK_PRISTINE + { + struct cl_bucket b; + pristoff = (unsigned char*)&(b.next)-(unsigned char*)&b; + pristoff += sizeof(b.next); + } +#endif +#ifdef HA_MALLOC_TRACK + cl_ptr_init(); +#endif +} + +void +cl_malloc_setstats(volatile cl_mem_stats_t *stats) +{ + if (memstats && stats) { + *stats = *memstats; + } + memstats = stats; +} + +volatile cl_mem_stats_t * +cl_malloc_getstats(void) +{ + return memstats; +} + +static void +cl_dump_item(const struct cl_bucket*b) +{ + const unsigned char * cbeg; + const unsigned char * cend; + const unsigned char * cp; + cl_log(LOG_INFO, "Dumping cl_malloc item @ 0x%lx, bucket address: 0x%lx" + , ((unsigned long)b)+cl_malloc_hdr_offset, (unsigned long)b); +#ifdef HA_MALLOC_TRACK + cl_log(LOG_INFO, "Owner: %s" + , b->hdr.owner); +#endif +#ifdef HA_MALLOC_MAGIC + cl_log(LOG_INFO, "Magic number: 0x%lx reqsize=%ld" + ", bucket=%d, bucksize=%ld" + , b->hdr.magic + , (long)b->hdr.reqsize, b->hdr.bucket + , (long)(b->hdr.bucket >= NUMBUCKS ? 0 + : cl_bucket_sizes[b->hdr.bucket])); +#else + cl_log(LOG_INFO, "reqsize=%ld" + ", bucket=%d, bucksize=%ld" + , (long)b->hdr.reqsize, b->hdr.bucket + , (long)(b->hdr.bucket >= NUMBUCKS ? 0 + : cl_bucket_sizes[b->hdr.bucket])); +#endif + cbeg = ((const unsigned char *)b)+cl_malloc_hdr_offset; + cend = cbeg+b->hdr.reqsize+GUARDSIZE; + + for (cp=cbeg; cp < cend; cp+= sizeof(unsigned)) { + cl_log(LOG_INFO, "%02x %02x %02x %02x \"%c%c%c%c\"" + , (unsigned)cp[0], (unsigned)cp[1] + , (unsigned)cp[2], (unsigned)cp[3] + , cp[0], cp[1], cp[2], cp[3]); + } +} + +/* The only reason these functions exist is because glib uses non-standard + * types (gsize)in place of size_t. Since size_t is 64-bits on some + * machines where gsize (unsigned int) is 32-bits, this is annoying. + */ + +static gpointer +cl_malloc_glib(gsize n_bytes) +{ + return (gpointer)cl_malloc((size_t)n_bytes); +} + +static void +cl_free_glib(gpointer mem) +{ + cl_free((void*)mem); +} + +static void * +cl_realloc_glib(gpointer mem, gsize n_bytes) +{ + return cl_realloc((void*)mem, (size_t)n_bytes); +} + + +/* Call before using any glib functions(!) */ +/* See also: g_mem_set_vtable() */ +void +cl_malloc_forced_for_glib(void) +{ + static GMemVTable vt = { + cl_malloc_glib, + cl_realloc_glib, + cl_free_glib, + NULL, + NULL, + NULL, + }; + if (!cl_malloc_inityet) { + cl_malloc_init(); + } + g_mem_set_vtable(&vt); +} + +#ifdef MARK_PRISTINE +static int +cl_check_is_pristine(const void* v, unsigned size) +{ + const unsigned char * cp; + const unsigned char * last; + cp = v; + last = cp + size; + cp += pristoff; + + for (;cp < last; ++cp) { + if (*cp != PRISTVALUE) { + return FALSE; + } + } + return TRUE; +} +static void +cl_mark_pristine(void* v, unsigned size) +{ + unsigned char * cp = v; + memset(cp+pristoff, PRISTVALUE, size-pristoff); +} +#endif + +#endif /* _CLPLUMBING_CLMALLOC_NATIVE_H */ diff --git a/lib/clplumbing/cl_misc.c b/lib/clplumbing/cl_misc.c new file mode 100644 index 0000000..be6441d --- /dev/null +++ b/lib/clplumbing/cl_misc.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_TIME_H +#include +#endif + +#include + +int +cl_str_to_boolean(const char * s, int * ret) +{ + if(s == NULL) { + return HA_FAIL; + } + + if ( strcasecmp(s, "true") == 0 + || strcasecmp(s, "on") == 0 + || strcasecmp(s, "yes") == 0 + || strcasecmp(s, "y") == 0 + || strcasecmp(s, "1") == 0){ + *ret = TRUE; + return HA_OK; + } + if ( strcasecmp(s, "false") == 0 + || strcasecmp(s, "off") == 0 + || strcasecmp(s, "no") == 0 + || strcasecmp(s, "n") == 0 + || strcasecmp(s, "0") == 0){ + *ret = FALSE; + return HA_OK; + } + return HA_FAIL; +} + +int +cl_file_exists(const char* filename) +{ + struct stat st; + + if (filename == NULL){ + cl_log(LOG_ERR, "%s: NULL filename", + __FUNCTION__); + return FALSE; + } + + if (lstat(filename, &st) == 0){ + return S_ISREG(st.st_mode); + } + + return FALSE; +} + +char* +cl_get_env(const char* env_name) +{ + if (env_name == NULL){ + cl_log(LOG_ERR, "%s: null name", + __FUNCTION__); + return NULL; + } + + return getenv(env_name); +} + + +int +cl_binary_to_int(const char* data, int len) +{ + const char *p = data; + const char *pmax = p + len; + guint h = *p; + + if (h){ + for (p += 1; p < pmax; p++){ + h = (h << 5) - h + *p; + } + } + + return h; +} + +/* + * Convert a string into a positive, rounded number of milliseconds. + * + * Returns -1 on error. + * + * Permissible forms: + * [0-9]+ units are seconds + * [0-9]*.[0-9]+ units are seconds + * [0-9]+ *[Mm][Ss] units are milliseconds + * [0-9]*.[0-9]+ *[Mm][Ss] units are milliseconds + * [0-9]+ *[Uu][Ss] units are microseconds + * [0-9]*.[0-9]+ *[Uu][Ss] units are microseconds + * + * Examples: + * + * 1 = 1000 milliseconds + * 1000ms = 1000 milliseconds + * 1000000us = 1000 milliseconds + * 0.1 = 100 milliseconds + * 100ms = 100 milliseconds + * 100000us = 100 milliseconds + * 0.001 = 1 millisecond + * 1ms = 1 millisecond + * 1000us = 1 millisecond + * 499us = 0 milliseconds + * 501us = 1 millisecond + */ + +#define NUMCHARS "0123456789." +#define WHITESPACE " \t\n\r\f" +#define EOS '\0' + +long +cl_get_msec(const char * input) +{ + const char * cp = input; + const char * units; + long multiplier = 1000; + long divisor = 1; + long ret = -1; + double dret; + + cp += strspn(cp, WHITESPACE); + units = cp + strspn(cp, NUMCHARS); + units += strspn(units, WHITESPACE); + + if (strchr(NUMCHARS, *cp) == NULL) { + return ret; + } + + if (strncasecmp(units, "ms", 2) == 0 + || strncasecmp(units, "cl_get_msec", 4) == 0) { + multiplier = 1; + divisor = 1; + }else if (strncasecmp(units, "us", 2) == 0 + || strncasecmp(units, "usec", 4) == 0) { + multiplier = 1; + divisor = 1000; + }else if (*units != EOS && *units != '\n' + && *units != '\r') { + return ret; + } + dret = atof(cp); + dret *= (double)multiplier; + dret /= (double)divisor; + dret += 0.5; + ret = (long)dret; + return(ret); +} diff --git a/lib/clplumbing/cl_msg.c b/lib/clplumbing/cl_msg.c new file mode 100644 index 0000000..22f00e3 --- /dev/null +++ b/lib/clplumbing/cl_msg.c @@ -0,0 +1,2537 @@ +/* + * Heartbeat messaging object. + * + * Copyright (C) 2000 Alan Robertson + * + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXMSGLINE 512 +#define MINFIELDS 30 +#define NEWLINE "\n" + + +#define NEEDAUTH 1 +#define NOAUTH 0 +#define MAX_INT_LEN 64 +#define MAX_NAME_LEN 255 +#define UUID_SLEN 64 +#define MAXCHILDMSGLEN 512 + +static int compression_threshold = (128*1024); + +static enum cl_msgfmt msgfmt = MSGFMT_NVPAIR; +static gboolean use_traditional_compression = FALSE; + +const char* +FT_strings[]={ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" +}; + +#undef DOAUDITS +#define DOAUDITS + +#undef DOPARANOIDAUDITS +/* #define DOPARANOIDAUDITS */ + +#ifdef DOAUDITS +void ha_msg_audit(const struct ha_msg* msg); +# define AUDITMSG(msg) ha_msg_audit(msg) +# ifdef DOPARANOIDAUDITS +# define PARANOIDAUDITMSG(msg) ha_msg_audit(msg) +# else +# define PARANOIDAUDITMSG(msg) /*nothing*/ +# endif +#else +# define AUDITMSG(msg) /*nothing*/ +# define PARANOIDAUDITMSG(msg) /*nothing*/ +#endif + + +static volatile hb_msg_stats_t* msgstats = NULL; + +gboolean cl_msg_quiet_fmterr = FALSE; + +extern int netstring_format; + +static struct ha_msg* wirefmt2msg_ll(const char* s, size_t length, int need_auth); + +struct ha_msg* string2msg_ll(const char * s, size_t length, int need_auth, int depth); + +extern int struct_stringlen(size_t namlen, size_t vallen, const void* value); +extern int struct_netstringlen(size_t namlen, size_t vallen, const void* value); +extern int process_netstring_nvpair(struct ha_msg* m, const char* nvpair, int nvlen); +static char* msg2wirefmt_ll(struct ha_msg*m, size_t* len, gboolean need_compress); +extern GHashTable* CompressFuncs; + + +void +cl_set_traditional_compression(gboolean value) +{ + use_traditional_compression = value; + if (use_traditional_compression && CompressFuncs) { + cl_log(LOG_WARNING + , "Traditional compression selected" + ". Realtime behavior will likely be impacted(!)"); + cl_log(LOG_INFO + , "See %s for more information." + , HAURL("Ha.cf#traditional_compression_-_controls_compression_mode")); + } +} + +void +cl_set_compression_threshold(size_t threadhold) +{ + compression_threshold = threadhold; + +} + +void +cl_msg_setstats(volatile hb_msg_stats_t* stats) +{ + msgstats = stats; +} + +static int msg_stats_fd = -1; + +static int +cl_msg_stats_open(const char* filename) +{ + if (filename == NULL){ + cl_log(LOG_ERR, "%s: filename is NULL", __FUNCTION__); + return -1; + } + + return open(filename, O_WRONLY|O_CREAT|O_APPEND, 0644); + +} + +static int +cl_msg_stats_close(void) +{ + if (msg_stats_fd > 0){ + close(msg_stats_fd); + } + + msg_stats_fd = -1; + + return HA_OK; +} + +#define STATSFILE "/var/log/ha_msg_stats" +int +cl_msg_stats_add(longclock_t time, int size) +{ + char buf[MAXLINE]; + int len; + + if (msg_stats_fd < 0){ + msg_stats_fd = cl_msg_stats_open(STATSFILE); + if (msg_stats_fd < 0){ + cl_log(LOG_ERR, "%s:opening file failed", + __FUNCTION__); + return HA_FAIL; + } + } + + + sprintf(buf, "%lld %d\n", (long long)time, size); + len = strnlen(buf, MAXLINE); + if (write(msg_stats_fd, buf, len) == len){ + cl_msg_stats_close(); + return HA_OK; + } + + cl_msg_stats_close(); + + return HA_FAIL;; + +} + + +/* Set default messaging format */ +void +cl_set_msg_format(enum cl_msgfmt mfmt) +{ + msgfmt = mfmt; +} + +void +cl_dump_msgstats(void) +{ + if (msgstats){ + cl_log(LOG_INFO, "dumping msg stats: " + "allocmsgs=%lu", + msgstats->allocmsgs); + } + return; +} +void +list_cleanup(GList* list) +{ + size_t i; + for (i = 0; i < g_list_length(list); i++){ + char* element = g_list_nth_data(list, i); + if (element == NULL){ + cl_log(LOG_WARNING, "list_cleanup:" + "element is NULL"); + continue; + } + free(element); + } + g_list_free(list); +} + + + +/* Create a new (empty) message */ +struct ha_msg * +ha_msg_new(int nfields) +{ + struct ha_msg * ret; + int nalloc; + + ret = MALLOCT(struct ha_msg); + if (ret) { + ret->nfields = 0; + + if (nfields > MINFIELDS) { + nalloc = nfields; + } else { + nalloc = MINFIELDS; + } + + ret->nalloc = nalloc; + ret->names = (char **)calloc(sizeof(char *), nalloc); + ret->nlens = (size_t *)calloc(sizeof(size_t), nalloc); + ret->values = (void **)calloc(sizeof(void *), nalloc); + ret->vlens = (size_t *)calloc(sizeof(size_t), nalloc); + ret->types = (int*)calloc(sizeof(int), nalloc); + + if (ret->names == NULL || ret->values == NULL + || ret->nlens == NULL || ret->vlens == NULL + || ret->types == NULL) { + + cl_log(LOG_ERR, "%s" + , "ha_msg_new: out of memory for ha_msg"); + /* It is safe to give this to ha_msg_del() */ + /* at this point. It's well-enough-formed */ + ha_msg_del(ret); /*violated property*/ + ret = NULL; + }else if (msgstats) { + msgstats->allocmsgs++; + msgstats->totalmsgs++; + msgstats->lastmsg = time_longclock(); + } + } + return(ret); +} + +/* Delete (destroy) a message */ +void +ha_msg_del(struct ha_msg *msg) +{ + if (msg) { + int j; + PARANOIDAUDITMSG(msg); + if (msgstats) { + msgstats->allocmsgs--; + } + if (msg->names) { + for (j=0; j < msg->nfields; ++j) { + if (msg->names[j]) { + free(msg->names[j]); + msg->names[j] = NULL; + } + } + free(msg->names); + msg->names = NULL; + } + if (msg->values) { + for (j=0; j < msg->nfields; ++j) { + + if (msg->values[j] == NULL){ + continue; + } + + if(msg->types[j] < DIMOF(fieldtypefuncs)){ + fieldtypefuncs[msg->types[j]].memfree(msg->values[j]); + } + } + free(msg->values); + msg->values = NULL; + } + if (msg->nlens) { + free(msg->nlens); + msg->nlens = NULL; + } + if (msg->vlens) { + free(msg->vlens); + msg->vlens = NULL; + } + if (msg->types){ + free(msg->types); + msg->types = NULL; + } + msg->nfields = -1; + msg->nalloc = -1; + free(msg); + } +} +struct ha_msg* +ha_msg_copy(const struct ha_msg *msg) +{ + struct ha_msg* ret; + int j; + + + PARANOIDAUDITMSG(msg); + if (msg == NULL || (ret = ha_msg_new(msg->nalloc)) == NULL) { + return NULL; + } + + ret->nfields = msg->nfields; + + memcpy(ret->nlens, msg->nlens, sizeof(msg->nlens[0])*msg->nfields); + memcpy(ret->vlens, msg->vlens, sizeof(msg->vlens[0])*msg->nfields); + memcpy(ret->types, msg->types, sizeof(msg->types[0])*msg->nfields); + + for (j=0; j < msg->nfields; ++j) { + + if ((ret->names[j] = malloc(msg->nlens[j]+1)) == NULL) { + goto freeandleave; + } + memcpy(ret->names[j], msg->names[j], msg->nlens[j]+1); + + + if(msg->types[j] < DIMOF(fieldtypefuncs)){ + ret->values[j] = fieldtypefuncs[msg->types[j]].dup(msg->values[j], + msg->vlens[j]); + if (!ret->values[j]){ + cl_log(LOG_ERR,"duplicating the message field failed"); + goto freeandleave; + } + } + } + return ret; + +freeandleave: + /* + * ha_msg_del nicely handles partially constructed ha_msgs + * so, there's not really a memory leak here at all, but BEAM + * thinks there is. + */ + ha_msg_del(ret);/* memory leak */ ret=NULL; + return ret; +} + +#ifdef DOAUDITS +void +ha_msg_audit(const struct ha_msg* msg) +{ + int doabort = FALSE; + int j; + + if (!msg) { + return; + } + if (!msg) { + cl_log(LOG_CRIT, "Message @ %p is not allocated" + , msg); + abort(); + } + if (msg->nfields < 0) { + cl_log(LOG_CRIT, "Message @ %p has negative fields (%d)" + , msg, msg->nfields); + doabort = TRUE; + } + if (msg->nalloc < 0) { + cl_log(LOG_CRIT, "Message @ %p has negative nalloc (%d)" + , msg, msg->nalloc); + doabort = TRUE; + } + + if (!msg->names) { + cl_log(LOG_CRIT + , "Message names @ %p is not allocated" + , msg->names); + doabort = TRUE; + } + if (!msg->values) { + cl_log(LOG_CRIT + , "Message values @ %p is not allocated" + , msg->values); + doabort = TRUE; + } + if (!msg->nlens) { + cl_log(LOG_CRIT + , "Message nlens @ %p is not allocated" + , msg->nlens); + doabort = TRUE; + } + if (!msg->vlens) { + cl_log(LOG_CRIT + , "Message vlens @ %p is not allocated" + , msg->vlens); + doabort = TRUE; + } + if (doabort) { + cl_log_message(LOG_INFO,msg); + abort(); + } + for (j=0; j < msg->nfields; ++j) { + + if (msg->nlens[j] == 0){ + cl_log(LOG_ERR, "zero namelen found in msg"); + abort(); + } + + if (msg->types[j] == FT_STRING){ + if (msg->vlens[j] != strlen(msg->values[j])){ + cl_log(LOG_ERR, "stringlen does not match"); + cl_log_message(LOG_INFO,msg); + abort(); + } + } + + if (!msg->names[j]) { + cl_log(LOG_CRIT, "Message name[%d] @ 0x%p" + " is not allocated." , + j, msg->names[j]); + abort(); + } + if (msg->types[j] != FT_LIST && !msg->values[j]) { + cl_log(LOG_CRIT, "Message value [%d] @ 0x%p" + " is not allocated.", j, msg->values[j]); + cl_log_message(LOG_INFO, msg); + abort(); + } + } +} +#endif + + + +int +ha_msg_expand(struct ha_msg* msg ) +{ + char ** names ; + size_t *nlens ; + void ** values ; + size_t* vlens ; + int * types ; + int nalloc; + + if(!msg){ + cl_log(LOG_ERR, "ha_msg_expand:" + "input msg is null"); + return HA_FAIL; + } + + names = msg->names; + nlens = msg->nlens; + values = msg->values; + vlens = msg->vlens; + types = msg->types; + + nalloc = msg->nalloc + MINFIELDS; + msg->names = (char **)calloc(sizeof(char *), nalloc); + msg->nlens = (size_t *)calloc(sizeof(size_t), nalloc); + msg->values = (void **)calloc(sizeof(void *), nalloc); + msg->vlens = (size_t *)calloc(sizeof(size_t), nalloc); + msg->types= (int*)calloc(sizeof(int), nalloc); + + if (msg->names == NULL || msg->values == NULL + || msg->nlens == NULL || msg->vlens == NULL + || msg->types == NULL) { + + cl_log(LOG_ERR, "%s" + , " out of memory for ha_msg"); + return(HA_FAIL); + } + + memcpy(msg->names, names, msg->nalloc*sizeof(char *)); + memcpy(msg->nlens, nlens, msg->nalloc*sizeof(size_t)); + memcpy(msg->values, values, msg->nalloc*sizeof(void *)); + memcpy(msg->vlens, vlens, msg->nalloc*sizeof(size_t)); + memcpy(msg->types, types, msg->nalloc*sizeof(int)); + + free(names); + free(nlens); + free(values); + free(vlens); + free(types); + + msg->nalloc = nalloc; + + return HA_OK; +} + +int +cl_msg_remove_value(struct ha_msg* msg, const void* value) +{ + int j; + + if (msg == NULL || value == NULL){ + cl_log(LOG_ERR, "cl_msg_remove: invalid argument"); + return HA_FAIL; + } + + for (j = 0; j < msg->nfields; ++j){ + if (value == msg->values[j]){ + break; + } + } + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_remove: field %p not found", value); + return HA_FAIL; + } + return cl_msg_remove_offset(msg, j); + +} + + +int +cl_msg_remove(struct ha_msg* msg, const char* name) +{ + int j; + + if (msg == NULL || name == NULL){ + cl_log(LOG_ERR, "cl_msg_remove: invalid argument"); + return HA_FAIL; + } + + for (j = 0; j < msg->nfields; ++j){ + if (strcmp(name, msg->names[j]) == 0){ + break; + } + } + + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_remove: field %s not found", name); + return HA_FAIL; + } + return cl_msg_remove_offset(msg, j); +} + +int +cl_msg_remove_offset(struct ha_msg* msg, int offset) +{ + int j = offset; + int i; + + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_remove: field %d not found", j); + return HA_FAIL; + } + + free(msg->names[j]); + fieldtypefuncs[msg->types[j]].memfree(msg->values[j]); + + for (i= j + 1; i < msg->nfields ; i++){ + msg->names[i -1] = msg->names[i]; + msg->nlens[i -1] = msg->nlens[i]; + msg->values[i -1] = msg->values[i]; + msg->vlens[i-1] = msg->vlens[i]; + msg->types[i-1] = msg->types[i]; + } + msg->nfields--; + + + return HA_OK; +} + + + +/* low level implementation for ha_msg_add + the caller is responsible to allocate/free memories + for @name and @value. + +*/ + +static int +ha_msg_addraw_ll(struct ha_msg * msg, char * name, size_t namelen, + void * value, size_t vallen, int type, int depth) +{ + + size_t startlen = sizeof(MSG_START)-1; + + + int (*addfield) (struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth); + + if (!msg || msg->names == NULL || (msg->values == NULL) ) { + cl_log(LOG_ERR, "ha_msg_addraw_ll: cannot add field to ha_msg"); + return(HA_FAIL); + } + + if (msg->nfields >= msg->nalloc) { + if( ha_msg_expand(msg) != HA_OK){ + cl_log(LOG_ERR, "message expanding failed"); + return(HA_FAIL); + } + + } + + if (namelen >= startlen + && name[0] == '>' + && strncmp(name, MSG_START, startlen) == 0) { + if(!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR, "ha_msg_addraw_ll: illegal field"); + } + return(HA_FAIL); + } + + if (name == NULL || (value == NULL) + || namelen <= 0 || vallen < 0) { + cl_log(LOG_ERR, "ha_msg_addraw_ll: " + "cannot add name/value to ha_msg"); + return(HA_FAIL); + } + + HA_MSG_ASSERT(type < DIMOF(fieldtypefuncs)); + + addfield = fieldtypefuncs[type].addfield; + if (!addfield || + addfield(msg, name, namelen, value, vallen,depth) != HA_OK){ + cl_log(LOG_ERR, "ha_msg_addraw_ll: addfield failed"); + return(HA_FAIL); + } + + PARANOIDAUDITMSG(msg); + + return(HA_OK); + + +} + +static int +ha_msg_addraw(struct ha_msg * msg, const char * name, size_t namelen, + const void * value, size_t vallen, int type, int depth) +{ + + char *cpvalue = NULL; + char *cpname = NULL; + int ret; + + + if (namelen == 0){ + cl_log(LOG_ERR, "%s: Adding a field with 0 name length", __FUNCTION__); + return HA_FAIL; + } + + if ((cpname = malloc(namelen+1)) == NULL) { + cl_log(LOG_ERR, "ha_msg_addraw: no memory for string (name)"); + return(HA_FAIL); + } + strncpy(cpname, name, namelen); + cpname[namelen] = EOS; + + HA_MSG_ASSERT(type < DIMOF(fieldtypefuncs)); + + if (fieldtypefuncs[type].dup){ + cpvalue = fieldtypefuncs[type].dup(value, vallen); + } + if (cpvalue == NULL){ + cl_log(LOG_ERR, "ha_msg_addraw: copying message failed"); + free(cpname); + return(HA_FAIL); + } + + ret = ha_msg_addraw_ll(msg, cpname, namelen, cpvalue, vallen + , type, depth); + + if (ret != HA_OK){ + cl_log(LOG_ERR, "ha_msg_addraw(): ha_msg_addraw_ll failed"); + free(cpname); + fieldtypefuncs[type].memfree(cpvalue); + } + + return(ret); + +} + +/*Add a null-terminated name and binary value to a message*/ +int +ha_msg_addbin(struct ha_msg * msg, const char * name, + const void * value, size_t vallen) +{ + + return(ha_msg_addraw(msg, name, strlen(name), + value, vallen, FT_BINARY, 0)); + +} + +int +ha_msg_adduuid(struct ha_msg* msg, const char *name, const cl_uuid_t* u) +{ + return(ha_msg_addraw(msg, name, strlen(name), + u, sizeof(cl_uuid_t), FT_BINARY, 0)); +} + +/*Add a null-terminated name and struct value to a message*/ +int +ha_msg_addstruct(struct ha_msg * msg, const char * name, const void * value) +{ + const struct ha_msg* childmsg = (const struct ha_msg*) value; + + if (get_netstringlen(childmsg) > MAXCHILDMSGLEN + || get_stringlen(childmsg) > MAXCHILDMSGLEN) { + /*cl_log(LOG_WARNING, + "%s: childmsg too big (name=%s, nslen=%d, len=%d)." + " Use ha_msg_addstruct_compress() instead.", + __FUNCTION__, name, get_netstringlen(childmsg), + get_stringlen(childmsg)); + */ + } + + return ha_msg_addraw(msg, name, strlen(name), value, + sizeof(struct ha_msg), FT_STRUCT, 0); +} + +int +ha_msg_addstruct_compress(struct ha_msg * msg, const char * name, const void * value) +{ + + if (use_traditional_compression){ + return ha_msg_addraw(msg, name, strlen(name), value, + sizeof(struct ha_msg), FT_STRUCT, 0); + }else{ + return ha_msg_addraw(msg, name, strlen(name), value, + sizeof(struct ha_msg), FT_UNCOMPRESS, 0); + } +} + +int +ha_msg_add_int(struct ha_msg * msg, const char * name, int value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%d", value); + return (ha_msg_add(msg, name, buf)); +} + +int +ha_msg_mod_int(struct ha_msg * msg, const char * name, int value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%d", value); + return (cl_msg_modstring(msg, name, buf)); +} + +int +ha_msg_value_int(const struct ha_msg * msg, const char * name, int* value) +{ + const char* svalue = ha_msg_value(msg, name); + if(NULL == svalue) { + return HA_FAIL; + } + *value = atoi(svalue); + return HA_OK; +} + +int +ha_msg_add_ul(struct ha_msg * msg, const char * name, unsigned long value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%lu", value); + return (ha_msg_add(msg, name, buf)); +} + +int +ha_msg_mod_ul(struct ha_msg * msg, const char * name, unsigned long value) +{ + char buf[MAX_INT_LEN]; + snprintf(buf, MAX_INT_LEN, "%lu", value); + return (cl_msg_modstring(msg, name, buf)); +} + +int +ha_msg_value_ul(const struct ha_msg * msg, const char * name, unsigned long* value) +{ + const char* svalue = ha_msg_value(msg, name); + if(NULL == svalue) { + return HA_FAIL; + } + *value = strtoul(svalue, NULL, 10); + return HA_OK; +} + +/* + * ha_msg_value_str_list()/ha_msg_add_str_list(): + * transform a string list suitable for putting into an ha_msg is by a convention + * of naming the fields into the following format: + * listname1=foo + * listname2=bar + * listname3=stuff + * etc. + */ + +GList* +ha_msg_value_str_list(struct ha_msg * msg, const char * name) +{ + + int i = 1; + int len = 0; + const char* value; + char* element; + GList* list = NULL; + + + if( NULL==msg||NULL==name||strnlen(name, MAX_NAME_LEN)>=MAX_NAME_LEN ){ + return NULL; + } + len = cl_msg_list_length(msg,name); + for(i=0; infields; i++) { + if( FT_STRING != msg->types[i] ) { + continue; + } + g_hash_table_insert(hash_table, + g_strndup(msg->names[i],msg->nlens[i]), + g_strndup(msg->values[i],msg->vlens[i])); + } + return hash_table; +} + +GHashTable* +ha_msg_value_str_table(struct ha_msg * msg, const char * name) +{ + struct ha_msg* hash_msg; + GHashTable * hash_table = NULL; + + if (NULL == msg || NULL == name) { + return NULL; + } + + hash_msg = cl_get_struct(msg, name); + if (NULL == hash_msg) { + return NULL; + } + hash_table = msg_to_str_table(hash_msg); + return hash_table; +} + +int +ha_msg_add_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table) +{ + struct ha_msg* hash_msg; + if (NULL == msg || NULL == name || NULL == hash_table) { + return HA_FAIL; + } + + hash_msg = str_table_to_msg(hash_table); + if( HA_OK != ha_msg_addstruct(msg, name, hash_msg)) { + ha_msg_del(hash_msg); + cl_log(LOG_ERR + , "ha_msg_addstruct in ha_msg_add_str_table failed"); + return HA_FAIL; + } + ha_msg_del(hash_msg); + return HA_OK; +} + +int +ha_msg_mod_str_table(struct ha_msg * msg, const char * name, + GHashTable* hash_table) +{ + struct ha_msg* hash_msg; + if (NULL == msg || NULL == name || NULL == hash_table) { + return HA_FAIL; + } + + hash_msg = str_table_to_msg(hash_table); + if( HA_OK != cl_msg_modstruct(msg, name, hash_msg)) { + ha_msg_del(hash_msg); + cl_log(LOG_ERR + , "ha_msg_modstruct in ha_msg_mod_str_table failed"); + return HA_FAIL; + } + ha_msg_del(hash_msg); + return HA_OK; +} + +int +cl_msg_list_add_string(struct ha_msg* msg, const char* name, const char* value) +{ + GList* list = NULL; + int ret; + + if(!msg || !name || !value){ + cl_log(LOG_ERR, "cl_msg_list_add_string: input invalid"); + return HA_FAIL; + } + + + list = g_list_append(list, UNCONST_CAST_POINTER(gpointer, value)); + if (!list){ + cl_log(LOG_ERR, "cl_msg_list_add_string: append element to" + "a glist failed"); + return HA_FAIL; + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + + g_list_free(list); + + return ret; + +} + +/* Add a null-terminated name and value to a message */ +int +ha_msg_add(struct ha_msg * msg, const char * name, const char * value) +{ + if(name == NULL || value == NULL) { + return HA_FAIL; + } + return(ha_msg_nadd(msg, name, strlen(name), value, strlen(value))); +} + +/* Add a name/value pair to a message (with sizes for name and value) */ +int +ha_msg_nadd(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen) +{ + return(ha_msg_addraw(msg, name, namelen, value, vallen, FT_STRING, 0)); + +} + +/* Add a name/value/type to a message (with sizes for name and value) */ +int +ha_msg_nadd_type(struct ha_msg * msg, const char * name, int namelen + , const char * value, int vallen, int type) +{ + return(ha_msg_addraw(msg, name, namelen, value, vallen, type, 0)); + +} + + + +/* Add a "name=value" line to the name, value pairs in a message */ +static int +ha_msg_add_nv_depth(struct ha_msg* msg, const char * nvline, + const char * bufmax, int depth) +{ + int namelen; + const char * valp; + int vallen; + + if (!nvline) { + cl_log(LOG_ERR, "ha_msg_add_nv: NULL nvline"); + return(HA_FAIL); + } + /* How many characters before the '='? */ + if ((namelen = strcspn(nvline, EQUAL)) <= 0 + || nvline[namelen] != '=') { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "ha_msg_add_nv_depth: line doesn't contain '='"); + cl_log(LOG_INFO, "%s", nvline); + } + return(HA_FAIL); + } + valp = nvline + namelen +1; /* Point just *past* the '=' */ + if (valp >= bufmax){ + return HA_FAIL; + } + vallen = strcspn(valp, NEWLINE); + if ((valp + vallen) >= bufmax){ + return HA_FAIL; + } + + if (vallen == 0){ + valp = NULL; + } + /* Call ha_msg_nadd to actually add the name/value pair */ + return(ha_msg_addraw(msg, nvline, namelen, valp, vallen + , FT_STRING, depth)); + +} + +int +ha_msg_add_nv(struct ha_msg* msg, const char * nvline, + const char * bufmax) +{ + + return(ha_msg_add_nv_depth(msg, nvline, bufmax, 0)); + +} + + +static void * +cl_get_value(const struct ha_msg * msg, const char * name, + size_t * vallen, int *type) +{ + + int j; + if (!msg || !msg->names || !msg->values) { + cl_log(LOG_ERR, "%s: wrong argument (%s)", + __FUNCTION__, name); + return(NULL); + } + + PARANOIDAUDITMSG(msg); + for (j=0; j < msg->nfields; ++j) { + const char *local_name = msg->names[j]; + if (name[0] == local_name[0] + && strcmp(name, local_name) == 0) { + if (vallen){ + *vallen = msg->vlens[j]; + } + if (type){ + *type = msg->types[j]; + } + return(msg->values[j]); + } + } + return(NULL); +} + +static void * +cl_get_value_mutate(struct ha_msg * msg, const char * name, + size_t * vallen, int *type) +{ + + int j; + if (!msg || !msg->names || !msg->values) { + cl_log(LOG_ERR, "%s: wrong argument", + __FUNCTION__); + return(NULL); + } + + AUDITMSG(msg); + for (j=0; j < msg->nfields; ++j) { + if (strcmp(name, msg->names[j]) == 0) { + int tp = msg->types[j]; + if (fieldtypefuncs[tp].pregetaction){ + fieldtypefuncs[tp].pregetaction(msg, j); + } + + if (vallen){ + *vallen = msg->vlens[j]; + } + if (type){ + *type = msg->types[j]; + } + return(msg->values[j]); + } + } + return(NULL); +} + + +const void * +cl_get_binary(const struct ha_msg *msg, + const char * name, size_t * vallen) +{ + + const void *ret; + int type; + + ret = cl_get_value( msg, name, vallen, &type); + + if (ret == NULL){ + /* + cl_log(LOG_WARNING, "field %s not found", name); + cl_log_message(msg); + */ + return(NULL); + } + if ( type != FT_BINARY){ + cl_log(LOG_WARNING, "field %s is not binary", name); + cl_log_message(LOG_WARNING, msg); + return(NULL); + } + + return(ret); +} + +/* UUIDs are stored with a machine-independent byte ordering (even though it's binary) */ +int +cl_get_uuid(const struct ha_msg *msg, const char * name, cl_uuid_t* retval) +{ + const void * vret; + size_t vretsize; + + cl_uuid_clear(retval); + + if ((vret = cl_get_binary(msg, name, &vretsize)/*discouraged function*/) == NULL) { + /* But perfectly portable in this case */ + return HA_FAIL; + } + if (vretsize != sizeof(cl_uuid_t)) { + cl_log(LOG_WARNING, "Binary field %s is not a uuid.", name); + cl_log(LOG_INFO, "expecting %d bytes, got %d bytes", + (int)sizeof(cl_uuid_t), (int)vretsize); + cl_log_message(LOG_INFO, msg); + return HA_FAIL; + } + memcpy(retval, vret, sizeof(cl_uuid_t)); + return HA_OK; +} + +const char * +cl_get_string(const struct ha_msg *msg, const char *name) +{ + + const void *ret; + int type; + ret = cl_get_value( msg, name, NULL, &type); + + if (ret == NULL || type != FT_STRING){ + return(NULL); + } + + return(ret); + +} + +int +cl_get_type(const struct ha_msg *msg, const char *name) +{ + + const void *ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if (ret == NULL) { + return -1; + } + if (type < 0){ + cl_log(LOG_WARNING, "field %s not a valid type" + , name); + return(-1); + } + + return(type); + +} + +/* +struct ha_msg * +cl_get_struct(const struct ha_msg *msg, const char* name) +{ + struct ha_msg* ret; + int type; + size_t vallen; + + ret = cl_get_value(msg, name, &vallen, &type); + + if (ret == NULL ){ + return(NULL); + } + + switch(type){ + + case FT_STRUCT: + break; + + default: + cl_log(LOG_ERR, "%s: field %s is not a struct (%d)", + __FUNCTION__, name, type); + return NULL; + } + + return ret; +} +*/ + + +struct ha_msg * +cl_get_struct(struct ha_msg *msg, const char* name) +{ + struct ha_msg* ret; + int type = -1; + size_t vallen; + + ret = cl_get_value_mutate(msg, name, &vallen, &type); + + if (ret == NULL ){ + return(NULL); + } + + switch(type){ + + case FT_UNCOMPRESS: + case FT_STRUCT: + break; + + default: + cl_log(LOG_ERR, "%s: field %s is not a struct (%d)", + __FUNCTION__, name, type); + return NULL; + } + + return ret; +} + + +int +cl_msg_list_length(struct ha_msg* msg, const char* name) +{ + GList* ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if ( ret == NULL || type != FT_LIST){ + return -1; + } + + return g_list_length(ret); + +} + + +void* +cl_msg_list_nth_data(struct ha_msg* msg, const char* name, int n) +{ + GList* ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if ( ret == NULL || type != FT_LIST){ + cl_log(LOG_WARNING, "field %s not found " + " or type mismatch", name); + return NULL; + } + + return g_list_nth_data(ret, n); + +} + +int +cl_msg_add_list(struct ha_msg* msg, const char* name, GList* list) +{ + int ret; + + if(msg == NULL|| name ==NULL || list == NULL){ + cl_log(LOG_ERR, "cl_msg_add_list:" + "invalid arguments"); + return HA_FAIL; + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + + return ret; +} + +GList* +cl_msg_get_list(struct ha_msg* msg, const char* name) +{ + GList* ret; + int type; + + ret = cl_get_value( msg, name, NULL, &type); + + if ( ret == NULL || type != FT_LIST){ + cl_log(LOG_WARNING, "field %s not found " + " or type mismatch", name); + return NULL; + } + + return ret; +} + + +int +cl_msg_add_list_str(struct ha_msg* msg, const char* name, + char** buf, size_t n) +{ + GList* list = NULL; + int i; + int ret = HA_FAIL; + + if (n <= 0 || buf == NULL|| name ==NULL ||msg == NULL){ + cl_log(LOG_ERR, "%s:" + "invalid parameter(%s)", + !n <= 0?"n is negative or zero": + !buf?"buf is NULL": + !name?"name is NULL": + "msg is NULL",__FUNCTION__); + return HA_FAIL; + } + + for ( i = 0; i < n; i++){ + if (buf[i] == NULL){ + cl_log(LOG_ERR, "%s: %dth element in buf is null", + __FUNCTION__, i); + goto free_and_out; + } + list = g_list_append(list, buf[i]); + if (list == NULL){ + cl_log(LOG_ERR, "%s:adding integer to list failed", + __FUNCTION__); + goto free_and_out; + } + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + + free_and_out: + if (list){ + g_list_free(list); + list = NULL; + } + return ret; +} + +static void +list_element_free(gpointer data, gpointer userdata) +{ + if (data){ + g_free(data); + } +} + +int +cl_msg_add_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t n) +{ + + GList* list = NULL; + size_t i; + int ret = HA_FAIL; + + if (n <= 0 || buf == NULL|| name ==NULL ||msg == NULL){ + cl_log(LOG_ERR, "cl_msg_add_list_int:" + "invalid parameter(%s)", + !n <= 0?"n is negative or zero": + !buf?"buf is NULL": + !name?"name is NULL": + "msg is NULL"); + goto free_and_out; + } + + for ( i = 0; i < n; i++){ + char intstr[MAX_INT_LEN]; + sprintf(intstr,"%d", buf[i]); + list = g_list_append(list, g_strdup(intstr)); + if (list == NULL){ + cl_log(LOG_ERR, "cl_msg_add_list_int:" + "adding integer to list failed"); + goto free_and_out; + } + } + + ret = ha_msg_addraw(msg, name, strlen(name), list, + string_list_pack_length(list), + FT_LIST, 0); + free_and_out: + if (list){ + g_list_foreach(list,list_element_free , NULL); + g_list_free(list); + list = NULL; + } + + return ret; +} +int +cl_msg_get_list_int(struct ha_msg* msg, const char* name, + int* buf, size_t* n) +{ + GList* list; + size_t len; + int i; + GList* list_element; + + + if (n == NULL || buf == NULL|| name ==NULL ||msg == NULL){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "invalid parameter(%s)", + !n?"n is NULL": + !buf?"buf is NULL": + !name?"name is NULL": + "msg is NULL"); + return HA_FAIL; + } + + list = cl_msg_get_list(msg, name); + if (list == NULL){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "list of integers %s not found", name); + return HA_FAIL; + } + + len = g_list_length(list); + if (len > *n){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "buffer too small: *n=%ld, required len=%ld", + (long)*n, (long)len); + *n = len; + return HA_FAIL; + } + + *n = len; + i = 0; + list_element = g_list_first(list); + while( list_element != NULL){ + char* intstr = list_element->data; + if (intstr == NULL){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "element data is NULL"); + return HA_FAIL; + } + + if (sscanf(intstr,"%d", &buf[i]) != 1){ + cl_log(LOG_ERR, "cl_msg_get_list_int:" + "element data is NULL"); + return HA_FAIL; + } + + i++; + list_element = g_list_next(list_element); + } + + return HA_OK; +} + +int +cl_msg_replace_value(struct ha_msg* msg, const void *old_value, + const void* value, size_t vlen, int type) +{ + int j; + + if (msg == NULL || old_value == NULL) { + cl_log(LOG_ERR, "cl_msg_replace: invalid argument"); + return HA_FAIL; + } + + for (j = 0; j < msg->nfields; ++j){ + if (old_value == msg->values[j]){ + break; + } + } + if (j == msg->nfields){ + cl_log(LOG_ERR, "cl_msg_replace: field %p not found", old_value); + return HA_FAIL; + } + return cl_msg_replace(msg, j, value, vlen, type); +} + +/*this function is for internal use only*/ +int +cl_msg_replace(struct ha_msg* msg, int index, + const void* value, size_t vlen, int type) +{ + void * newv ; + int newlen = vlen; + int oldtype; + + PARANOIDAUDITMSG(msg); + if (msg == NULL || value == NULL) { + cl_log(LOG_ERR, "%s: NULL input.", __FUNCTION__); + return HA_FAIL; + } + + if(type >= DIMOF(fieldtypefuncs)){ + cl_log(LOG_ERR, "%s:" + "invalid type(%d)",__FUNCTION__, type); + return HA_FAIL; + } + + if (index >= msg->nfields){ + cl_log(LOG_ERR, "%s: index(%d) out of range(%d)", + __FUNCTION__,index, msg->nfields); + return HA_FAIL; + } + + oldtype = msg->types[index]; + + newv = fieldtypefuncs[type].dup(value,vlen); + if (!newv){ + cl_log(LOG_ERR, "%s: duplicating message fields failed" + "value=%p, vlen=%d, msg->names[i]=%s", + __FUNCTION__,value, (int)vlen, msg->names[index]); + return HA_FAIL; + } + + fieldtypefuncs[oldtype].memfree(msg->values[index]); + + msg->values[index] = newv; + msg->vlens[index] = newlen; + msg->types[index] = type; + PARANOIDAUDITMSG(msg); + return(HA_OK); + +} + + +static int +cl_msg_mod(struct ha_msg * msg, const char * name, + const void* value, size_t vlen, int type) +{ + int j; + int rc; + + PARANOIDAUDITMSG(msg); + if (msg == NULL || name == NULL || value == NULL) { + cl_log(LOG_ERR, "cl_msg_mod: NULL input."); + return HA_FAIL; + } + + if(type >= DIMOF(fieldtypefuncs)){ + cl_log(LOG_ERR, "cl_msg_mod:" + "invalid type(%d)", type); + return HA_FAIL; + } + + for (j=0; j < msg->nfields; ++j) { + if (strcmp(name, msg->names[j]) == 0) { + + char * newv ; + int newlen = vlen; + + if (type != msg->types[j]){ + cl_log(LOG_ERR, "%s: type mismatch(%d %d)", + __FUNCTION__, type, msg->types[j]); + return HA_FAIL; + } + + newv = fieldtypefuncs[type].dup(value,vlen); + if (!newv){ + cl_log(LOG_ERR, "duplicating message fields failed" + "value=%p, vlen=%d, msg->names[j]=%s", + value, (int)vlen, msg->names[j]); + return HA_FAIL; + } + + fieldtypefuncs[type].memfree(msg->values[j]); + msg->values[j] = newv; + msg->vlens[j] = newlen; + PARANOIDAUDITMSG(msg); + return(HA_OK); + } + } + + rc = ha_msg_nadd_type(msg, name,strlen(name), value, vlen, type); + + PARANOIDAUDITMSG(msg); + return rc; +} + +int +cl_msg_modstruct(struct ha_msg * msg, const char* name, + const struct ha_msg* value) +{ + return cl_msg_mod(msg, name, value, 0, FT_STRUCT); +} + +int +cl_msg_modbin(struct ha_msg * msg, const char* name, + const void* value, size_t vlen) +{ + return cl_msg_mod(msg, name, value, vlen, FT_BINARY); + +} +int +cl_msg_moduuid(struct ha_msg * msg, const char* name, + const cl_uuid_t* uuid) +{ + return cl_msg_mod(msg, name, uuid, sizeof(cl_uuid_t), FT_BINARY); +} + + + +/* Modify the value associated with a particular name */ +int +cl_msg_modstring(struct ha_msg * msg, const char * name, const char * value) +{ + return cl_msg_mod(msg, name, value, strlen(value), FT_STRING); +} + + + +/* Return the next message found in the stream */ +struct ha_msg * +msgfromstream(FILE * f) +{ + char buf[MAXMSGLINE]; + char * getsret; + clearerr(f); + /* Skip until we find a MSG_START (hopefully we skip nothing) */ + while(1) { + getsret = fgets(buf, sizeof(buf), f); + if (!getsret) { + break; + } + if (strcmp(buf, MSG_START) == 0) { + return msgfromstream_string(f); + + } + if (strcmp(buf, MSG_START_NETSTRING) == 0){ + return msgfromstream_netstring(f); + } + + } + + return NULL; +} + +/* Return the next message found in the stream with string format */ +struct ha_msg * +msgfromstream_string(FILE * f) +{ + char buf[MAXMSGLINE]; + const char * bufmax = buf + sizeof(buf); + struct ha_msg* ret; + char * getsret; + + + if ((ret = ha_msg_new(0)) == NULL) { + /* Getting an error with EINTR is pretty normal */ + /* (so is EOF) */ + if ( (!ferror(f) || (errno != EINTR && errno != EAGAIN)) + && !feof(f)) { + cl_log(LOG_ERR, "msgfromstream: cannot get message"); + } + return(NULL); + } + + /* Add Name=value pairs until we reach MSG_END or EOF */ + while(1) { + getsret = fgets(buf, MAXMSGLINE, f); + if (!getsret) { + break; + } + + if (strnlen(buf, MAXMSGLINE) > MAXMSGLINE - 2) { + cl_log(LOG_DEBUG + , "msgfromstream: field too long [%s]" + , buf); + } + + if (!strcmp(buf, MSG_END)) { + break; + } + + + /* Add the "name=value" string on this line to the message */ + if (ha_msg_add_nv(ret, buf, bufmax) != HA_OK) { + cl_log(LOG_ERR, "NV failure (msgfromsteam): [%s]" + , buf); + ha_msg_del(ret); ret=NULL; + return(NULL); + } + } + return(ret); +} + + +/* Return the next message found in the stream with netstring format*/ + +struct ha_msg * +msgfromstream_netstring(FILE * f) +{ + struct ha_msg * ret; + + if ((ret = ha_msg_new(0)) == NULL) { + /* Getting an error with EINTR is pretty normal */ + /* (so is EOF) */ + if ( (!ferror(f) || (errno != EINTR && errno != EAGAIN)) + && !feof(f)) { + cl_log(LOG_ERR + , "msgfromstream_netstring(): cannot get message"); + } + return(NULL); + } + + while(1) { + char* nvpair; + int nvlen; + int n; + + if (fscanf(f, "%d:", &nvlen) <= 0 || nvlen <= 0){ + return(ret); + } + + nvpair = malloc(nvlen + 2); + + if ((n =fread(nvpair, 1, nvlen + 1, f)) != nvlen + 1){ + cl_log(LOG_WARNING, "msgfromstream_netstring()" + ": Can't get enough nvpair," + "expecting %d bytes long, got %d bytes", + nvlen + 1, n); + ha_msg_del(ret); + return(NULL); + } + + process_netstring_nvpair(ret, nvpair, nvlen); + + } + +} + +static gboolean ipc_timer_expired = FALSE; + +static void cl_sigalarm_handler(int signum) +{ + if (signum == SIGALRM) { + ipc_timer_expired = TRUE; + } +} + +int +cl_ipc_wait_timeout( + IPC_Channel *chan, int (*waitfun)(IPC_Channel *chan), unsigned int timeout) +{ + int rc = IPC_FAIL; + struct sigaction old_action; + + memset(&old_action, 0, sizeof(old_action)); + cl_signal_set_simple_handler(SIGALRM, cl_sigalarm_handler, &old_action); + + ipc_timer_expired = FALSE; + + alarm(timeout); + rc = waitfun(chan); + if (rc == IPC_INTR && ipc_timer_expired) { + rc = IPC_TIMEOUT; + } + + alarm(0); /* ensure it expires */ + cl_signal_set_simple_handler(SIGALRM, old_action.sa_handler, &old_action); + + + return rc; +} + +/* Return the next message found in the IPC channel */ +static struct ha_msg* +msgfromIPC_ll(IPC_Channel * ch, int flag, unsigned int timeout, int *rc_out) +{ + int rc; + IPC_Message* ipcmsg; + struct ha_msg* hmsg; + int need_auth = flag & MSG_NEEDAUTH; + int allow_intr = flag & MSG_ALLOWINTR; + + startwait: + if(timeout > 0) { + rc = cl_ipc_wait_timeout(ch, ch->ops->waitin, timeout); + } else { + rc = ch->ops->waitin(ch); + } + + if(rc_out) { + *rc_out = rc; + } + + switch(rc) { + default: + case IPC_FAIL: + cl_perror("msgfromIPC: waitin failure"); + return NULL; + + case IPC_TIMEOUT: + return NULL; + + case IPC_BROKEN: + sleep(1); + return NULL; + + case IPC_INTR: + if ( allow_intr){ + goto startwait; + }else{ + return NULL; + } + + case IPC_OK: + break; + } + + + ipcmsg = NULL; + rc = ch->ops->recv(ch, &ipcmsg); +#if 0 + if (DEBUGPKTCONT) { + cl_log(LOG_DEBUG, "msgfromIPC: recv returns %d ipcmsg = 0x%lx" + , rc, (unsigned long)ipcmsg); + } +#endif + if(rc_out) { + *rc_out = rc; + } + + if (rc != IPC_OK) { + return NULL; + } + + hmsg = wirefmt2msg_ll((char *)ipcmsg->msg_body, ipcmsg->msg_len, need_auth); + if (ipcmsg->msg_done) { + ipcmsg->msg_done(ipcmsg); + } + + AUDITMSG(hmsg); + return hmsg; +} + +/* Return the next message found in the IPC channel */ +struct ha_msg* +msgfromIPC_timeout(IPC_Channel *ch, int flag, unsigned int timeout, int *rc_out) +{ + return msgfromIPC_ll(ch, flag, timeout, rc_out); +} + +struct ha_msg* +msgfromIPC(IPC_Channel * ch, int flag) +{ + return msgfromIPC_ll(ch, flag, 0, NULL); +} + + +struct ha_msg* +msgfromIPC_noauth(IPC_Channel * ch) +{ + int flag = 0; + + flag |= MSG_ALLOWINTR; + return msgfromIPC_ll(ch, flag, 0, NULL); +} + +/* Return the next message found in the IPC channel */ +IPC_Message * +ipcmsgfromIPC(IPC_Channel * ch) +{ + int rc; + IPC_Message* ipcmsg; + + rc = ch->ops->waitin(ch); + + switch(rc) { + default: + case IPC_FAIL: + cl_perror("msgfromIPC: waitin failure"); + return NULL; + + case IPC_BROKEN: + sleep(1); + return NULL; + + case IPC_INTR: + return NULL; + + case IPC_OK: + break; + } + + + ipcmsg = NULL; + rc = ch->ops->recv(ch, &ipcmsg); +#if 0 + if (DEBUGPKTCONT) { + cl_log(LOG_DEBUG, "msgfromIPC: recv returns %d ipcmsg = 0x%lx" + , rc, (unsigned long)ipcmsg); + } +#endif + if (rc != IPC_OK) { + return NULL; + } + + + return(ipcmsg); +} + + +/* Writes a message into a stream - used for serial lines */ +int +msg2stream(struct ha_msg* m, FILE * f) +{ + size_t len; + char * s = msg2wirefmt(m, &len); + + if (s != NULL) { + int rc = HA_OK; + if (fputs(s, f) == EOF) { + rc = HA_FAIL; + cl_perror("msg2stream: fputs failure"); + } + if (fflush(f) == EOF) { + cl_perror("msg2stream: fflush failure"); + rc = HA_FAIL; + } + free(s); + return(rc); + }else{ + return(HA_FAIL); + } +} +static void ipcmsg_done(IPC_Message* m); + +static int clmsg_ipcmsg_allocated = 0; +static int clmsg_ipcmsg_freed = 0; + +void dump_clmsg_ipcmsg_stats(void); +void +dump_clmsg_ipcmsg_stats(void) +{ + cl_log(LOG_INFO, "clmsg ipcmsg allocated=%d, freed=%d, diff=%d", + clmsg_ipcmsg_allocated, + clmsg_ipcmsg_freed, + clmsg_ipcmsg_allocated - clmsg_ipcmsg_freed); + + return; +} + +static void +ipcmsg_done(IPC_Message* m) +{ + if (!m) { + return; + } + if (m->msg_buf) { + free(m->msg_buf); + } + free(m); + m = NULL; + clmsg_ipcmsg_freed ++; +} + + + +/* + * create an ipcmsg and copy the data + */ + +IPC_Message* +wirefmt2ipcmsg(void* p, size_t len, IPC_Channel* ch) +{ + IPC_Message* ret = NULL; + + if (p == NULL){ + return(NULL); + } + + ret = MALLOCT(IPC_Message); + if (!ret) { + return(NULL); + } + + memset(ret, 0, sizeof(IPC_Message)); + + if (NULL == (ret->msg_buf = malloc(len + ch->msgpad))) { + free(ret); + return NULL; + } + ret->msg_body = (char*)ret->msg_buf + ch->msgpad; + memcpy(ret->msg_body, p, len); + + ret->msg_done = ipcmsg_done; + ret->msg_private = NULL; + ret->msg_ch = ch; + ret->msg_len = len; + + clmsg_ipcmsg_allocated ++; + + return ret; + +} + +IPC_Message* +hamsg2ipcmsg(struct ha_msg* m, IPC_Channel* ch) +{ + size_t len; + char * s = msg2wirefmt_ll(m, &len, MSG_NEEDCOMPRESS); + IPC_Message* ret = NULL; + + if (s == NULL) { + return ret; + } + ret = MALLOCT(IPC_Message); + if (!ret) { + free(s); + return ret; + } + + memset(ret, 0, sizeof(IPC_Message)); + + if (NULL == (ret->msg_buf = malloc(len + ch->msgpad))) { + free(s); + free(ret); + return NULL; + } + ret->msg_body = (char*)ret->msg_buf + ch->msgpad; + memcpy(ret->msg_body, s, len); + free(s); + + ret->msg_done = ipcmsg_done; + ret->msg_private = NULL; + ret->msg_ch = ch; + ret->msg_len = len; + + clmsg_ipcmsg_allocated ++; + + return ret; +} + +struct ha_msg* +ipcmsg2hamsg(IPC_Message*m) +{ + struct ha_msg* ret = NULL; + + + ret = wirefmt2msg(m->msg_body, m->msg_len,MSG_NEEDAUTH); + return ret; +} + +int +msg2ipcchan(struct ha_msg*m, IPC_Channel*ch) +{ + IPC_Message* imsg; + + if (m == NULL || ch == NULL) { + cl_log(LOG_ERR, "Invalid msg2ipcchan argument"); + errno = EINVAL; + return HA_FAIL; + } + + if ((imsg = hamsg2ipcmsg(m, ch)) == NULL) { + cl_log(LOG_ERR, "hamsg2ipcmsg() failure"); + return HA_FAIL; + } + + if (ch->ops->send(ch, imsg) != IPC_OK) { + if (ch->ch_status == IPC_CONNECT) { + snprintf(ch->failreason,MAXFAILREASON, + "send failed,farside_pid=%d, sendq length=%ld(max is %ld)", + ch->farside_pid, (long)ch->send_queue->current_qlen, + (long)ch->send_queue->max_qlen); + } + imsg->msg_done(imsg); + return HA_FAIL; + } + return HA_OK; +} + +static gboolean (*msg_authentication_method)(const struct ha_msg* ret) = NULL; + + +void +cl_set_oldmsgauthfunc(gboolean (*authfunc)(const struct ha_msg*)) +{ + msg_authentication_method = authfunc; +} + + + +/* Converts a string (perhaps received via UDP) into a message */ +struct ha_msg * +string2msg_ll(const char * s, size_t length, int depth, int need_auth) +{ + struct ha_msg* ret; + int startlen; + int endlen; + const char * sp = s; + const char * smax = s + length; + + + if ((ret = ha_msg_new(0)) == NULL) { + cl_log(LOG_ERR, "%s: creating new msg failed", __FUNCTION__); + return(NULL); + } + + startlen = sizeof(MSG_START)-1; + if (strncmp(sp, MSG_START, startlen) != 0) { + /* This can happen if the sender gets killed */ + /* at just the wrong time... */ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING, "string2msg_ll: no MSG_START"); + cl_log(LOG_WARNING, "%s: s=%s", __FUNCTION__, s); + cl_log(LOG_WARNING, "depth=%d", depth); + } + ha_msg_del(ret); + return(NULL); + }else{ + sp += startlen; + } + + endlen = sizeof(MSG_END)-1; + + /* Add Name=value pairs until we reach MSG_END or end of string */ + + while (*sp != EOS && strncmp(sp, MSG_END, endlen) != 0) { + + if (sp >= smax) { + cl_log(LOG_ERR, "%s: buffer overflow(sp=%p, smax=%p)", + __FUNCTION__, sp, smax); + return(NULL); + } + /* Skip over initial CR/NL things */ + sp += strspn(sp, NEWLINE); + if (sp >= smax) { + cl_log(LOG_ERR, "%s: buffer overflow after NEWLINE(sp=%p, smax=%p)", + __FUNCTION__, sp, smax); + return(NULL); + } + /* End of message marker? */ + if (strncmp(sp, MSG_END, endlen) == 0) { + break; + } + /* Add the "name=value" string on this line to the message */ + if (ha_msg_add_nv_depth(ret, sp, smax, depth) != HA_OK) { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR, "NV failure (string2msg_ll):"); + cl_log(LOG_ERR, "Input string: [%s]", s); + cl_log(LOG_ERR, "sp=%s", sp); + cl_log(LOG_ERR, "depth=%d", depth); + cl_log_message(LOG_ERR,ret); + } + ha_msg_del(ret); + return(NULL); + } + if (sp >= smax) { + cl_log(LOG_ERR, "%s: buffer overflow after adding field(sp=%p, smax=%p)", + __FUNCTION__, sp, smax); + return(NULL); + } + sp += strcspn(sp, NEWLINE); + } + + if (need_auth && msg_authentication_method + && !msg_authentication_method(ret)) { + const char* from = ha_msg_value(ret, F_ORIG); + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING, + "string2msg_ll: node [%s]" + " failed authentication", from ? from : "?"); + } + ha_msg_del(ret); + ret = NULL; + } + return(ret); +} + + + +struct ha_msg * +string2msg(const char * s, size_t length) +{ + return(string2msg_ll(s, length, 0, MSG_NEEDAUTH)); +} + + + + + + +/* Converts a message into a string (for sending out UDP interface) + + used in two places: + + 1.called by msg2string as a implementation for computing string for a + message provided the buffer + + 2.called by is_authentic. In this case, there are no start/end string + and the "auth" field is not included in the string + +*/ + +#define NOROOM { \ + cl_log(LOG_ERR, "%s:%d: out of memory bound" \ + ", bp=%p, buf + len=%p, len=%ld" \ + , __FUNCTION__, __LINE__ \ + , bp, buf + len, (long)len); \ + cl_log_message(LOG_ERR, m); \ + return(HA_FAIL); \ + } + +#define CHECKROOM_CONST(c) CHECKROOM_INT(STRLEN_CONST(c)) +#define CHECKROOM_STRING(s) CHECKROOM_INT(strnlen(s, len)) +#define CHECKROOM_STRING_INT(s,i) CHECKROOM_INT(strnlen(s, len)+(i)) +#define CHECKROOM_INT(i) { \ + if ((bp + (i)) > maxp) { \ + NOROOM; \ + } \ + } + + +int +msg2string_buf(const struct ha_msg *m, char* buf, size_t len +, int depth,int needhead) +{ + + char * bp = NULL; + int j; + char* maxp = buf + len; + + buf[0]=0; + bp = buf; + + if (needhead){ + CHECKROOM_CONST(MSG_START); + strcpy(bp, MSG_START); + bp += STRLEN_CONST(MSG_START); + } + + for (j=0; j < m->nfields; ++j) { + + int truelen; + int (*tostring)(char*, char*, void*, size_t, int); + + if (needhead == NOHEAD && strcmp(m->names[j], F_AUTH) == 0) { + continue; + } + + if (m->types[j] != FT_STRING){ + CHECKROOM_STRING_INT(FT_strings[m->types[j]],2); + strcat(bp, "("); + bp++; + strcat(bp, FT_strings[m->types[j]]); + bp++; + strcat(bp,")"); + bp++; + } + + CHECKROOM_STRING_INT(m->names[j],1); + strcat(bp, m->names[j]); + bp += m->nlens[j]; + strcat(bp, "="); + bp++; + + if(m->types[j] < DIMOF(fieldtypefuncs)){ + tostring = fieldtypefuncs[m->types[j]].tostring; + } else { + cl_log(LOG_ERR, "type(%d) unrecognized", m->types[j]); + return HA_FAIL; + } + if (!tostring || + (truelen = tostring(bp, maxp, m->values[j], m->vlens[j], depth)) + < 0){ + cl_log(LOG_ERR, "tostring failed for field %d", j); + return HA_FAIL; + } + + CHECKROOM_INT(truelen+1); + bp +=truelen; + + strcat(bp,"\n"); + bp++; + } + if (needhead){ + CHECKROOM_CONST(MSG_END); + strcat(bp, MSG_END); + bp += strlen(MSG_END); + } + + CHECKROOM_INT(1); + bp[0] = EOS; + + return(HA_OK); +} + + +char * +msg2string(const struct ha_msg *m) +{ + void *buf; + int len; + + AUDITMSG(m); + if (m->nfields <= 0) { + cl_log(LOG_ERR, "msg2string: Message with zero fields"); + return(NULL); + } + + len = get_stringlen(m); + + buf = malloc(len); + + if (buf == NULL) { + cl_log(LOG_ERR, "msg2string: no memory for string"); + return(NULL); + } + + if (msg2string_buf(m, buf, len ,0, NEEDHEAD) != HA_OK){ + cl_log(LOG_ERR, "msg2string: msg2string_buf failed"); + free(buf); + return(NULL); + } + + return(buf); +} + +gboolean +must_use_netstring(const struct ha_msg* msg) +{ + int i; + + for ( i = 0; i < msg->nfields; i++){ + if (msg->types[i] == FT_COMPRESS + || msg->types[i] == FT_UNCOMPRESS + || msg->types[i] == FT_STRUCT){ + return TRUE; + } + } + + return FALSE; + +} + +#define use_netstring(m) (msgfmt == MSGFMT_NETSTRING || must_use_netstring(m)) + +static char* +msg2wirefmt_ll(struct ha_msg*m, size_t* len, int flag) +{ + + int wirefmtlen; + int i; + int netstg = use_netstring(m); + + wirefmtlen = netstg ? get_netstringlen(m) : get_stringlen(m); + if (use_traditional_compression + &&(flag & MSG_NEEDCOMPRESS) + && (wirefmtlen> compression_threshold) + && cl_get_compress_fns() != NULL){ + return cl_compressmsg(m, len); + } + + if (flag & MSG_NEEDCOMPRESS){ + for (i=0 ;i < m->nfields; i++){ + int type = m->types[i]; + if (fieldtypefuncs[type].prepackaction){ + fieldtypefuncs[type].prepackaction(m,i); + } + } + } + + wirefmtlen = netstg ? get_netstringlen(m) : get_stringlen(m); + if (wirefmtlen >= MAXMSG){ + if (flag&MSG_NEEDCOMPRESS) { + if (cl_get_compress_fns() != NULL) + return cl_compressmsg(m, len); + } + cl_log(LOG_ERR, "%s: msg too big(%d)", + __FUNCTION__, wirefmtlen); + return NULL; + } + if (flag & MSG_NEEDAUTH) { + return msg2netstring(m, len); + } + return msg2wirefmt_noac(m, len); +} + +char* +msg2wirefmt(struct ha_msg*m, size_t* len){ + return msg2wirefmt_ll(m, len, MSG_NEEDAUTH|MSG_NEEDCOMPRESS); +} + +char* +msg2wirefmt_noac(struct ha_msg*m, size_t* len) +{ + if (use_netstring(m)) { + return msg2netstring_noauth(m, len); + } else { + char *tmp; + + tmp = msg2string(m); + if(tmp == NULL){ + *len = 0; + return NULL; + } + *len = strlen(tmp) + 1; + return tmp; + } +} + +static struct ha_msg* +wirefmt2msg_ll(const char* s, size_t length, int need_auth) +{ + + size_t startlen; + struct ha_msg* msg = NULL; + + + startlen = sizeof(MSG_START)-1; + + if (startlen > length){ + return NULL; + } + + if (strncmp( s, MSG_START, startlen) == 0) { + msg = string2msg_ll(s, length, 0, need_auth); + goto out; + } + + startlen = sizeof(MSG_START_NETSTRING) - 1; + + if (startlen > length){ + return NULL; + } + + if (strncmp(s, MSG_START_NETSTRING, startlen) == 0) { + msg = netstring2msg(s, length, need_auth); + goto out; + } + +out: + if (msg && is_compressed_msg(msg)){ + struct ha_msg* ret; + if ((ret = cl_decompressmsg(msg))==NULL){ + cl_log(LOG_ERR, "decompress msg failed"); + ha_msg_del(msg); + return NULL; + } + ha_msg_del(msg); + return ret; + } + return msg; + +} + + + + +struct ha_msg* +wirefmt2msg(const char* s, size_t length, int flag) +{ + return wirefmt2msg_ll(s, length, flag& MSG_NEEDAUTH); + +} + + +void +cl_log_message (int log_level, const struct ha_msg *m) +{ + int j; + + if(m == NULL) { + cl_log(log_level, "MSG: No message to dump"); + return; + } + + cl_log(log_level, "MSG: Dumping message with %d fields", m->nfields); + + for (j=0; j < m->nfields; ++j) { + + if(m->types[j] < DIMOF(fieldtypefuncs)){ + fieldtypefuncs[m->types[j]].display(log_level, j, + m->names[j], + m->values[j], + m->vlens[j]); + } + } +} + + +#ifdef TESTMAIN_MSGS +int +main(int argc, char ** argv) +{ + struct ha_msg* m; + while (!feof(stdin)) { + if ((m=controlfifo2msg(stdin)) != NULL) { + fprintf(stderr, "Got message!\n"); + if (msg2stream(m, stdout) == HA_OK) { + fprintf(stderr, "Message output OK!\n"); + }else{ + fprintf(stderr, "Could not output Message!\n"); + } + }else{ + fprintf(stderr, "Could not get message!\n"); + } + } + return(0); +} +#endif diff --git a/lib/clplumbing/cl_msg_types.c b/lib/clplumbing/cl_msg_types.c new file mode 100644 index 0000000..56cf56a --- /dev/null +++ b/lib/clplumbing/cl_msg_types.c @@ -0,0 +1,1736 @@ +/* + * Heartbeat message type functions + * + * Copyright (C) 2004 Guochun Shi + * + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MAX +# define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + + +extern const char* FT_strings[]; + + + +#define NL_TO_SYM 0 +#define SYM_TO_NL 1 + +static const int SPECIAL_SYMS[MAXDEPTH] = { + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 15, + 16, + 17, + 18, +}; + +#define SPECIAL_SYM 19 + +struct ha_msg* string2msg_ll(const char*, size_t, int, int); +int compose_netstring(char*, const char*, const char*, size_t, size_t*); +int msg2netstring_buf(const struct ha_msg*, char*, size_t, size_t*); +int struct_display_print_spaces(char *buffer, int depth); +int struct_display_as_xml(int log_level, int depth, struct ha_msg *data, + const char *prefix, gboolean formatted); +int struct_stringlen(size_t namlen, size_t vallen, const void* value); +int struct_netstringlen(size_t namlen, size_t vallen, const void* value); +int convert_nl_sym(char* s, int len, char sym, int direction); +int bytes_for_int(int x); + +int +bytes_for_int(int x) +{ + int len = 0; + if(x < 0) { + x = 0-x; + len=1; + } + while(x > 9) { + x /= 10; + len++; + } + return len+1; +} + +int +netstring_extra(int x) +{ + return (bytes_for_int(x) + x + 2); +} + +int +get_netstringlen(const struct ha_msg *m) +{ + int i; + int total_len =0 ; + + if (m == NULL){ + cl_log(LOG_ERR, "get_netstringlen:" + "asking netstringlen of a NULL message"); + return 0; + } + + total_len = sizeof(MSG_START_NETSTRING) + + sizeof(MSG_END_NETSTRING) -2 ; + + + for (i = 0; i < m->nfields; i++){ + int len; + len = fieldtypefuncs[m->types[i]].netstringlen(m->nlens[i], + m->vlens[i], + m->values[i]); + total_len += netstring_extra(len); + } + + + return total_len; + + +} + + + +int +get_stringlen(const struct ha_msg *m) +{ + int i; + int total_len =0 ; + + if (m == NULL){ + cl_log(LOG_ERR, "get_stringlen:" + "asking stringlen of a NULL message"); + return 0; + } + + total_len = sizeof(MSG_START)+sizeof(MSG_END)-1; + + for (i = 0; i < m->nfields; i++){ + total_len += fieldtypefuncs[m->types[i]].stringlen(m->nlens[i], + m->vlens[i], + m->values[i]); + } + + return total_len; +} + + + +/* + compute the total size of the resulted string + if the string list is to be converted + +*/ +size_t +string_list_pack_length(const GList* _list) +{ + size_t i; + GList* list = UNCONST_CAST_POINTER(GList *, _list); + size_t total_length = 0; + + if (list == NULL){ + cl_log(LOG_WARNING, "string_list_pack_length():" + "list is NULL"); + + return 0; + } + for (i = 0; i < g_list_length(list) ; i++){ + + int len = 0; + char * element = g_list_nth_data(list, i); + if (element == NULL){ + cl_log(LOG_ERR, "string_list_pack_length: " + "%luth element of the string list is NULL" + , (unsigned long)i); + return 0; + } + len = strlen(element); + total_length += bytes_for_int(len) + len + 2; + /* 2 is for ":" and "," */ + } + return total_length ; +} + + + +/* + convert a string list into a single string + the format to convert is similar to netstring: + ":" "," + + for example, a list containing two strings "abc" "defg" + will be converted into + 3:abc,4:defg, + @list: the list to be converted + @buf: the converted string should be put in the @buf + @maxp: max pointer +*/ + + +int +string_list_pack(GList* list, char* buf, char* maxp) +{ + size_t i; + char* p = buf; + + for (i = 0; i < g_list_length(list) ; i++){ + char * element = g_list_nth_data(list, i); + int element_len; + + if (element == NULL){ + cl_log(LOG_ERR, "string_list_pack: " + "%luth element of the string list is NULL" + , (unsigned long)i); + return 0; + } + element_len = strlen(element); + if (p + 2 + element_len + bytes_for_int(element_len)> maxp){ + cl_log(LOG_ERR, "%s: memory out of boundary", + __FUNCTION__); + return 0; + } + p += sprintf(p, "%d:%s,", element_len,element); + + if (p > maxp){ + cl_log(LOG_ERR, "string_list_pack: " + "buffer overflowed "); + return 0; + } + } + + + return (p - buf); +} + + + +/* + this is reverse process of pack_string_list +*/ +GList* +string_list_unpack(const char* packed_str_list, size_t length) +{ + GList* list = NULL; + const char* psl = packed_str_list; + const char * maxp= packed_str_list + length; + int len = 0; + + + while(TRUE){ + char* buf; + + if (*psl == '\0' || psl >= maxp){ + break; + } + + if (sscanf( psl, "%d:", &len) <= 0 ){ + break; + } + + if (len <=0){ + cl_log(LOG_ERR, "unpack_string_list:" + "reading len of string error"); + if (list){ + list_cleanup(list); + } + return NULL; + } + + while (*psl != ':' && *psl != '\0' ){ + psl++; + } + + if (*psl == '\0'){ + break; + } + + psl++; + + buf = malloc(len + 1); + if (buf == NULL){ + cl_log(LOG_ERR, "unpack_string_list:" + "unable to allocate buf"); + if(list){ + list_cleanup(list); + } + return NULL; + + } + memcpy(buf, psl, len); + buf[len] = '\0'; + list = g_list_append(list, buf); + psl +=len; + + if (*psl != ','){ + cl_log(LOG_ERR, "unpack_string_list:" + "wrong format, s=%s",packed_str_list); + } + psl++; + } + + return list; + +} + + +static void +string_memfree(void* value) +{ + if (value){ + free(value); + }else { + cl_log(LOG_ERR, "string_memfree: " + "value is NULL"); + } + + + return; +} + +static void +binary_memfree(void* value) +{ + string_memfree(value); +} + + +static void +struct_memfree( void* value) +{ + struct ha_msg* msg; + + if (!value){ + cl_log(LOG_ERR, + "value is NULL"); + return ; + } + + msg = (struct ha_msg*) value; + ha_msg_del(msg); + return ; +} + +static void +list_memfree(void* value) +{ + + if (!value){ + cl_log(LOG_ERR, + "value is NULL"); + return ; + } + + list_cleanup(value); + +} + + +static void* +binary_dup(const void* value, size_t len) +{ + + char* dupvalue; + + /* 0 byte binary field is allowed*/ + + if (value == NULL && len > 0){ + cl_log(LOG_ERR, "binary_dup:" + "NULL value with non-zero len=%d", + (int)len); + return NULL; + } + + dupvalue = malloc(len + 1); + if (dupvalue == NULL){ + cl_log(LOG_ERR, "binary_dup:" + "malloc failed"); + return NULL; + } + + if (value != NULL) { + memcpy(dupvalue, value, len); + } + + dupvalue[len] =0; + + return dupvalue; +} + +static void* +string_dup(const void* value, size_t len) +{ + return binary_dup(value, len); +} + + +static void* +struct_dup(const void* value, size_t len) +{ + char* dupvalue; + + (void)len; + + if (!value){ + cl_log(LOG_ERR,"struct_dup:" + "value is NULL"); + return NULL ; + } + + + dupvalue = (void*)ha_msg_copy((const struct ha_msg*)value); + if (dupvalue == NULL){ + cl_log(LOG_ERR, "struct_dup: " + "ha_msg_copy failed"); + return NULL; + } + + return dupvalue; +} + +static GList* +list_copy(const GList* _list) +{ + size_t i; + GList* newlist = NULL; + GList* list = UNCONST_CAST_POINTER(GList *, _list); + + for (i = 0; i < g_list_length(list); i++){ + char* dup_element = NULL; + char* element = g_list_nth_data(list, i); + int len; + if (element == NULL){ + cl_log(LOG_WARNING, "list_copy:" + "element is NULL"); + continue; + } + + len = strlen(element); + dup_element= malloc(len + 1); + if ( dup_element == NULL){ + cl_log(LOG_ERR, "duplicate element failed"); + continue; + } + memcpy(dup_element, element,len); + dup_element[len] = 0; + + newlist = g_list_append(newlist, dup_element); + } + + return newlist; +} + +static void* +list_dup( const void* value, size_t len) +{ + char* dupvalue; + + (void)len; + if (!value){ + cl_log(LOG_ERR,"struct_dup:" + "value is NULL"); + return NULL ; + } + + dupvalue = (void*)list_copy((const GList*)value); + + if (!dupvalue){ + cl_log(LOG_ERR, "list_dup: " + "list_copy failed"); + return NULL; + } + + return dupvalue; +} + + +static void +general_display(int log_level, int seq, char* name, void* value, int vlen, int type) +{ + int netslen; + int slen; + HA_MSG_ASSERT(value); + HA_MSG_ASSERT(name); + + slen = fieldtypefuncs[type].stringlen(strlen(name), vlen, value); + netslen = fieldtypefuncs[type].netstringlen(strlen(name), vlen, value); + cl_log(log_level, "MSG[%d] : [(%s)%s=%p(%d %d)]", + seq, FT_strings[type], + name, value, slen, netslen); + +} +static void +string_display(int log_level, int seq, char* name, void* value, int vlen) +{ + HA_MSG_ASSERT(name); + HA_MSG_ASSERT(value); + cl_log(log_level, "MSG[%d] : [%s=%s]", + seq, name, (const char*)value); + return; +} + +static void +binary_display(int log_level, int seq, char* name, void* value, int vlen) +{ + general_display(log_level, seq, name, value, vlen, FT_BINARY); +} + +static void +compress_display(int log_level, int seq, char* name, void* value, int vlen){ + general_display(log_level, seq, name, value, vlen, FT_COMPRESS); +} + + +static void +general_struct_display(int log_level, int seq, char* name, void* value, int vlen, int type) +{ + int slen; + int netslen; + + HA_MSG_ASSERT(name); + HA_MSG_ASSERT(value); + + slen = fieldtypefuncs[type].stringlen(strlen(name), vlen, value); + netslen = fieldtypefuncs[type].netstringlen(strlen(name), vlen, value); + + cl_log(log_level, "MSG[%d] : [(%s)%s=%p(%d %d)]", + seq, FT_strings[type], + name, value, slen, netslen); + if(cl_get_string((struct ha_msg*) value, F_XML_TAGNAME) == NULL) { + cl_log_message(log_level, (struct ha_msg*) value); + } else { + /* use a more friendly output format for nested messages */ + struct_display_as_xml(log_level, 0, value, NULL, TRUE); + } +} +static void +struct_display(int log_level, int seq, char* name, void* value, int vlen) +{ + general_struct_display(log_level, seq, name, value, vlen, FT_STRUCT); + +} +static void +uncompress_display(int log_level, int seq, char* name, void* value, int vlen) +{ + general_struct_display(log_level, seq, name, value, vlen, FT_UNCOMPRESS); +} + +#define update_buffer_head(buffer, len) if(len < 0) { \ + (*buffer) = EOS; return -1; \ + } else { \ + buffer += len; \ + } + +int +struct_display_print_spaces(char *buffer, int depth) +{ + int lpc = 0; + int spaces = 2*depth; + /* <= so that we always print 1 space - prevents problems with syslog */ + for(lpc = 0; lpc <= spaces; lpc++) { + if(sprintf(buffer, "%c", ' ') < 1) { + return -1; + } + buffer += 1; + } + return lpc; +} + +int +struct_display_as_xml( + int log_level, int depth, struct ha_msg *data, + const char *prefix, gboolean formatted) +{ + int lpc = 0; + int printed = 0; + gboolean has_children = FALSE; + char print_buffer[1000]; + char *buffer = print_buffer; + const char *name = cl_get_string(data, F_XML_TAGNAME); + + if(data == NULL) { + return 0; + + } else if(name == NULL) { + cl_log(LOG_WARNING, "Struct at depth %d had no name", depth); + cl_log_message(log_level, data); + return 0; + } + + if(formatted) { + printed = struct_display_print_spaces(buffer, depth); + update_buffer_head(buffer, printed); + } + + printed = sprintf(buffer, "<%s", name); + update_buffer_head(buffer, printed); + + for (lpc = 0; lpc < data->nfields; lpc++) { + const char *prop_name = data->names[lpc]; + const char *prop_value = data->values[lpc]; + if(data->types[lpc] != FT_STRING) { + continue; + } else if(prop_name == NULL) { + continue; + } else if(prop_name[0] == '_' && prop_name[1] == '_') { + continue; + } + printed = sprintf(buffer, " %s=\"%s\"", prop_name, prop_value); + update_buffer_head(buffer, printed); + } + + for (lpc = 0; lpc < data->nfields; lpc++) { + if(data->types[lpc] == FT_STRUCT) { + has_children = TRUE; + break; + } + } + + printed = sprintf(buffer, "%s>", has_children==0?"/":""); + update_buffer_head(buffer, printed); + cl_log(log_level, "%s%s", prefix?prefix:"", print_buffer); + buffer = print_buffer; + + if(has_children == FALSE) { + return 0; + } + + for (lpc = 0; lpc < data->nfields; lpc++) { + if(data->types[lpc] != FT_STRUCT) { + continue; + } else if(0 > struct_display_as_xml( + log_level, depth+1, data->values[lpc], + prefix, formatted)) { + return -1; + } + } + + if(formatted) { + printed = struct_display_print_spaces(buffer, depth); + update_buffer_head(buffer, printed); + } + cl_log(log_level, "%s%s", prefix?prefix:"", print_buffer, name); + + return 0; +} + + + + +static int +liststring(GList* list, char* buf, int maxlen) +{ + char* p = buf; + char* maxp = buf + maxlen; + size_t i; + + for ( i = 0; i < g_list_length(list); i++){ + char* element = g_list_nth_data(list, i); + if (element == NULL) { + cl_log(LOG_ERR, "%luth element is NULL " + , (unsigned long)i); + return HA_FAIL; + } else{ + if (i == 0){ + p += sprintf(p,"%s",element); + }else{ + p += sprintf(p," %s",element); + } + + } + if ( p > maxp){ + cl_log(LOG_ERR, "buffer overflow"); + return HA_FAIL; + } + + } + + return HA_OK; +} + +static void +list_display(int log_level, int seq, char* name, void* value, int vlen) +{ + GList* list; + char buf[MAXLENGTH]; + + HA_MSG_ASSERT(name); + HA_MSG_ASSERT(value); + + list = value; + + if (liststring(list, buf, MAXLENGTH) != HA_OK){ + cl_log(LOG_ERR, "liststring error"); + return; + } + cl_log(log_level, "MSG[%d] :[(%s)%s=%s]", + seq, FT_strings[FT_LIST], + name, buf); + + return ; + +} + + +/* + * This function changes each new line in the input string + * into a special symbol, or the other way around + */ + +int +convert_nl_sym(char* s, int len, char sym, int direction) +{ + int i; + + if (direction != NL_TO_SYM && direction != SYM_TO_NL){ + cl_log(LOG_ERR, "convert_nl_sym(): direction not defined!"); + return(HA_FAIL); + } + + + for (i = 0; i < len && s[i] != EOS; i++){ + + switch(direction){ + case NL_TO_SYM : + if (s[i] == '\n'){ + s[i] = sym; + break; + } + + if (s[i] == sym){ + cl_log(LOG_ERR + , "convert_nl_sym(): special symbol \'0x%x\' (%c) found" + " in string at %d (len=%d)", s[i], s[i], i, len); + i -= 10; + if(i < 0) { + i = 0; + } + cl_log(LOG_ERR, "convert_nl_sym(): %s", s + i); + return(HA_FAIL); + } + + break; + + case SYM_TO_NL: + + if (s[i] == sym){ + s[i] = '\n'; + break; + } + break; + default: + /* nothing, never executed*/; + + } + } + + return(HA_OK); +} + + +/* + * This function changes each new line in the input string + * into a special symbol, or the other way around + */ + +static int +convert(char* s, int len, int depth, int direction) +{ + + if (direction != NL_TO_SYM && direction != SYM_TO_NL){ + cl_log(LOG_ERR, "convert(): direction not defined!"); + return(HA_FAIL); + } + + + if (depth >= MAXDEPTH ){ + cl_log(LOG_ERR, "convert(): MAXDEPTH exceeded: %d", depth); + return(HA_FAIL); + } + + return convert_nl_sym(s, len, SPECIAL_SYMS[depth], direction); +} + + + + +static int +string_stringlen(size_t namlen, size_t vallen, const void* value) +{ + + HA_MSG_ASSERT(value); +/* HA_MSG_ASSERT( vallen == strlen(value)); */ + return namlen + vallen + 2; +} + +static int +binary_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + int length; + + HA_MSG_ASSERT(value); + + length = 3 + namlen + 1 + vallen; + + return length; +} + + +static int +string_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + HA_MSG_ASSERT(value); + HA_MSG_ASSERT( vallen == strlen(value)); + + return binary_netstringlen(namlen, vallen, value); +} + + +static int +binary_stringlen(size_t namlen, size_t vallen, const void* value) +{ + HA_MSG_ASSERT(value); + + return namlen + B64_stringlen(vallen) + 2 + 3; + /*overhead 3 is for type*/ +} + + + + + +int +struct_stringlen(size_t namlen, size_t vallen, const void* value) +{ + const struct ha_msg* childmsg; + + HA_MSG_ASSERT(value); + + (void)vallen; + childmsg = (const struct ha_msg*)value; + + return namlen +2 + 3 + get_stringlen(childmsg); + /*overhead 3 is for type*/ +} + +int +struct_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + + int ret; + const struct ha_msg* childmsg; + int len; + + HA_MSG_ASSERT(value); + + (void)vallen; + childmsg = (const struct ha_msg*)value; + + len = get_netstringlen(childmsg); + + ret = 3 + namlen + 1 + len; + + return ret; + +} + + +static int +list_stringlen(size_t namlen, size_t vallen, const void* value) +{ + (void)value; + return namlen + vallen + 2 + 3; + /*overhead 3 is for type (FT_STRUCT)*/ +} + +static int +list_netstringlen(size_t namlen, size_t vallen, const void* value) +{ + int ret; + const GList* list; + + list = (const GList*)value; + + ret = 3 + namlen + 1 + string_list_pack_length(list); + + return ret; + +} + +static int +add_binary_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_binary_field:" + " invalid input argument"); + return HA_FAIL; + } + + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_BINARY; + msg->nfields++; + + return HA_OK; +} + + +static int +add_struct_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_struct_field:" + " invalid input argument"); + return HA_FAIL; + } + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_STRUCT; + + msg->nfields++; + + return HA_OK; +} + + + + +static int +add_list_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + int next; + int j; + GList* list = NULL; + + if ( !msg || !name || !value + || namelen <= 0 + || vallen <= 0 + || depth < 0){ + cl_log(LOG_ERR, "add_list_field:" + " invalid input argument"); + return HA_FAIL; + } + + + for (j=0; j < msg->nfields; ++j) { + if (strcmp(name, msg->names[j]) == 0) { + break; + } + } + + if ( j >= msg->nfields){ + list = (GList*)value; + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_LIST; + msg->nfields++; + + } else if( msg->types[j] == FT_LIST ){ + + GList* oldlist = (GList*) msg->values[j]; + int listlen; + size_t i; + + for ( i =0; i < g_list_length((GList*)value); i++){ + list = g_list_append(oldlist, g_list_nth_data((GList*)value, i)); + } + if (list == NULL){ + cl_log(LOG_ERR, "add_list_field:" + " g_list_append() failed"); + return HA_FAIL; + } + + listlen = string_list_pack_length(list); + + msg->values[j] = list; + msg->vlens[j] = listlen; + g_list_free((GList*)value); /*we don't free each element + because they are used in new list*/ + free(name); /* this name is no longer necessary + because msg->names[j] is reused */ + + } else { + cl_log(LOG_ERR, "field already exists " + "with differnt type=%d", msg->types[j]); + return (HA_FAIL); + } + + return HA_OK; +} + + +static int +add_compress_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_binary_field:" + " invalid input argument"); + return HA_FAIL; + } + + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_COMPRESS; + msg->nfields++; + + return HA_OK; +} + + + + +static int +add_uncompress_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + int next; + + if ( !msg || !name || !value + || depth < 0){ + cl_log(LOG_ERR, "add_struct_field:" + " invalid input argument"); + return HA_FAIL; + } + + next = msg->nfields; + msg->names[next] = name; + msg->nlens[next] = namelen; + msg->values[next] = value; + msg->vlens[next] = vallen; + msg->types[next] = FT_UNCOMPRESS; + + msg->nfields++; + + return HA_OK; +} + + + +/*print a string to a string, + pretty simple one :) +*/ +static int +str2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + char* s = value; + char* p = buf; + (void)maxp; + (void)depth; + + if (buf + len > maxp){ + cl_log(LOG_ERR, "%s: out of boundary", + __FUNCTION__); + return -1; + } + + if ( strlen(s) != len){ + cl_log(LOG_ERR, "str2string:" + "the input len != string length"); + return -1; + } + + strcat(buf, s); + while(*p != '\0'){ + if (*p == '\n'){ + *p = SPECIAL_SYM; + } + p++; + } + + return len; + +} + +/*print a binary value to a string using base64 + library +*/ + +static int +binary2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + int baselen; + int truelen = 0; + + (void)depth; + baselen = B64_stringlen(len) + 1; + + if ( buf + baselen > maxp){ + cl_log(LOG_ERR, "binary2string: out of bounary"); + return -1; + } + + truelen = binary_to_base64(value, len, buf, baselen); + + return truelen; +} + +/*print a struct(ha_msg) to a string + @depth denotes the number of recursion +*/ + +static int +struct2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + + struct ha_msg* msg = value; + int baselen = get_stringlen(msg); + + (void)len; + + if ( buf + baselen > maxp){ + cl_log(LOG_ERR, "struct2string: not enough buffer" + "for the struct to generate a string"); + return -1; + } + + if (msg2string_buf(msg, buf ,baselen,depth + 1, NEEDHEAD) + != HA_OK){ + + cl_log(LOG_ERR + , "struct2string(): msg2string_buf for" + " child message failed"); + return -1; + + } + + if (convert(buf, baselen, depth, NL_TO_SYM) != HA_OK){ + cl_log(LOG_ERR , "struct2string(): convert failed"); + return -1; + } + + return strlen(buf); +} + + + + +/* print a list to a string + */ + +static int +list2string(char* buf, char* maxp, void* value, size_t len, int depth) +{ + int listlen; + GList* list = (GList*) value; + + (void)len; + (void)depth; + listlen = string_list_pack(list , buf, maxp); + if (listlen == 0){ + cl_log(LOG_ERR, "list2string():" + "string_list_pack() failed"); + return -1; + } + + return listlen; + +} + + +static int +string2str(void* value, size_t len, int depth, void** nv, size_t* nlen ) +{ + if (!value || !nv || !nlen || depth < 0){ + cl_log(LOG_ERR, "string2str:invalid input"); + return HA_FAIL; + } + + if (convert_nl_sym(value, len, SPECIAL_SYM, SYM_TO_NL) != HA_OK){ + cl_log(LOG_ERR, "string2str:convert_nl_sym" + "from symbol to new line failed"); + return HA_FAIL; + } + *nv = value; + *nlen = len; + + return HA_OK; +} + +static int +string2binary(void* value, size_t len, int depth, void** nv, size_t* nlen) +{ + char tmpbuf[MAXLINE]; + char* buf = NULL; + int buf_malloced = 0; + int ret = HA_FAIL; + if (len > MAXLINE){ + buf = malloc(len); + if (buf == NULL){ + cl_log(LOG_ERR, "%s: malloc failed", + __FUNCTION__); + goto out; + } + buf_malloced = 1; + }else { + buf = &tmpbuf[0]; + } + + if (value == NULL && len == 0){ + *nv = NULL; + *nlen = 0; + ret = HA_OK; + goto out; + } + + if ( !value || !nv || depth < 0){ + cl_log(LOG_ERR, "string2binary:invalid input"); + ret = HA_FAIL; + goto out; + } + + memcpy(buf, value, len); + *nlen = base64_to_binary(buf, len, value, len); + + *nv = value; + ret = HA_OK; + out: + if (buf_malloced && buf){ + free(buf); + } + return ret; +} + +static int +string2struct(void* value, size_t vallen, int depth, void** nv, size_t* nlen) +{ + + struct ha_msg *tmpmsg; + + if (!value || !nv || depth < 0){ + cl_log(LOG_ERR, "string2struct:invalid input"); + return HA_FAIL; + } + + + if (convert(value, vallen, depth,SYM_TO_NL) != HA_OK){ + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): convert failed"); + return(HA_FAIL); + } + + tmpmsg = string2msg_ll(value, vallen,depth + 1, 0); + if (tmpmsg == NULL){ + cl_log(LOG_ERR + , "string2struct()" + ": string2msg_ll failed"); + return(HA_FAIL); + } + free(value); + *nv = tmpmsg; + *nlen = 0; + + return HA_OK; + +} + +static int +string2list(void* value, size_t vallen, int depth, void** nv, size_t* nlen) +{ + GList* list; + + if (!value || !nv || !nlen || depth < 0){ + cl_log(LOG_ERR, "string2struct:invalid input"); + return HA_FAIL; + } + + list = string_list_unpack(value, vallen); + if (list == NULL){ + cl_log(LOG_ERR, "ha_msg_addraw_ll():" + "unpack_string_list failed: %s", (char*)value); + return(HA_FAIL); + } + free(value); + + *nv = (void*)list; + *nlen = string_list_pack_length(list); + + return HA_OK; + +} + +static int +fields2netstring(char* sp, char* smax, char* name, size_t nlen, + void* value, size_t vallen, int type, size_t* comlen) +{ + size_t fieldlen; + size_t slen; + int ret = HA_OK; + char* sp_save = sp; + char* tmpsp; + + fieldlen = fieldtypefuncs[type].netstringlen(nlen, vallen, value); + /* this check seems to be superfluous because of the next one + if (fieldlen > MAXMSG){ + cl_log(LOG_INFO, "%s: field too big(%d)", __FUNCTION__, (int)fieldlen); + return HA_FAIL; + } + */ + tmpsp = sp + netstring_extra(fieldlen); + if (tmpsp > smax){ + cl_log(LOG_ERR, "%s: memory out of boundary, tmpsp=%p, smax=%p", + __FUNCTION__, tmpsp, smax); + return HA_FAIL; + } + sp += sprintf(sp , "%d:(%d)%s=", (int)fieldlen, type, name); + switch (type){ + + case FT_STRING: + case FT_BINARY: + case FT_COMPRESS: + memcpy(sp, value, vallen); + slen = vallen; + break; + + case FT_UNCOMPRESS: + case FT_STRUCT: + { + struct ha_msg* msg = (struct ha_msg*) value; + /* infinite recursion? Must say that I got lost at + * this point + */ + ret = msg2netstring_buf(msg, sp,get_netstringlen(msg), + &slen); + break; + } + case FT_LIST: + { + + char buf[MAXLENGTH]; + GList* list = NULL; + int tmplen; + + list = (GList*) value; + + tmplen = string_list_pack_length(list); + if (tmplen >= MAXLENGTH){ + cl_log(LOG_ERR, + "string list length exceeds limit"); + return(HA_FAIL); + } + + if (string_list_pack(list, buf, buf + MAXLENGTH) + != tmplen ){ + cl_log(LOG_ERR, + "packing string list return wrong length"); + return(HA_FAIL); + } + + + memcpy(sp, buf, tmplen); + slen = tmplen; + ret = HA_OK; + break; + } + + default: + ret = HA_FAIL; + cl_log(LOG_ERR, "%s: Wrong type (%d)", __FUNCTION__,type); + } + + if (ret == HA_FAIL){ + return ret; + } + + sp +=slen; + *sp++ = ','; + *comlen = sp - sp_save; + + return HA_OK; + + +} + + +static int +netstring2string(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + char* dupvalue; + + if (value == NULL && vlen == 0){ + *retvalue = NULL; + *ret_vlen = 0; + return HA_OK; + } + + if ( !value || !retvalue || !ret_vlen){ + cl_log(LOG_ERR, " netstring2string:" + "invalid input arguments"); + return HA_FAIL; + } + + dupvalue = binary_dup(value, vlen); + if (!dupvalue){ + cl_log(LOG_ERR, "netstring2string:" + "duplicating value failed"); + return HA_FAIL; + } + + *retvalue = dupvalue; + *ret_vlen = vlen; + + return HA_OK; +} + +static int +netstring2binary(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + return netstring2string(value, vlen, retvalue, ret_vlen); + +} + +static int +netstring2struct(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + struct ha_msg* msg; + + if ( !value || !retvalue || !ret_vlen){ + cl_log(LOG_ERR, " netstring2struct:" + "invalid input arguments"); + return HA_FAIL; + } + + msg = netstring2msg(value, vlen, 0); + if (!msg){ + cl_log(LOG_ERR, "netstring2struct:" + "netstring2msg failed"); + return HA_FAIL; + } + + *retvalue =(void* ) msg; + *ret_vlen = 0; + + return HA_OK; + +} + +static int +netstring2list(const void* value, size_t vlen, void** retvalue, size_t* ret_vlen) +{ + GList* list; + + if ( !value || !retvalue || !ret_vlen){ + cl_log(LOG_ERR, " netstring2struct:" + "invalid input arguments"); + return HA_FAIL; + } + + + list = string_list_unpack(value, vlen); + if (list == NULL){ + cl_log(LOG_ERR, "netstring2list: unpacking string list failed"); + cl_log(LOG_INFO, "thisbuf=%s", (const char*)value); + return HA_FAIL; + } + *retvalue = (void*)list; + + *ret_vlen = string_list_pack_length(list); + + return HA_OK; + +} + + + + + +static int +add_string_field(struct ha_msg* msg, char* name, size_t namelen, + void* value, size_t vallen, int depth) +{ + + size_t internal_type; + unsigned long tmptype; + char *cp_name = NULL; + size_t cp_namelen; + size_t cp_vallen; + void *cp_value = NULL; + int next; + + if ( !msg || !name || !value + || namelen <= 0 + || depth < 0){ + cl_log(LOG_ERR, "add_string_field:" + " invalid input argument"); + return HA_FAIL; + } + + + + internal_type = FT_STRING; + if (name[0] == '('){ + + int nlo = 3; /*name length overhead */ + if (name[2] != ')'){ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): no closing parentheses"); + } + return(HA_FAIL); + } + tmptype = name[1] - '0'; + if (tmptype < 0 || tmptype > 9) { + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): not a number."); + return(HA_FAIL); + } + + internal_type = tmptype; + + if (internal_type == FT_STRING){ + cl_log(LOG_ERR + , "ha_msg_addraw_ll(): wrong type"); + return(HA_FAIL); + } + + cp_name = name; + cp_namelen = namelen - nlo ; + memmove(cp_name, name + nlo, namelen - nlo); + cp_name[namelen - nlo] = EOS; + }else { + cp_name = name; + cp_namelen = namelen; + + } + + if(internal_type < DIMOF(fieldtypefuncs)){ + int (*stringtofield)(void*, size_t, int depth, void**, size_t* ); + int (*fieldstringlen)( size_t, size_t, const void*); + + stringtofield= fieldtypefuncs[internal_type].stringtofield; + + if (!stringtofield || stringtofield(value, vallen, depth, &cp_value, &cp_vallen) != HA_OK){ + cl_log(LOG_ERR, "add_string_field: stringtofield failed"); + return HA_FAIL; + } + + fieldstringlen = fieldtypefuncs[internal_type].stringlen; + if (!fieldstringlen || + fieldstringlen(cp_namelen, cp_vallen, cp_value) <= 0 ){ + + cl_log(LOG_ERR, "add_string_field: stringlen failed"); + return HA_FAIL; + } + + } else { + cl_log(LOG_ERR, "add_string_field():" + " wrong type %lu", (unsigned long)internal_type); + return HA_FAIL; + } + + + next = msg->nfields; + msg->values[next] = cp_value; + msg->vlens[next] = cp_vallen; + msg->names[next] = cp_name; + msg->nlens[next] = cp_namelen; + msg->types[next] = internal_type; + msg->nfields++; + + return HA_OK; + +} + +static int +uncompress2compress(struct ha_msg* msg, int index) +{ + char* buf; + size_t buflen = MAXMSG; + int rc = HA_FAIL; + + buf = malloc(buflen); + if (!buf) { + cl_log(LOG_ERR, "%s: failed to allocate buffer", + __FUNCTION__); + goto err; + } + + if (msg->types[index] != FT_UNCOMPRESS){ + cl_log(LOG_ERR, "%s: the %dth field is not FT_UNCOMPRESS type", + __FUNCTION__, index); + goto err; + } + + + if (cl_compress_field(msg, index, buf, &buflen) != HA_OK){ + cl_log(LOG_ERR, "%s: compressing %dth field failed", __FUNCTION__, index); + goto err; + } + + rc = cl_msg_replace(msg, index, buf, buflen, FT_COMPRESS); + +err: + if (buf) { + free(buf); + } + + return rc; +} + +static int +compress2uncompress(struct ha_msg* msg, int index) +{ + char *buf = NULL; + size_t buflen = MAXUNCOMPRESSED; + struct ha_msg* msgfield; + int err = HA_FAIL; + + buf = malloc(buflen); + + if (!buf) { + cl_log(LOG_ERR, "%s: allocating buffer for uncompression failed", + __FUNCTION__); + goto out; + } + + if (cl_decompress_field(msg, index, buf, &buflen) != HA_OK){ + cl_log(LOG_ERR, "%s: compress field failed", + __FUNCTION__); + goto out; + } + + msgfield = wirefmt2msg(buf, buflen, 0); + if (msgfield == NULL){ + cl_log(LOG_ERR, "%s: wirefmt to msg failed", + __FUNCTION__); + goto out; + } + + err = cl_msg_replace(msg, index, (char*)msgfield, 0, FT_UNCOMPRESS); + + ha_msg_del(msgfield); + +out: + if (buf) { + free(buf); + } + + return err; +} + +/* + * string FT_STRING + * string is the basic type used in heartbeat, it is used for printable ascii value + * + * binary FT_BINARY + * binary means the value can be any binary value, including non-printable ascii value + * + * struct FT_STRUCT + * struct means the value is also an ha_msg (actually it is a pointer to an ha message) + * + * list FT_LIST + * LIST means the value is a GList. Right now we only suppport a Glist of strings + * + * compress FT_COMPRESS + * This field and the next one(FT_UNCOMPRESS) is designed to optimize compression in message + * (see cl_compression.c for more about compression). This field is similar to the binary field. + * It stores a compressed field, which will be an ha_msg if uncompressed. Most of time this field + * act like a binary field until compress2uncompress() is called. That function will be called + * when someone calls cl_get_struct() to get this field value. After that this field is converted + * to a new type FT_UNCOMPRESS + * + * uncompress FT_UNCOMPRESS + * As said above, this field is used to optimize compression. This field is similar to the struct + * field. It's value is a pointer to an ha_msg. This field will be converted to a new type FT_COMPRESS + * when msg2wirefmt() is called, where uncompress2compress is called to do the field compression + */ + +struct fieldtypefuncs_s fieldtypefuncs[NUM_MSG_TYPES]= + { {string_memfree, string_dup, string_display, add_string_field, + string_stringlen,string_netstringlen, str2string,fields2netstring, + string2str, netstring2string, NULL, NULL}, + + {binary_memfree, binary_dup, binary_display, add_binary_field, + binary_stringlen,binary_netstringlen, binary2string,fields2netstring, + string2binary, netstring2binary, NULL, NULL}, + + {struct_memfree, struct_dup, struct_display, add_struct_field, + struct_stringlen, struct_netstringlen, struct2string, fields2netstring, \ + string2struct, netstring2struct, NULL, NULL}, + + {list_memfree, list_dup, list_display, add_list_field, + list_stringlen, list_netstringlen, list2string, fields2netstring, + string2list, netstring2list, NULL, NULL}, + + {binary_memfree, binary_dup, compress_display, add_compress_field, + binary_stringlen,binary_netstringlen, binary2string ,fields2netstring, + string2binary , netstring2binary, NULL, compress2uncompress}, /*FT_COMPRESS*/ + + {struct_memfree, struct_dup, uncompress_display, add_uncompress_field, + struct_stringlen, struct_netstringlen, NULL , fields2netstring, + NULL , netstring2struct, uncompress2compress, NULL}, /*FT_UNCOMPRSS*/ + }; + + diff --git a/lib/clplumbing/cl_netstring.c b/lib/clplumbing/cl_netstring.c new file mode 100644 index 0000000..f4040e0 --- /dev/null +++ b/lib/clplumbing/cl_netstring.c @@ -0,0 +1,570 @@ +/* + * netstring implementation + * + * Copyright (c) 2003 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Avoid sprintf. Use snprintf instead, even if you count your bytes. + * It can detect calculation errors (if used properly) + * and will not make the security audit tools crazy. + */ + +#define MAX_AUTH_BYTES 64 + + +int msg2netstring_buf(const struct ha_msg*, char*, size_t, size_t*); +int compose_netstring(char*, const char*, const char*, size_t, size_t*); +int is_auth_netstring(const char*, size_t, const char*, size_t); +char* msg2netstring(const struct ha_msg*, size_t*); +int process_netstring_nvpair(struct ha_msg* m, const char* nvpair, int nvlen); +extern int bytes_for_int(int x); +extern const char * FT_strings[]; + +static int (*authmethod)(int whichauth +, const void * data +, size_t datalen +, char * authstr +, size_t authlen) = NULL; + +void +cl_set_authentication_computation_method(int (*method)(int whichauth +, const void * data +, size_t datalen +, char * authstr +, size_t authlen)) +{ + authmethod = method; +} + +int cl_parse_int(const char *sp, const char *smax, int* len); + +int +cl_parse_int(const char *sp, const char *smax, int* len) +{ + char ch = 0; + int offset = 0; + *len = 0; + + errno = 0; + for( ; sp+offset < smax; offset++) { + ch = sp[offset] - '0'; + if(ch > 9) { /* ch >= 0 is implied by the data type*/ + break; + } + *len *= 10; + *len += ch; + } + + if(offset == 0) { + cl_log(LOG_ERR, + "cl_parse_int: Couldn't parse an int from: %.5s", sp); + } + return offset; +} + +int +compose_netstring(char * s, const char * smax, const char* data, + size_t len, size_t* comlen) +{ + + char * sp = s; + + /* 2 == ":" + "," */ + if (s + len + 2 + bytes_for_int(len) > smax) { + cl_log(LOG_ERR, + "netstring pointer out of boundary(compose_netstring)"); + return(HA_FAIL); + } + + sp += sprintf(sp, "%ld:", (long)len); + + if(data){ + memcpy(sp, data, len); + } + sp += len; + *sp++ = ','; + + *comlen = sp - s; + + return(HA_OK); +} + + + +/* Converts a message into a netstring */ + +int +msg2netstring_buf(const struct ha_msg *m, char *s, + size_t buflen, size_t * slen) +{ + int i; + char * sp; + char * smax; + int ret = HA_OK; + + sp = s; + smax = s + buflen; + + strcpy(sp, MSG_START_NETSTRING); + + sp += strlen(MSG_START_NETSTRING); + + for (i=0; i < m->nfields; i++) { + size_t flen; + int tmplen; + + /* some of these functions in its turn invoke us again */ + ret = fieldtypefuncs[m->types[i]].tonetstring(sp, + smax, + m->names[i], + m->nlens[i], + m->values[i], + m->vlens[i], + m->types[i], + &flen); + + if (ret != HA_OK){ + cl_log(LOG_ERR, "encoding msg to netstring failed"); + cl_log_message(LOG_ERR, m); + return ret; + } + + tmplen = netstring_extra(fieldtypefuncs[m->types[i]].netstringlen(m->nlens[i], + m->vlens[i], + m->values[i])); + + if (flen != tmplen ){ + cl_log(LOG_ERR,"netstring len discrepency: actual usage is %d bytes" + "it should use %d", (int)flen, tmplen); + } + sp +=flen; + + } + + if (sp + strlen(MSG_END_NETSTRING) > smax){ + cl_log(LOG_ERR, "%s: out of boundary for MSG_END_NETSTRING", + __FUNCTION__); + return HA_FAIL; + } + strcpy(sp, MSG_END_NETSTRING); + sp += sizeof(MSG_END_NETSTRING) -1; + + if (sp > smax){ + cl_log(LOG_ERR, + "msg2netstring: exceed memory boundary sp =%p smax=%p", + sp, smax); + return(HA_FAIL); + } + + *slen = sp - s; + return(HA_OK); +} + + +int get_netstringlen_auth(const struct ha_msg* m); + +int get_netstringlen_auth(const struct ha_msg* m) +{ + int len = get_netstringlen(m) + MAX_AUTH_BYTES; + return len; +} + + + +static char * +msg2netstring_ll(const struct ha_msg *m, size_t * slen, int need_auth) +{ + int len; + char* s; + int authnum; + char authtoken[MAXLINE]; + char authstring[MAXLINE]; + char* sp; + size_t payload_len; + char* smax; + + len= get_netstringlen_auth(m) + 1; + + /* use MAXUNCOMPRESSED for the in memory size check */ + if (len >= MAXUNCOMPRESSED){ + cl_log(LOG_ERR, "%s: msg is too large; len=%d," + " MAX msg allowed=%d", __FUNCTION__, len, MAXUNCOMPRESSED); + return NULL; + } + + s = calloc(1, len); + if (!s){ + cl_log(LOG_ERR, "%s: no memory for netstring", __FUNCTION__); + return(NULL); + } + + smax = s + len; + + if (msg2netstring_buf(m, s, len, &payload_len) != HA_OK){ + cl_log(LOG_ERR, "%s: msg2netstring_buf() failed", __FUNCTION__); + free(s); + return(NULL); + } + + sp = s + payload_len; + + if ( need_auth && authmethod){ + int auth_strlen; + + authnum = authmethod(-1, s, payload_len, authtoken,sizeof(authtoken)); + if (authnum < 0){ + cl_log(LOG_WARNING + , "Cannot compute message authentication!"); + free(s); + return(NULL); + } + + sprintf(authstring, "%d %s", authnum, authtoken); + auth_strlen = strlen(authstring); + if (sp + 2 + auth_strlen + bytes_for_int(auth_strlen) >= smax){ + cl_log(LOG_ERR, "%s: out of boundary for auth", __FUNCTION__); + free(s); + return NULL; + } + sp += sprintf(sp, "%ld:%s,", (long)strlen(authstring), authstring); + + } + *slen = sp - s; + + return(s); +} + +char * +msg2netstring(const struct ha_msg *m, size_t * slen) +{ + char* ret; + ret = msg2netstring_ll(m, slen, TRUE); + + return ret; + +} +char * +msg2netstring_noauth(const struct ha_msg *m, size_t * slen) +{ + char * ret; + + ret = msg2netstring_ll(m, slen, FALSE); + + return ret; +} + + +/* + * Peel one string off in a netstring + */ + +static int +peel_netstring(const char * s, const char * smax, int* len, + const char ** data, int* parselen ) +{ + int offset = 0; + const char * sp = s; + + if (sp >= smax){ + return(HA_FAIL); + } + + offset = cl_parse_int(sp, smax, len); + if (*len < 0 || offset <= 0){ + cl_log(LOG_ERR, "peel_netstring: Couldn't parse an int starting at: %.5s", sp); + return(HA_FAIL); + } + + sp = sp+offset; + while (*sp != ':' && sp < smax) { + sp ++; + } + + if (sp >= smax) { + return(HA_FAIL); + } + + sp ++; + + *data = sp; + + sp += (*len); + if (sp >= smax) { + return(HA_FAIL); + } + if (*sp != ','){ + return(HA_FAIL); + } + sp++; + + *parselen = sp - s; + + return(HA_OK); +} + + +int +process_netstring_nvpair(struct ha_msg* m, const char* nvpair, int nvlen) +{ + + const char *name; + int nlen; + const char *ns_value; + int ns_vlen; + void *value; + size_t vlen; + int type; + void (*memfree)(void*); + int ret = HA_OK; + + assert(*nvpair == '('); + nvpair++; + + type = nvpair[0] - '0'; + nvpair++; + + /* if this condition is no longer true, change the above to: + * nvpair += cl_parse_int(nvpair, nvpair+nvlen, &type) + */ + assert(type >= 0 && type < 10); + + assert(*nvpair == ')'); + nvpair++; + + if ((nlen = strcspn(nvpair, EQUAL)) <= 0 + || nvpair[nlen] != '=') { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "%s: line doesn't contain '='", __FUNCTION__); + cl_log(LOG_INFO, "%s", nvpair); + } + return(HA_FAIL); + } + + name = nvpair; + ns_value = name +nlen + 1; + ns_vlen = nvpair + nvlen - ns_value -3 ; + if (fieldtypefuncs[type].netstringtofield(ns_value,ns_vlen, &value, &vlen) != HA_OK){ + cl_log(LOG_ERR, "netstringtofield failed in %s", __FUNCTION__); + return HA_FAIL; + + } + + memfree = fieldtypefuncs[type].memfree; + + if (ha_msg_nadd_type(m , name, nlen, value, vlen,type) + != HA_OK) { + cl_log(LOG_ERR, "ha_msg_nadd fails(netstring2msg_rec)"); + ret = HA_FAIL; + } + + + if (memfree && value){ + memfree(value); + } else{ + cl_log(LOG_ERR, "netstring2msg_rec:" + "memfree or ret_value is NULL"); + ret= HA_FAIL; + } + + return ret; + + +} + + +/* Converts a netstring into a message*/ +static struct ha_msg * +netstring2msg_rec(const char *s, size_t length, int* slen) +{ + struct ha_msg* ret = NULL; + const char * sp = s; + const char * smax = s + length; + int startlen; + int endlen; + + if ((ret = ha_msg_new(0)) == NULL){ + return(NULL); + } + + startlen = sizeof(MSG_START_NETSTRING)-1; + + if (strncmp(sp, MSG_START_NETSTRING, startlen) != 0) { + /* This can happen if the sender gets killed */ + /* at just the wrong time... */ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING, "netstring2msg_rec: no MSG_START"); + ha_msg_del(ret); + } + return(NULL); + }else{ + sp += startlen; + } + + endlen = sizeof(MSG_END_NETSTRING) - 1; + + while (sp < smax && strncmp(sp, MSG_END_NETSTRING, endlen) !=0 ){ + + const char *nvpair; + int nvlen; + int parselen; + + if (peel_netstring(sp , smax, &nvlen, &nvpair,&parselen) != HA_OK){ + cl_log(LOG_ERR + , "%s:peel_netstring fails for name/value pair", __FUNCTION__); + cl_log(LOG_ERR, "sp=%s", sp); + ha_msg_del(ret); + return(NULL); + } + sp += parselen; + + if (process_netstring_nvpair(ret, nvpair, nvlen) != HA_OK){ + cl_log(LOG_ERR, "%s: processing nvpair failed", __FUNCTION__); + return HA_FAIL; + } + + } + + + sp += sizeof(MSG_END_NETSTRING) -1; + *slen = sp - s; + return(ret); + +} + + +struct ha_msg * +netstring2msg(const char* s, size_t length, int needauth) +{ + const char *sp; + struct ha_msg *msg; + const char *smax = s + length; + int parselen; + int authlen; + const char *authstring; + /*actual string length used excluding auth string*/ + int slen = 0; /* assign to keep compiler happy */ + + msg = netstring2msg_rec(s, length, &slen); + + if (needauth == FALSE || !authmethod){ + goto out; + } + + sp = s + slen; + + if (peel_netstring(sp , smax, &authlen, &authstring, &parselen) !=HA_OK){ + cl_log(LOG_ERR, + "peel_netstring() error in getting auth string"); + cl_log(LOG_ERR, "sp=%s", sp); + cl_log(LOG_ERR, "s=%s", s); + ha_msg_del(msg); + return(NULL); + } + + if (sp + parselen > smax){ + cl_log(LOG_ERR, " netstring2msg: smax passed"); + ha_msg_del(msg); + return NULL; + } + + if (!is_auth_netstring(s, slen, authstring,authlen) ){ + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR + , "netstring authentication" + " failed, s=%s, autotoken=%s" + , s, authstring); + cl_log_message(LOG_ERR, msg); + } + ha_msg_del(msg); + return(NULL); + } + + out: + return msg; +} + + + + +int +is_auth_netstring(const char * datap, size_t datalen, + const char * authstring, size_t authlen) +{ + + char authstr[MAXLINE]; /* A copy of authstring */ + int authwhich; + char authtoken[MAXLINE]; + + + /* + * If we don't have any authentication method - everything is authentic... + */ + if (!authmethod) { + return TRUE; + } + strncpy(authstr, authstring, MAXLINE); + authstr[authlen] = 0; + if (sscanf(authstr, "%d %s", &authwhich, authtoken) != 2) { + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "Bad/invalid netstring auth string"); + } + return(0); + } + + memset(authstr, 0, MAXLINE); + if (authmethod(authwhich, datap, datalen, authstr, MAXLINE) + != authwhich) { + + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_WARNING + , "Invalid authentication [%d] in message!" + , authwhich); + } + return(FALSE); + } + + if (strcmp(authtoken, authstr) == 0) { + return(TRUE); + } + + if (!cl_msg_quiet_fmterr) { + cl_log(LOG_ERR + , "authtoken does not match, authtoken=%s, authstr=%s" + , authtoken, authstr); + } + return(FALSE); +} + diff --git a/lib/clplumbing/cl_pidfile.c b/lib/clplumbing/cl_pidfile.c new file mode 100644 index 0000000..b94573b --- /dev/null +++ b/lib/clplumbing/cl_pidfile.c @@ -0,0 +1,294 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The following information is from the Filesystem Hierarchy Standard + * version 2.1 dated 12 April, 2000. + * + * 5.6 /var/lock : Lock files + * Lock files should be stored within the /var/lock directory structure. + * Device lock files, such as the serial device lock files that were originally + * found in either /usr/spool/locks or /usr/spool/uucp, must now be stored in + * /var/lock. The naming convention which must be used is LCK.. followed by + * the base name of the device file. For example, to lock /dev/cua0 the file + * LCK..cua0 would be created. + * + * The format used for device lock files must be the HDB UUCP lock file format. + * The HDB format is to store the process identifier (PID) as a ten byte + * ASCII decimal number, with a trailing newline. For example, if process 1230 + * holds a lock file, it would contain the eleven characters: space, space, + * space, space, space, space, one, two, three, zero, and newline. + * Then, anything wishing to use /dev/cua0 can read the lock file and act + * accordingly (all locks in /var/lock should be world-readable). + * + * + * PERMISSIONS NOTE: + * Different linux distributions set the mode of the lock directory differently + * Any process which wants to create lock files must have write permissions + * on FILE_LOCK_D (probably /var/lock). For things like the heartbeat API + * code, this may mean allowing the uid of the processes that use this API + * to join group uucp, or making the binaries setgid to uucp. + */ + +/* The code in this file originally written by Guenther Thomsen */ +/* Somewhat mangled by Alan Robertson */ + +/* + * Lock a tty (using lock files, see linux `man 2 open` close to O_EXCL) + * serial_device has to be _the complete path_, i.e. including '/dev/' to the + * special file, which denotes the tty to lock -tho + * return 0 on success, + * -1 if device is locked (lockfile exists and isn't stale), + * -2 for temporarily failure, try again, + * other negative value, if something unexpected happend (failure anyway) + */ + + +/* This is what the FHS standard specifies for the size of our lock file */ +#define LOCKSTRLEN 11 +#include +static int IsRunning(long pid) +{ + int rc = 0; + long mypid; + int running = 0; + char proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX]; + + /* check if pid is running */ + if (CL_KILL(pid, 0) < 0 && errno == ESRCH) { + goto bail; + } + +#ifndef HAVE_PROC_PID + return 1; +#endif + + /* check to make sure pid hasn't been reused by another process */ + snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", pid); + + rc = readlink(proc_path, exe_path, PATH_MAX-1); + if(rc < 0) { + cl_perror("Could not read from %s", proc_path); + goto bail; + } + exe_path[rc] = 0; + + mypid = (unsigned long) getpid(); + + snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", mypid); + rc = readlink(proc_path, myexe_path, PATH_MAX-1); + if(rc < 0) { + cl_perror("Could not read from %s", proc_path); + goto bail; + } + myexe_path[rc] = 0; + + if(strcmp(exe_path, myexe_path) == 0) { + running = 1; + } + + bail: + return running; +} + +static int +DoLock(const char *filename) +{ + char lf_name[256], tf_name[256], buf[LOCKSTRLEN+1]; + int fd; + long pid, mypid; + int rc; + struct stat sbuf; + + mypid = (unsigned long) getpid(); + + snprintf(lf_name, sizeof(lf_name), "%s",filename); + + snprintf(tf_name, sizeof(tf_name), "%s.%lu", + filename, mypid); + + if ((fd = open(lf_name, O_RDONLY)) >= 0) { + if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) { + sleep(1); /* if someone was about to create one, + * give'm a sec to do so + * Though if they follow our protocol, + * this won't happen. They should really + * put the pid in, then link, not the + * other way around. + */ + } + if (read(fd, buf, sizeof(buf)) < 1) { + /* lockfile empty -> rm it and go on */; + } else { + if (sscanf(buf, "%lu", &pid) < 1) { + /* lockfile screwed up -> rm it and go on */ + } else { + if (pid > 1 && (getpid() != pid) + && IsRunning(pid)) { + /* is locked by existing process + * -> give up */ + close(fd); + return -1; + } else { + /* stale lockfile -> rm it and go on */ + } + } + } + unlink(lf_name); + close(fd); + } + if ((fd = open(tf_name, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) { + /* Hmmh, why did we fail? Anyway, nothing we can do about it */ + return -3; + } + + /* Slight overkill with the %*d format ;-) */ + snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN-1, mypid); + + if (write(fd, buf, LOCKSTRLEN) != LOCKSTRLEN) { + /* Again, nothing we can do about this */ + rc = -3; + close(fd); + goto out; + } + close(fd); + + switch (link(tf_name, lf_name)) { + case 0: + if (stat(tf_name, &sbuf) < 0) { + /* something weird happened */ + rc = -3; + break; + } + if (sbuf.st_nlink < 2) { + /* somehow, it didn't get through - NFS trouble? */ + rc = -2; + break; + } + rc = 0; + break; + case EEXIST: + rc = -1; + break; + default: + rc = -3; + } + out: + unlink(tf_name); + return rc; +} + +static int +DoUnlock(const char * filename) +{ + char lf_name[256]; + + snprintf(lf_name, sizeof(lf_name), "%s", filename); + + return unlink(lf_name); +} + + +int +cl_read_pidfile(const char*filename) +{ + long pid = 0; + + pid = cl_read_pidfile_no_checking(filename); + + if (pid < 0){ + return - LSB_STATUS_STOPPED; + } + + if (IsRunning(pid)){ + return pid; + }else{ + return -LSB_STATUS_VAR_PID; + } +} + + +int +cl_read_pidfile_no_checking(const char*filename) +{ + int fd; + long pid = 0; + char buf[LOCKSTRLEN+1]; + if ((fd = open(filename, O_RDONLY)) < 0) { + return -1; + } + + if (read(fd, buf, sizeof(buf)) < 1) { + close(fd); + return -1; + } + + if (sscanf(buf, "%lu", &pid) <= 0) { + close(fd); + return -1; + } + + if (pid <= 0){ + close(fd); + return -1; + } + close(fd); + return pid; +} + + +int +cl_lock_pidfile(const char *filename) +{ + if (filename == NULL) { + errno = EFAULT; + return -3; + } + return DoLock(filename); +} + +/* + * Unlock a file (remove its lockfile) + * do we need to check, if its (still) ours? No, IMHO, if someone else + * locked our line, it's his fault -tho + * returns 0 on success + * <0 if some failure occured + */ + +int +cl_unlock_pidfile(const char *filename) +{ + if (filename == NULL) { + errno = EFAULT; + return -3; + } + + return(DoUnlock(filename)); +} diff --git a/lib/clplumbing/cl_plugin.c b/lib/clplumbing/cl_plugin.c new file mode 100644 index 0000000..c039a35 --- /dev/null +++ b/lib/clplumbing/cl_plugin.c @@ -0,0 +1,140 @@ + +/* + * cl_plugin.c: This file handle plugin loading and deleting + * + * Copyright (C) 2005 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* #include */ +/* #include */ +#include + +#define MAXTYPES 16 +#define MAXTYPELEN 64 + +static GHashTable* funcstable[MAXTYPES]; + +static PILPluginUniv* plugin_univ = NULL; + +static PILGenericIfMgmtRqst reqs[] = + { + {"compress", &funcstable[0], NULL, NULL, NULL}, + {"HBcoms", &funcstable[1], NULL, NULL, NULL}, + {"HBauth", &funcstable[2], NULL, NULL, NULL}, + {"RAExec", &funcstable[3], NULL, NULL, NULL}, + {"quorum", &funcstable[4], NULL, NULL, NULL}, + {"tiebreaker", &funcstable[5], NULL, NULL, NULL}, + {"quorumd", &funcstable[6], NULL, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} + }; + +static int +init_pluginsys(void){ + + if (plugin_univ) { + return TRUE; + } + + plugin_univ = NewPILPluginUniv(HA_PLUGIN_DIR); + + if (plugin_univ) { + if (PILLoadPlugin(plugin_univ, PI_IFMANAGER, "generic", reqs) + != PIL_OK){ + cl_log(LOG_ERR, "generic plugin load failed\n"); + DelPILPluginUniv(plugin_univ); + plugin_univ = NULL; + } + }else{ + cl_log(LOG_ERR, "pi univ creation failed\n"); + } + return plugin_univ != NULL; + +} + +int +cl_remove_plugin(const char* type, const char* pluginname) +{ + return HA_OK; +} + +void* +cl_load_plugin(const char* type, const char* pluginname) +{ + void* funcs = NULL; + int i = 0; + GHashTable** table = NULL; + + while (reqs[i].iftype != NULL){ + if ( strcmp(reqs[i].iftype,type) != 0){ + i++; + continue; + } + + table = reqs[i].ifmap; + break; + } + + if (table == NULL){ + cl_log(LOG_ERR, "%s: function table not found",__FUNCTION__); + return NULL; + } + + if (!init_pluginsys()){ + cl_log(LOG_ERR, "%s: init plugin universe failed", __FUNCTION__); + return NULL; + } + + if ((funcs = g_hash_table_lookup(*table, pluginname)) + == NULL){ + if (PILPluginExists(plugin_univ, type, pluginname) == PIL_OK){ + PIL_rc rc; + rc = PILLoadPlugin(plugin_univ, type, pluginname, NULL); + if (rc != PIL_OK){ + cl_log(LOG_ERR, + "Cannot load plugin %s[%s]", + pluginname, + PIL_strerror(rc)); + return NULL; + } + funcs = g_hash_table_lookup(*table, + pluginname); + } + + } + if (funcs == NULL){ + cl_log(LOG_ERR, "%s: module(%s) not found", + __FUNCTION__, pluginname); + return NULL; + } + + return funcs; + +} + diff --git a/lib/clplumbing/cl_poll.c b/lib/clplumbing/cl_poll.c new file mode 100644 index 0000000..789eb1a --- /dev/null +++ b/lib/clplumbing/cl_poll.c @@ -0,0 +1,809 @@ +#include +#include +#include +/* + * Substitute poll(2) function using POSIX real time signals. + * + * The poll(2) system call often has significant latencies and realtime + * impacts (probably because of its variable length argument list). + * + * These functions let us use real time signals and sigtimedwait(2) instead + * of poll - for those files which work with real time signals. + * In the 2.4 series of Linux kernels, this does *not* include FIFOs. + * + * NOTE: We (have to) grab the SIGPOLL signal for our own purposes. + * Hope that's OK with you... + * + * Special caution: We can only incompletely simulate the difference between + * the level-triggered interface of poll(2) and the edge-triggered behavior + * of I/O signals. As a result you *must* read all previously-indicated + * incoming data before calling cl_poll() again. Otherwise you may miss + * some incoming data (and possibly hang). + * + * + * Copyright (C) 2003 IBM Corporation + * + * Author: + * + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + **************************************************************************/ + + +#define __USE_GNU 1 +# include +#undef __USE_GNU + +#include +#include +#include +#include +#include +#include + + + +/* Turn on to log odd realtime behavior */ + +#define TIME_CALLS 1 +#ifdef TIME_CALLS +# include +# include +#endif + +static int debug = 0; + +int /* Slightly sleazy... */ +cl_glibpoll(GPollFD* ufds, guint nfsd, gint timeout) +{ + (void)debug; + return cl_poll((struct pollfd*)ufds, nfsd, timeout); +} + +#if defined (F_SETSIG) && defined(F_SETOWN) && defined (O_ASYNC) +# define HAVE_FCNTL_F_SETSIG +#endif + +#ifndef HAVE_FCNTL_F_SETSIG + +/* + * Dummy cl_poll() and cl_poll_ignore() functions for systems where + * we don't have all the support we need. + */ + +int +cl_poll(struct pollfd *fds, unsigned int nfds, int timeout) +{ + return poll(fds, (nfds_t)nfds, timeout); +} + +int +cl_poll_ignore(int fd) +{ + return 0; +} + +#else /* HAVE_FCNTL_F_SETSIG */ +static void dump_fd_info(struct pollfd *fds, unsigned int nfds, int timeoutms); +static void check_fd_info(struct pollfd *fds, unsigned int nfds); +static void cl_real_poll_fd(int fd); +static void cl_poll_sigpoll_overflow_sigaction(int nsig, siginfo_t* , void*); +static void cl_poll_sigpoll_overflow(void); +static int cl_poll_get_sigqlimit(void); +typedef unsigned char poll_bool; + +/* + * Here's our strategy: + * We have a set of signals which we use for these file descriptors, + * and we use sigtimedwait(2) to wait on information from these various + * signals. + * + * If we are ever asked to wait for a particular signal, then we will + * enable signals for that file descriptor, and post the events in + * our own cache. The next time you include that signal in a call + * to cl_poll(), you will get the information delivered + * to you in your cl_poll() call. + * + * If you want to stop monitoring a particular file descriptor, use + * cl_poll_ignore() for that purpose. Doing this is a good idea, but + * not fatal if omitted... + */ + +/* Information about a file descriptor we're monitoring */ + +typedef struct poll_fd_info_s { + short nsig; /* Which signal goes with it? */ + short pendevents; /* Pending events */ +}poll_info_t; + +static int max_allocated = 0; +static poll_bool* is_monitored = NULL; /* Sized by max_allocated */ +static poll_info_t* monitorinfo = NULL; /* Sized by max_allocated */ +static int cl_nsig = 0; +static gboolean SigQOverflow = FALSE; + +static int cl_init_poll_sig(struct pollfd *fds, unsigned int nfds); +static short cl_poll_assignsig(int fd); +static void cl_poll_sigaction(int nsig, siginfo_t* info, void* v); +static int cl_poll_prepsig(int nsig); + + +/* + * SignalSet is the set of all file descriptors we're monitoring. + * + * We monitor a file descriptor forever, unless you tell us not to + * by calling cl_poll_ignore(), or you (mistakenly) give it to + * us to look at in another poll call after you've closed it. + */ + +static sigset_t SignalSet; + +/* Select the signal you want us to use (must be a RT signal) */ +int +cl_poll_setsig(int nsig) +{ + if (nsig < SIGRTMIN || nsig >= SIGRTMAX) { + errno = EINVAL; + return -1; + } + if (cl_poll_prepsig(nsig) < 0) { + return -1; + } + cl_nsig = nsig; + return 0; +} + +/* + * It's harmless to call us multiple times on the same signal. + */ +static int +cl_poll_prepsig(int nsig) +{ + static gboolean setinityet=FALSE; + + if (!setinityet) { + CL_SIGEMPTYSET(&SignalSet); + cl_signal_set_simple_action(SIGPOLL + , cl_poll_sigpoll_overflow_sigaction + , NULL); + setinityet = TRUE; + } + if (CL_SIGINTERRUPT(nsig, FALSE) < 0) { + cl_perror("sig_interrupt(%d, FALSE)", nsig); + return -1; + } + if (CL_SIGADDSET(&SignalSet, nsig) < 0) { + cl_perror("sig_addset(&SignalSet, %d)", nsig); + return -1; + } + if (CL_SIGPROCMASK(SIG_BLOCK, &SignalSet, NULL) < 0) { + cl_perror("sig_sigprocmask(SIG_BLOCK, sig %d)", nsig); + return -1; + } + if (debug) { + cl_log(LOG_DEBUG + , "Signal %d belongs to us...", nsig); + cl_log(LOG_DEBUG, "cl_poll_prepsig(%d) succeeded.", nsig); + } + + return 0; +} + +#define FD_CHUNKSIZE 64 + +/* Set of events everyone must monitor whether they want to or not ;-) */ +#define CONSTEVENTS (POLLHUP|POLLERR|POLLNVAL) + +#define RECORDFDEVENT(fd, flags) (monitorinfo[fd].pendevents |= (flags)) + +/* + * Initialized our poll-simulation data structures. + * This means (among other things) registering any monitored + * file descriptors. + */ +static int +cl_init_poll_sig(struct pollfd *fds, unsigned int nfds) +{ + unsigned j; + int maxmonfd = -1; + int nmatch = 0; + + + if (cl_nsig == 0) { + cl_nsig = ((SIGRTMIN+SIGRTMAX)/2); + if (cl_poll_setsig(cl_nsig) < 0) { + return -1; + } + } + for (j=0; j < nfds; ++j) { + const int fd = fds[j].fd; + + if (fd > maxmonfd) { + maxmonfd = fd; + } + } + + /* See if we need to malloc/realloc our data structures */ + + if (maxmonfd >= max_allocated) { + int newsize; + int growthamount; + + newsize = ((maxmonfd + FD_CHUNKSIZE)/FD_CHUNKSIZE) + * FD_CHUNKSIZE; + growthamount = newsize - max_allocated; + + /* This can't happen ;-) */ + if (growthamount <= 0 || newsize <= maxmonfd) { + errno = EINVAL; + return -1; + } + + /* Allocate (more) memory! */ + + if ((is_monitored = (poll_bool*)realloc(is_monitored + , newsize * sizeof(poll_bool))) == NULL + || (monitorinfo = (poll_info_t*) realloc(monitorinfo + , newsize * sizeof(poll_info_t))) == NULL) { + + if (is_monitored) { + free(is_monitored); + is_monitored = NULL; + } + if (monitorinfo) { + free(monitorinfo); + monitorinfo = NULL; + } + max_allocated = 0; + errno = ENOMEM; + return -1; + } + memset(monitorinfo+max_allocated, 0 + , growthamount * sizeof(monitorinfo[0])); + memset(is_monitored+max_allocated, FALSE + , growthamount*sizeof(is_monitored[0])); + max_allocated = newsize; + } + + if (fds->events != 0 && debug) { + cl_log(LOG_DEBUG + , "Current event mask for fd [0] {%d} 0x%x" + , fds->fd, fds->events); + } + /* + * Examine each fd for the following things: + * Is it already monitored? + * if not, set it up for monitoring. + * Do we have events for it? + * if so, post events... + */ + + for (j=0; j < nfds; ++j) { + const int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + short nsig; + int badfd = FALSE; + + is_monitored[fd] = TRUE; + + if (moni->nsig <= 0) { + nsig = cl_poll_assignsig(fd); + if (nsig < 0) { + RECORDFDEVENT(fd, POLLERR); + badfd = TRUE; + }else{ + /* Use real poll(2) to get initial + * event status + */ + moni->nsig = nsig; + cl_real_poll_fd(fd); + } + }else if (fcntl(fd, F_GETFD) < 0) { + cl_log(LOG_ERR, "bad fd(%d)", fd); + RECORDFDEVENT(fd, POLLNVAL); + badfd = TRUE; + } + + /* Look for pending events... */ + + fds[j].revents = (moni->pendevents + & (fds[j].events|CONSTEVENTS)); + + if (fds[j].revents) { + ++nmatch; + moni->pendevents &= ~(fds[j].revents); + if (debug) { + cl_log(LOG_DEBUG + , "revents for fd %d: 0x%x" + , fds[j].fd, fds[j].revents); + cl_log(LOG_DEBUG + , "events for fd %d: 0x%x" + , fds[j].fd, fds[j].events); + } + }else if (fds[j].events && debug) { + cl_log(LOG_DEBUG + , "pendevents for fd %d: 0x%x" + , fds[j].fd, moni->pendevents); + } + if (badfd) { + cl_poll_ignore(fd); + } + } + if (nmatch != 0 && debug) { + cl_log(LOG_DEBUG, "Returning %d events from cl_init_poll_sig()" + , nmatch); + } + return nmatch; +} + +/* + * Initialize our current state of the world with info from the + * real poll(2) call. + * + * We call this when we first see a particular fd, and after a signal + * queue overflow. + */ +static void +cl_real_poll_fd(int fd) +{ + struct pollfd pfd[1]; + + if (fd >= max_allocated || !is_monitored[fd]) { + return; + } + + if (debug) { + cl_log(LOG_DEBUG + , "Calling poll(2) on fd %d", fd); + } + /* Get the current state of affaris from poll(2) */ + pfd[0].fd = fd; + pfd[0].revents = 0; + pfd[0].events = ~0; + if (poll(pfd, 1, 0) >= 0) { + RECORDFDEVENT(fd, pfd[0].revents); + if (pfd[0].revents & (POLLNVAL|POLLERR)) { + cl_log(LOG_INFO, "cl_poll_real_fd(%d): error in revents [%d]" + , fd, pfd[0].revents); + } + if (debug) { + cl_log(LOG_DEBUG + , "Old news from poll(2) for fd %d: 0x%x" + , fd, pfd[0].revents); + } + }else{ + if (fcntl(fd, F_GETFL) < 0) { + cl_perror("cl_poll_real_fd(%d): F_GETFL failure" + , fd); + RECORDFDEVENT(fd, POLLNVAL); + }else{ + RECORDFDEVENT(fd, POLLERR); + } + } +} + +/* + * Assign a signal for monitoring the given file descriptor + */ + +static short +cl_poll_assignsig(int fd) +{ + int flags; + + + if (debug) { + cl_log(LOG_DEBUG + , "Signal %d monitors fd %d...", cl_nsig, fd); + } + + /* Test to see if the file descriptor is good */ + if ((flags = fcntl(fd, F_GETFL)) < 0) { + cl_perror("cl_poll_assignsig(%d) F_GETFL failure" + , fd); + return -1; + } + + /* Associate the right signal with the fd */ + + if (fcntl(fd, F_SETSIG, cl_nsig) < 0) { + cl_perror("cl_poll_assignsig(%d) F_SETSIG failure" + , fd); + return -1; + } + + /* Direct the signals to us */ + if (fcntl(fd, F_SETOWN, getpid()) < 0) { + cl_perror("cl_poll_assignsig(%d) F_SETOWN failure", fd); + return -1; + } + + /* OK... Go ahead and send us signals! */ + + if (fcntl(fd, F_SETFL, flags|O_ASYNC) < 0) { + cl_perror("cl_poll_assignsig(%d) F_SETFL(O_ASYNC) failure" + , fd); + return -1; + } + + return cl_nsig; +} + + +/* + * This is a function we call as a (fake) signal handler. + * + * It records events to our "monitorinfo" structure. + * + * Except for the cl_log() call, it could be called in a signal + * context. + */ + +static void +cl_poll_sigaction(int nsig, siginfo_t* info, void* v) +{ + int fd; + + /* What do you suppose all the various si_code values mean? */ + + fd = info->si_fd; + if (debug) { + cl_log(LOG_DEBUG + , "cl_poll_sigaction(nsig=%d fd=%d" + ", si_code=%d si_band=0x%lx)" + , nsig, fd, info->si_code + , (unsigned long)info->si_band); + } + + if (fd <= 0) { + return; + } + + + if (fd >= max_allocated || !is_monitored[fd]) { + return; + } + + /* We should not call logging functions in (real) signal handlers */ + if (nsig != monitorinfo[fd].nsig) { + cl_log(LOG_ERR, "cl_poll_sigaction called with signal %d/%d" + , nsig, monitorinfo[fd].nsig); + } + + /* Record everything as a pending event. */ + RECORDFDEVENT(fd, info->si_band); +} + + + +/* + * This is called whenever a file descriptor shouldn't be + * monitored any more. + */ +int +cl_poll_ignore(int fd) +{ + int flags; + + if (debug) { + cl_log(LOG_DEBUG + , "cl_poll_ignore(%d)", fd); + } + if (fd < 0 || fd >= max_allocated) { + errno = EINVAL; + return -1; + } + if (!is_monitored[fd]) { + return 0; + } + + is_monitored[fd] = FALSE; + memset(monitorinfo+fd, 0, sizeof(monitorinfo[0])); + + if ((flags = fcntl(fd, F_GETFL)) >= 0) { + flags &= ~O_ASYNC; + if (fcntl(fd, F_SETFL, flags) < 0) { + return -1; + } + }else{ + return flags; + } + return 0; +} + + +/* + * cl_poll: fake poll routine based on POSIX realtime signals. + * + * We want to emulate poll as exactly as possible, but poll has a couple + * of problems: scaleability, and it tends to sleep in the kernel + * because the first argument is an argument of arbitrary size, and + * generally requires allocating memory. + * + * The challenge is that poll is level-triggered, but the POSIX + * signals (and sigtimedwait(2)) are edge triggered. This is + * one of the reasons why we have the cl_real_poll_fd() function + * - to get the current "level" before we start. + * Once we have this level we can compute something like the current + * level + */ + +int +cl_poll(struct pollfd *fds, unsigned int nfds, int timeoutms) +{ + int nready; + struct timespec ts; + static const struct timespec zerotime = {0L, 0L}; + const struct timespec* itertime = &ts; + siginfo_t info; + int eventcount = 0; + unsigned int j; + int savederrno = errno; + int stw_errno; + int rc; + longclock_t starttime; + longclock_t endtime; + const int msfudge + = 2* 1000/hz_longclock(); + int mselapsed = 0; + + /* Do we have any old news to report? */ + if ((nready=cl_init_poll_sig(fds, nfds)) != 0) { + /* Return error or old news to report */ + if (debug) { + cl_log(LOG_DEBUG, "cl_poll: early return(%d)", nready); + } + return nready; + } + + /* Nothing to report yet... */ + + /* So, we'll do a sigtimedwait(2) to wait for signals + * and see if we can find something to report... + * + * cl_init_poll() prepared a set of file signals to watch... + */ + +recalcandwaitagain: + if (timeoutms >= 0) { + ts.tv_sec = timeoutms / 1000; + ts.tv_nsec = (((unsigned long)timeoutms) % 1000UL)*1000000UL; + }else{ + ts.tv_sec = G_MAXLONG; + ts.tv_nsec = 99999999UL; + } + + /* + * Perform a timed wait for any of our signals... + * + * We shouldn't sleep for any call but (possibly) the first one. + * Subsequent calls should just pick up other events without + * sleeping. + */ + + starttime = time_longclock(); + /* + * Wait up to the prescribed time for a signal. + * If we get a signal, then loop grabbing all other + * pending signals. Note that subsequent iterations will + * use &zerotime to get the minimum wait time. + */ + if (debug) { + check_fd_info(fds, nfds); + dump_fd_info(fds, nfds, timeoutms); + } +waitagain: + while (sigtimedwait(&SignalSet, &info, itertime) >= 0) { + int nsig = info.si_signo; + + /* Call signal handler to simulate signal reception */ + + cl_poll_sigaction(nsig, &info, NULL); + itertime = &zerotime; + } + stw_errno=errno; /* Save errno for later use */ + endtime = time_longclock(); + mselapsed = longclockto_ms(sub_longclock(endtime, starttime)); + +#ifdef TIME_CALLS + if (timeoutms >= 0 && mselapsed > timeoutms + msfudge) { + /* We slept too long... */ + cl_log(LOG_WARNING + , "sigtimedwait() sequence for %d ms took %d ms" + , timeoutms, mselapsed); + } +#endif + + if (SigQOverflow) { + /* OOPS! Better recover from this! */ + /* This will use poll(2) to correct our current status */ + cl_poll_sigpoll_overflow(); + } + + /* Post observed events and count them... */ + + for (j=0; j < nfds; ++j) { + int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + fds[j].revents = (moni->pendevents + & (fds[j].events|CONSTEVENTS)); + if (fds[j].revents) { + ++eventcount; + moni->pendevents &= ~(fds[j].revents); + /* Make POLLHUP persistent */ + if (fds[j].revents & POLLHUP) { + moni->pendevents |= POLLHUP; + /* Don't lose input events at EOF */ + if (fds[j].events & POLLIN) { + cl_real_poll_fd(fds[j].fd); + } + } + } + } + if (eventcount == 0 && stw_errno == EAGAIN && timeoutms != 0) { + /* We probably saw an event the user didn't ask to see. */ + /* Consquently, we may have more waiting to do */ + if (timeoutms < 0) { + /* Restore our infinite wait time */ + itertime = &ts; + goto waitagain; + }else if (timeoutms > 0) { + if (mselapsed < timeoutms) { + timeoutms -= mselapsed; + goto recalcandwaitagain; + } + } + } + rc = (eventcount > 0 ? eventcount : (stw_errno == EAGAIN ? 0 : -1)); + + if (rc >= 0) { + errno = savederrno; + } + return rc; +} +/* + * Debugging routine for printing current poll arguments, etc. + */ +static void +dump_fd_info(struct pollfd *fds, unsigned int nfds, int timeoutms) +{ + unsigned j; + + cl_log(LOG_DEBUG, "timeout: %d milliseconds", timeoutms); + for (j=0; j < nfds; ++j) { + int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + + cl_log(LOG_DEBUG, "fd %d flags: 0%o, signal: %d, events: 0x%x" + ", revents: 0x%x, pendevents: 0x%x" + , fd, fcntl(fd, F_GETFL), moni->nsig + , fds[j].events, fds[j].revents, moni->pendevents); + } + for (j=SIGRTMIN; j < (unsigned)SIGRTMAX; ++j) { + if (!sigismember(&SignalSet, j)) { + continue; + } + cl_log(LOG_DEBUG, "Currently monitoring RT signal %d", j); + } +} + +/* + * Debugging routine for auditing our file descriptors, etc. + */ +static void +check_fd_info(struct pollfd *fds, unsigned int nfds) +{ + unsigned j; + + for (j=0; j < nfds; ++j) { + int fd = fds[j].fd; + poll_info_t* moni = monitorinfo+fd; + + if (!sigismember(&SignalSet, moni->nsig)) { + cl_log(LOG_ERR, "SIGNAL %d not in monitored SignalSet" + , moni->nsig); + } + } + for (j=0; j < 10; ++j) { + int sig; + int flags; + int pid; + if ((flags = fcntl(j, F_GETFL)) < 0 || (flags & O_ASYNC) ==0){ + continue; + } + sig = fcntl(j, F_GETSIG); + if (sig == 0) { + cl_log(LOG_ERR, "FD %d will get SIGIO", j); + } + if (!sigismember(&SignalSet, sig)) { + cl_log(LOG_ERR, "FD %d (signal %d) is not in SignalSet" + , j, sig); + } + if (sig < SIGRTMIN || sig >= SIGRTMAX) { + cl_log(LOG_ERR, "FD %d (signal %d) is not RealTime" + , j, sig); + } + pid = fcntl(j, F_GETOWN); + if (pid != getpid()) { + cl_log(LOG_ERR, "FD %d (signal %d) owner is pid %d" + , j, sig, pid); + } + } +} + +/* Note that the kernel signalled an event queue overflow */ +static void +cl_poll_sigpoll_overflow_sigaction(int nsig, siginfo_t* info, void* v) +{ + SigQOverflow = TRUE; +} + +#define MAXQNAME "rtsig-max" +/* + * Called when signal queue overflow is suspected. + * We then use poll(2) to get the current data. It's slow, but it + * should work quite nicely. + */ +static void +cl_poll_sigpoll_overflow(void) +{ + int fd; + int limit; + + if (!SigQOverflow) { + return; + } + cl_log(LOG_WARNING, "System signal queue overflow."); + limit = cl_poll_get_sigqlimit(); + if (limit > 0) { + cl_log(LOG_WARNING, "Increase '%s'. Current limit is %d" + " (see sysctl(8)).", MAXQNAME, limit); + } + + SigQOverflow = FALSE; + + for (fd = 0; fd < max_allocated; ++fd) { + if (is_monitored[fd]) { + cl_real_poll_fd(fd); + } + } +} + +#define PSK "/proc/sys/kernel/" + +/* Get current kernel signal queue limit */ +/* This only works on Linux - but that's not a big problem... */ +static int +cl_poll_get_sigqlimit(void) +{ + int limit = -1; + int pfd; + char result[32]; + + pfd = open(PSK MAXQNAME, O_RDONLY); + if (pfd >= 0 && read(pfd, result, sizeof(result)) > 1) { + limit = atoi(result); + if (limit < 1) { + limit = -1; + } + } + if (pfd >= 0) { + close(pfd); + } + return limit; +} +#endif /* HAVE_FCNTL_F_SETSIG */ diff --git a/lib/clplumbing/cl_random.c b/lib/clplumbing/cl_random.c new file mode 100644 index 0000000..4bafcfe --- /dev/null +++ b/lib/clplumbing/cl_random.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2005 Guochun Shi + * Copyright (C) 2005 International Business Machines Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_TIME_H +#include +#endif +#include +#include + +/* Used to provide seed to the random number generator */ +unsigned int +cl_randseed(void) +{ + char buf[16]; + FILE* fs; + struct timeval tv; + const char * randdevname [] = {"/dev/urandom", "/dev/random"}; + int idev; +#if 0 + long horrid; +#endif + + /* + * Notes, based on reading of man pages of Solaris, FreeBSD and Linux, + * and on proof-of-concept tests on Solaris and Linux (32- and 64-bit). + * + * Reminder of a subtlety: our intention is not to return a random + * number, but rather to return a random-enough seed for future + * random numbers. So don't bother trying (e.g.) "rand()" and + * "random()". + * + * /dev/random and dev/urandom seem to be a related pair. In the + * words of the song: "You can't have one without the other". + * + * /dev/random is probably the best. But it can block. The Solaris + * implementation can apparently honour "O_NONBLOCK" and "O_NDELAY". + * But can others? For this reason, I chose not to use it at present. + * + * /dev/urandom (with the "u") is also good. This doesn't block. + * But some OSes may lack it. It is tempting to detect its presence + * with autoconf and use the result in a "hash-if" here. BUT... in + * at least one OS, its presence can vary depending upon patch levels, + * so a binary/package built on an enabled machine might hit trouble + * when run on one where it is absent. (And vice versa: a build on a + * disabled machine would be unable to take advantage of it on an + * enabled machine.) Therefore always try for it at run time. + * + * "gettimeofday()" returns a random-ish number in its millisecond + * component. + * + * -- David Lee, Jan 2006 + */ + + /* + * Each block below is logically of the form: + * if good-feature appears present { + * try feature + * if feature worked { + * return its result + * } + * } + * -- fall through to not-quite-so-good feature -- + */ + + /* + * Does any of the random device names work? + */ + for (idev=0; idev < DIMOF(randdevname); ++idev) { + fs = fopen(randdevname[idev], "r"); + if (fs == NULL) { + cl_log(LOG_INFO, "%s: Opening file %s failed" + , __FUNCTION__, randdevname[idev]); + }else{ + if (fread(buf, 1, sizeof(buf), fs)!= sizeof(buf)){ + cl_log(LOG_INFO, "%s: reading file %s failed" + , __FUNCTION__, randdevname[idev]); + fclose(fs); + }else{ + fclose(fs); + return (unsigned int)cl_binary_to_int(buf, sizeof(buf)); + } + } + } + + /* + * Try "gettimeofday()"; use its microsecond output. + * (Might it be prudent to let, say, the seconds further adjust this, + * in case the microseconds are too predictable?) + */ + if (gettimeofday(&tv, NULL) != 0) { + cl_log(LOG_INFO, "%s: gettimeofday failed", + __FUNCTION__); + }else{ + return (unsigned int) tv.tv_usec; + } + /* + * times(2) returns the number of clock ticks since + * boot. Fairly predictable, but not completely so... + */ + return (unsigned int) cl_times(); + + +#if 0 + /* + * If all else has failed, return (as a number) the address of + * something on the stack. + * Poor, but at least it has a chance of some sort of variability. + */ + horrid = (long) &tv; + return (unsigned int) horrid; /* pointer to local variable exposed */ +#endif +} + +static gboolean inityet = FALSE; + +static void +cl_init_random(void) +{ + if (inityet) + return; + + inityet=TRUE; + srand(cl_randseed()); +} + +int +get_next_random(void) +{ + if (!inityet) + cl_init_random(); + + return rand(); +} diff --git a/lib/clplumbing/cl_reboot.c b/lib/clplumbing/cl_reboot.c new file mode 100644 index 0000000..c4c3ab0 --- /dev/null +++ b/lib/clplumbing/cl_reboot.c @@ -0,0 +1,59 @@ +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_SYS_REBOOT_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#include +#include + +enum rebootopt { + REBOOT_DEFAULT = 0, + REBOOT_NOCOREDUMP = 1, + REBOOT_COREDUMP = 2, +}; +static enum rebootopt coredump = REBOOT_DEFAULT; + +void +cl_enable_coredump_before_reboot(gboolean yesno) +{ + coredump = (yesno ? REBOOT_COREDUMP : REBOOT_NOCOREDUMP); +} + + +void cl_reboot(int msdelaybeforereboot, const char * reason) +{ + int rebootflag = 0; + int systemrc = 0; +#ifdef RB_AUTOBOOT + rebootflag = RB_AUTOBOOT; +#endif +#ifdef RB_NOSYNC + rebootflag = RB_NOSYNC; +#endif +#ifdef RB_DUMP + if (coredump == REBOOT_COREDUMP) { + rebootflag = RB_DUMP; + } +#endif + cl_log(LOG_EMERG, "Rebooting system. Reason: %s", reason); + sync(); + mssleep(msdelaybeforereboot); +#if REBOOT_ARGS == 1 + reboot(rebootflag); +#elif REBOOT_ARGS == 2 + reboot(rebootflag, NULL); +#else +#error "reboot() call needs to take one or two args" +#endif + /* Shouldn't ever get here, but just in case... */ + systemrc=system(REBOOT " " REBOOT_OPTIONS); + cl_log(LOG_EMERG, "ALL REBOOT OPTIONS FAILED: %s returned %d" + , REBOOT " " REBOOT_OPTIONS, systemrc); + exit(1); +} diff --git a/lib/clplumbing/cl_signal.c b/lib/clplumbing/cl_signal.c new file mode 100644 index 0000000..feedb3d --- /dev/null +++ b/lib/clplumbing/cl_signal.c @@ -0,0 +1,209 @@ +/* + * cl_signal.c: signal handling routines to be used by Linux-HA programmes + * + * Copyright (C) 2002 Horms + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +#include +#include + + +int +cl_signal_set_handler(int sig, void (*handler)(int), sigset_t *mask +, int flags, struct sigaction *oldact) +{ + struct sigaction sa; + + sa.sa_handler = handler; + sa.sa_mask = *mask; + sa.sa_flags = flags; + + if (sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_handler(): sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + if(sigemptyset(&mask) < 0) { + cl_perror("cl_signal_set_simple_handler(): " + "sigemptyset()"); + return(-1); + } + + sa.sa_handler = handler; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_simple_handler()" + ": sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_action(int sig, void (*action)(int, siginfo_t *, void *) +, sigset_t *mask, int flags, struct sigaction *oldact) +{ + struct sigaction sa; + + sa.sa_sigaction = action; + sa.sa_mask = *mask; + sa.sa_flags = flags; + + if(sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_action(): sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_simple_action(int sig, void (*action)(int, siginfo_t *, void *) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + if(sigemptyset(&mask) < 0) { + cl_perror("cl_signal_set_simple_action()" + ": sigemptyset()"); + return(-1); + } + + sa.sa_sigaction = action; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + cl_perror("cl_signal_set_simple_action()" + ": sigaction()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_interrupt(int sig, int flag) +{ + if(siginterrupt(sig, flag) < 0) { + cl_perror("cl_signal_set_interrupt(): siginterrupt()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_block(int how, int signal, sigset_t *oldset) +{ + sigset_t set; + + if(sigemptyset(&set) < 0) { + cl_perror("cl_signal_block(): sigemptyset()"); + return(-1); + } + + if(sigaddset(&set, signal) < 0) { + cl_perror("cl_signal_block(): sigaddset()"); + return(-1); + } + + if(sigprocmask(how, &set, oldset) < 0) { + cl_perror("cl_signal_block(): sigprocmask()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_block_set(int how, const sigset_t *set, sigset_t *oldset) +{ + if(sigprocmask(how, set, oldset) < 0) { + cl_perror("cl_signal_block_mask(): sigprocmask()"); + return(-1); + } + + return(0); +} + + +int +cl_signal_set_handler_mode(const cl_signal_mode_t *mode, sigset_t *set) +{ + size_t i; + sigset_t our_set; + sigset_t *use_set; + + use_set = (set) ? set : &our_set; + + for (i=0; mode[i].sig; ++i) { + if(sigaddset(use_set, mode[i].sig) < 0) { + cl_perror("cl_signal_set_handler_mode(): " + "sigaddset() [signum=%d]", mode[i].sig); + return(-1); + } + } + + if (sigprocmask(SIG_UNBLOCK, use_set, NULL) < 0) { + cl_perror("cl_signal_set_handler_mode()" + ": sigprocmask()"); + return(-1); + } + + for (i=0; mode[i].sig; ++i) { + if(cl_signal_set_handler(mode[i].sig, mode[i]. handler + , use_set, SA_NOCLDSTOP, NULL) < 0) { + cl_log(LOG_ERR, "cl_signal_set_handler_mode(): " + "ha_set_sig_handler()"); + return(-1); + } + if(cl_signal_set_interrupt(mode[i].sig, mode[i].interrupt) < 0) { + cl_log(LOG_ERR, "cl_signal_set_handler_mode(): " + "hb_signal_interrupt()"); + return(-1); + } + } + + return(0); +} + diff --git a/lib/clplumbing/cl_syslog.c b/lib/clplumbing/cl_syslog.c new file mode 100644 index 0000000..6920bd5 --- /dev/null +++ b/lib/clplumbing/cl_syslog.c @@ -0,0 +1,149 @@ +/* + * Functions to support syslog. + * David Lee (c) 2005 + */ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +/* + * Some OSes already have tables to convert names into corresponding numbers. + * For instance Linux makes these available if SYSLOG_NAMES is defined. + */ +#define SYSLOG_NAMES +#include +#include + +#include +#include + +struct _syslog_code { + const char *c_name; + int c_val; +}; + +#if defined(HAVE_SYSLOG_FACILITYNAMES) + +/* + * will have included a table called "facilitynames" structured + * as a "struct _syslog_code" but the tag "_syslog_code" may be something else. + */ + +#else + +struct _syslog_code facilitynames[] = +{ +#ifdef LOG_AUTH + { "auth", LOG_AUTH }, + { "security", LOG_AUTH }, /* DEPRECATED */ +#endif +#ifdef LOG_AUTHPRIV + { "authpriv", LOG_AUTHPRIV }, +#endif +#ifdef LOG_CRON + { "cron", LOG_CRON }, +#endif +#ifdef LOG_DAEMON + { "daemon", LOG_DAEMON }, +#endif +#ifdef LOG_FTP + { "ftp", LOG_FTP }, +#endif +#ifdef LOG_KERN + { "kern", LOG_KERN }, +#endif +#ifdef LOG_LPR + { "lpr", LOG_LPR }, +#endif +#ifdef LOG_MAIL + { "mail", LOG_MAIL }, +#endif + +/* { "mark", INTERNAL_MARK }, * INTERNAL */ + +#ifdef LOG_NEWS + { "news", LOG_NEWS }, +#endif +#ifdef LOG_SYSLOG + { "syslog", LOG_SYSLOG }, +#endif +#ifdef LOG_USER + { "user", LOG_USER }, +#endif +#ifdef LOG_UUCP + { "uucp", LOG_UUCP }, +#endif +#ifdef LOG_LOCAL0 + { "local0", LOG_LOCAL0 }, +#endif +#ifdef LOG_LOCAL1 + { "local1", LOG_LOCAL1 }, +#endif +#ifdef LOG_LOCAL2 + { "local2", LOG_LOCAL2 }, +#endif +#ifdef LOG_LOCAL3 + { "local3", LOG_LOCAL3 }, +#endif +#ifdef LOG_LOCAL4 + { "local4", LOG_LOCAL4 }, +#endif +#ifdef LOG_LOCAL5 + { "local5", LOG_LOCAL5 }, +#endif +#ifdef LOG_LOCAL6 + { "local6", LOG_LOCAL6 }, +#endif +#ifdef LOG_LOCAL7 + { "local7", LOG_LOCAL7 }, +#endif + { NULL, -1 } +}; + +#endif /* HAVE_SYSLOG_FACILITYNAMES */ + +/* Convert string "auth" to equivalent number "LOG_AUTH" etc. */ +int +cl_syslogfac_str2int(const char *fname) +{ + int i; + + if(fname == NULL || strcmp("none", fname) == 0) { + return 0; + } + + for (i = 0; facilitynames[i].c_name != NULL; i++) { + if (strcmp(fname, facilitynames[i].c_name) == 0) { + return facilitynames[i].c_val; + } + } + return -1; +} + +/* Convert number "LOG_AUTH" to equivalent string "auth" etc. */ +const char * +cl_syslogfac_int2str(int fnum) +{ + int i; + + for (i = 0; facilitynames[i].c_name != NULL; i++) { + if (facilitynames[i].c_val == fnum) { + return facilitynames[i].c_name; + } + } + return NULL; +} diff --git a/lib/clplumbing/cl_uuid.c b/lib/clplumbing/cl_uuid.c new file mode 100644 index 0000000..d0dfcb6 --- /dev/null +++ b/lib/clplumbing/cl_uuid.c @@ -0,0 +1,180 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +/* + * uuid: wrapper declarations. + * + * heartbeat originally used "uuid" functionality by calling directly, + * and only, onto the "e2fsprogs" implementation. + * + * The run-time usages in the code have since been abstracted, funnelled + * through a thin, common interface layer: a Good Thing. + * + * Similarly, the compile-time usages of "include " are + * replaced, being funnelled through a reference to this header file. + * + * This header file interfaces onto the actual underlying implementation. + * In the case of the "e2fsprogs" implementation, it is simply a stepping + * stone onto "". As other implementations are accommodated, + * so their header requirements can be accommodated here. + * + * Copyright (C) 2004 David Lee + */ + +#if defined (HAVE_UUID_UUID_H) +/* + * Almost certainly the "e2fsprogs" implementation. + */ +# include + +/* elif defined(HAVE...UUID_OTHER_1 e.g. OSSP ...) */ + +/* elif defined(HAVE...UUID_OTHER_2...) */ +#else +# include +#endif + +#include +#include +#include + +void +cl_uuid_copy(cl_uuid_t* dst, cl_uuid_t* src) +{ + if (dst == NULL || src == NULL){ + cl_log(LOG_ERR, "cl_uuid_copy: " + "wrong argument %s is NULL", + dst == NULL?"dst":"src"); + assert(0); + } + + uuid_copy(dst->uuid, src->uuid); +} + +void +cl_uuid_clear(cl_uuid_t* uu) +{ + if (uu == NULL){ + cl_log(LOG_ERR, "cl_uuid_clear: " + "wrong argument (uu is NULL)"); + assert(0); + } + + uuid_clear(uu->uuid); + +} + +int +cl_uuid_compare(const cl_uuid_t* uu1, const cl_uuid_t* uu2) +{ + if (uu1 == NULL || uu2 == NULL){ + cl_log(LOG_ERR, "cl_uuid_compare: " + " wrong argument (%s is NULL)", + uu1 == NULL?"uu1":"uu2"); + assert(0); + } + + return uuid_compare(uu1->uuid, uu2->uuid); + +} + + + +void +cl_uuid_generate(cl_uuid_t* out) +{ + if (out == NULL){ + cl_log(LOG_ERR, "cl_uuid_generate: " + " wrong argument (out is NULL)"); + assert(0); + } + + uuid_generate(out->uuid); + +} + +int +cl_uuid_is_null(cl_uuid_t* uu) +{ + if (uu == NULL){ + cl_log(LOG_ERR, "cl_uuid_is_null: " + "wrong argument (uu is NULL)"); + assert(0); + } + + return uuid_is_null(uu->uuid); + +} + +int +cl_uuid_parse( char *in, cl_uuid_t* uu) +{ + if (in == NULL || uu == NULL){ + + cl_log(LOG_ERR, "cl_uuid_parse: " + "wrong argument (%s is NULL)", + in == NULL? "in":"uu"); + assert(0); + } + + return uuid_parse(in, uu->uuid); +} + + +void +cl_uuid_unparse(const cl_uuid_t* uu, char *out){ + + if (uu == NULL || out == NULL){ + cl_log(LOG_ERR, "cl_uuid_unparse: " + "wrong argument (%s is NULL)", + uu == NULL? "uu":"out"); + assert(0); + } + + uuid_unparse(uu->uuid, out); +} + + +guint +cl_uuid_g_hash(gconstpointer uuid_ptr) +{ + guint ret = 0U; + guint32 value32; + int index; + const unsigned char * uuid_char = uuid_ptr; + + /* It is probably not strictly necessary, but I'm trying to get the + * same hash result on all platforms. After all, the uuids are the + * same on every platform. + */ + + for (index = 0; index < sizeof(cl_uuid_t); index += sizeof(value32)) { + memcpy(&value32, uuid_char+index, sizeof (value32)); + ret += g_ntohl(value32); + } + return ret; +} +gboolean +cl_uuid_g_equal(gconstpointer uuid_ptr_a, gconstpointer uuid_ptr_b) +{ + return cl_uuid_compare(uuid_ptr_a, uuid_ptr_b) == 0; +} diff --git a/lib/clplumbing/coredumps.c b/lib/clplumbing/coredumps.c new file mode 100644 index 0000000..79da737 --- /dev/null +++ b/lib/clplumbing/coredumps.c @@ -0,0 +1,309 @@ +/* + * Basic Core dump control functions. + * + * Author: Alan Robertson + * + * Copyright (C) 2004 IBM Corporation + * + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_PRCTL_H +# include +#endif +#include +#include +#include +#include + +static char * coreroot = NULL; + +/* Set the root directory of our core directory hierarchy */ +int +cl_set_corerootdir(const char * dir) +{ + if (dir == NULL || *dir != '/') { + cl_perror("Invalid dir in cl_set_corerootdir() [%s]" + , dir ? dir : ""); + errno = EINVAL; + return -1; + } + if (coreroot != NULL) { + free(coreroot); + coreroot = NULL; + } + coreroot = strdup(dir); + if (coreroot == NULL) { + return -1; + } + return 0; +} + +/* + * Change directory to the directory our core file needs to go in + * Call after you establish the userid you're running under. + */ +int +cl_cdtocoredir(void) +{ + const char * dir = coreroot; + int rc; + struct passwd* pwent; + + if (dir == NULL) { + dir = HA_COREDIR; + } + if ((rc=chdir(dir)) < 0) { + int errsave = errno; + cl_perror("Cannot chdir to [%s]", dir); + errno = errsave; + return rc; + } + pwent = getpwuid(getuid()); + if (pwent == NULL) { + int errsave = errno; + cl_perror("Cannot get name for uid [%d]", getuid()); + errno = errsave; + return -1; + } + if ((rc=chdir(pwent->pw_name)) < 0) { + int errsave = errno; + cl_perror("Cannot chdir to [%s/%s]", dir, pwent->pw_name); + errno = errsave; + } + return rc; +} + +#define CHECKED_KERNEL_CORE_ENV "_PROC_SYS_CORE_CHECKED_" +#define PROC_SYS_KERNEL_CORE_PID "/proc/sys/kernel/core_uses_pid" +#define PROC_SYS_KERNEL_CORE_PAT "/proc/sys/kernel/core_pattern" + +static void cl_coredump_signal_handler(int nsig); + +/* + * core_uses_pid(): + * + * returns {-1, 0, 1} + * -1: not supported + * 0: supported and disabled + * 1: supported and enabled + */ +#define BUF_MAX 256 +static int +core_uses_pid(void) +{ + const char * uses_pid_pathnames[] = {PROC_SYS_KERNEL_CORE_PID}; + const char * corepats_pathnames[] = {PROC_SYS_KERNEL_CORE_PAT}; + const char * goodpats [] = {"%t", "%p"}; + int j; + + + for (j=0; j < DIMOF(corepats_pathnames); ++j) { + int fd; + char buf[BUF_MAX]; + int rc; + int k; + + if ((fd = open(corepats_pathnames[j], O_RDONLY)) < 0) { + continue; + } + + memset(buf, 0, BUF_MAX); + rc = read(fd, buf, BUF_MAX - 1); /* Ensure it is always NULL terminated */ + close(fd); + + for (k=0; rc > 0 && k < DIMOF(goodpats); ++k) { + if (strstr(buf, goodpats[k]) != NULL) { + return 1; + } + } + + break; + } + for (j=0; j < DIMOF(uses_pid_pathnames); ++j) { + int fd; + char buf[2]; + int rc; + if ((fd = open(uses_pid_pathnames[j], O_RDONLY)) < 0) { + continue; + } + rc = read(fd, buf, sizeof(buf)); + close(fd); + if (rc < 1) { + continue; + } + return (buf[0] == '1'); + } + setenv(CHECKED_KERNEL_CORE_ENV, "1", TRUE); + return -1; +} + +/* Enable/disable core dumps for ourselves and our child processes */ +int +cl_enable_coredumps(int doenable) +{ + int rc; + struct rlimit rlim; + + if ((rc = getrlimit(RLIMIT_CORE, &rlim)) < 0) { + int errsave = errno; + cl_perror("Cannot get current core limit value."); + errno = errsave; + return rc; + } + if (rlim.rlim_max == 0 && geteuid() == 0) { + rlim.rlim_max = RLIM_INFINITY; + } + + rlim.rlim_cur = (doenable ? rlim.rlim_max : 0); + + if (doenable && rlim.rlim_max == 0) { + cl_log(LOG_WARNING + , "Not possible to enable core dumps (rlim_max is 0)"); + } + + if ((rc = setrlimit(RLIMIT_CORE, &rlim)) < 0) { + int errsave = errno; + cl_perror("Unable to %s core dumps" + , doenable ? "enable" : "disable"); + errno = errsave; + return rc; + } + if (getenv(CHECKED_KERNEL_CORE_ENV) == NULL + && core_uses_pid() == 0) { + cl_log(LOG_WARNING + , "Core dumps could be lost if multiple dumps occur."); + cl_log(LOG_WARNING + , "Consider setting non-default value in %s" + " (or equivalent) for maximum supportability", PROC_SYS_KERNEL_CORE_PAT); + cl_log(LOG_WARNING + , "Consider setting %s (or equivalent) to" + " 1 for maximum supportability", PROC_SYS_KERNEL_CORE_PID); + } + return 0; +} + +/* + * SIGQUIT 3 Core Quit from keyboard + * SIGILL 4 Core Illegal Instruction + * SIGABRT 6 Core Abort signal from abort(3) + * SIGFPE 8 Core Floating point exception + * SIGSEGV 11 Core Invalid memory reference + * SIGBUS 10,7,10 Core Bus error (bad memory access) + * SIGSYS 2,-,12 Core Bad argument to routine (SVID) + * SIGTRAP 5 Core Trace/breakpoint trap + * SIGXCPU 24,24,30 Core CPU time limit exceeded (4.2 BSD) + * SIGXFSZ 25,25,31 Core File size limit exceeded (4.2 BSD) + */ + +/* + * This function exists to allow security-sensitive programs + * to safely take core dumps. Such programs can't can't call + * cl_untaint_coredumps() alone - because it might cause a + * leak of confidential information - as information which should + * only be known by the "high-privilege" user id will be written + * into a core dump which is readable by the "low-privilege" user id. + * This is a bad thing. + * + * This function causes this program to call a special signal handler + * on receipt of any core dumping signal. This handler then does + * the following four things on receipt of a core dumping signal: + * + * 1) Set privileges to "maximum" on receipt of a signal + * 2) "untaint" themselves with regard to core dumping + * 3) set SIG_DFLT for the received signal + * 4) Kill themselves with the received core-dumping signal + * + * Any process *could* do this to get core dumps, but if your stack + * is screwed up, then the signal handler might not work. + * If you're core dumping because of a stack overflow, it certainly won't work. + * + * On the other hand, this function may work on some OSes that don't support + * prctl(2). This is an untested theory at this time... + */ +void +cl_set_all_coredump_signal_handlers(void) +{ + static const int coresigs [] = {SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV +#ifdef SIGBUS +, SIGBUS +#endif +#ifdef SIGSYS +, SIGSYS +#endif +#ifdef SIGTRAP +, SIGTRAP +#endif +#ifdef SIGXCPU +, SIGXCPU +#endif +#ifdef SIGXFSZ +, SIGXFSZ +#endif +}; + int j; + + for (j=0; j < DIMOF(coresigs); ++j) { + cl_set_coredump_signal_handler(coresigs[j]); + } +} + +/* + * See note above about why using this function directly is sometimes + * a bad idea, and you might need to use cl_set_all_coredump_signal_handlers() + * instead. + */ +void +cl_untaint_coredumps(void) +{ +#if defined(PR_SET_DUMPABLE) + prctl(PR_SET_DUMPABLE, (unsigned long)TRUE, 0UL, 0UL, 0UL); +#endif +} +static void +cl_coredump_signal_handler(int nsig) +{ + return_to_orig_privs(); + if (geteuid() == 0) { + /* Put ALL privileges back to root... */ + if (setuid(0) < 0) { + cl_perror("cl_coredump_signal_handler: unable to setuid(0)"); + } + } + cl_untaint_coredumps(); /* Do the best we know how to do... */ + CL_SIGNAL(nsig, SIG_DFL); + kill(getpid(), nsig); +} + +void +cl_set_coredump_signal_handler(int nsig) +{ + CL_SIGNAL(nsig, cl_coredump_signal_handler); +} diff --git a/lib/clplumbing/cpulimits.c b/lib/clplumbing/cpulimits.c new file mode 100644 index 0000000..4c03f23 --- /dev/null +++ b/lib/clplumbing/cpulimits.c @@ -0,0 +1,219 @@ +/* + * Functions to put dynamic limits on CPU consumption. + * + * Copyright (C) 2003 IBM Corporation + * + * Author: + * + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + ************************************************************************** + * + * This allows us to better catch runaway realtime processes that + * might otherwise hang the whole system (if they're POSIX realtime + * processes). + * + * We do this by getting a "lease" on CPU time, and then extending + * the lease every so often as real time elapses. Since we only + * extend the lease by a bounded amount computed on the basis of an + * upper bound of how much CPU the code is "expected" to consume during + * the lease interval, this means that if we go into an infinite + * loop, it is highly probable that this will be detected and our + * process will be terminated by the operating system with a SIGXCPU. + * + * If you want to handle this signal, then fine... Do so... + * + * If not, the default is to terminate the process and produce a core + * dump. This is a great default for debugging... + * + * + * The process is basically this: + * - Set the CPU percentage limit with cl_cpu_limit_setpercent() + * according to what you expect the CPU percentage to top out at + * measured over an interval at >= 60 seconds + * - Call cl_cpu_limit_ms_interval() to figure out how often to update + * the CPU limit (it returns milliseconds) + * - At least as often as indicated above, call cl_cpu_limit_update() + * to update our current CPU limit. + * + * These limits are approximate, so be a little conservative. + * If you've gone into an infinite loop, it'll likely get caught ;-) + * + * As of this writing, this code will never set the soft CPU limit less + * than four seconds, or greater than 60 seconds. + * + */ +#include +#include +#include +#include +#include +#include +#include + +static longclock_t nexttimetoupdate; + +/* How long between checking out CPU usage? */ +static int cpuinterval_ms = 0; + +/* How much cpu (in seconds) allowed at each check interval? */ +static int cpusecs; + +#define ROUND(foo) ((int)((foo)+0.5)) + + +/* + * Update our current CPU limit (via setrlimit) according to our + * current resource consumption, and our current cpu % limit + * + * We only set the soft CPU limit, and do not change the maximum + * (hard) CPU limit, but we respect it if it's already set. + * + * As a result, this code can be used by privileged and non-privileged + * processes. + */ + +static int +update_cpu_interval(void) +{ + struct rusage ru; + struct rlimit rlim; + unsigned long timesecs; + unsigned long microsec; + + /* Compute how much CPU we've used so far... */ + + getrusage(RUSAGE_SELF, &ru); + timesecs = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec; + microsec = ru.ru_utime.tv_usec + ru.ru_stime.tv_usec; + + /* Round up to the next higher second */ + if (microsec > 1000000) { + timesecs += 2; + }else{ + timesecs += 1; + } + + /* Compute our next CPU limit */ + timesecs += cpusecs; + + /* Figure out when we next need to update our CPU limit */ + nexttimetoupdate = add_longclock(time_longclock() + , msto_longclock(cpuinterval_ms)); + + getrlimit(RLIMIT_CPU, &rlim); + + /* Make sure we don't exceed the hard CPU limit (if set) */ + if (rlim.rlim_max != RLIM_INFINITY && timesecs > rlim.rlim_max) { + timesecs = rlim.rlim_max; + } +#if 0 + cl_log(LOG_DEBUG + , "Setting max CPU limit to %ld seconds", timesecs); +#endif + + /* Update the OS-level soft CPU limit */ + rlim.rlim_cur = timesecs; + return setrlimit(RLIMIT_CPU, &rlim); +} + +#define MININTERVAL 60 /* seconds */ + +int +cl_cpu_limit_setpercent(int ipercent) +{ + float percent; + int interval; + + if (ipercent > 99) { + ipercent = 99; + } + if (ipercent < 1) { + ipercent = 1; + } + percent = ipercent; + percent /= (float)100; + + interval= MININTERVAL; + + /* + * Compute how much CPU we will allow to be used + * for each check interval. + * + * Rules: + * - we won't require checking more often than + * every 60 seconds + * - we won't limit ourselves to less than + * 4 seconds of CPU per checking interval + */ + for (;;) { + cpusecs = ROUND((float)interval*percent); + if (cpusecs >= 4) { + break; + } + interval *= 2; + } + + /* + * Now compute how long to go between updates to our CPU limit + * from the perspective of the OS (via setrlimit(2)). + * + * We do the computation this way because the CPU limit + * can only be set to the nearest second, but timers can + * generally be set more accurately. + */ + cpuinterval_ms = (int)(((float)cpusecs / percent)*1000.0); + + cl_log(LOG_DEBUG + , "Limiting CPU: %d CPU seconds every %d milliseconds" + , cpusecs, cpuinterval_ms); + + return update_cpu_interval(); +} + +int +cl_cpu_limit_ms_interval(void) +{ + return cpuinterval_ms; +} + +int +cl_cpu_limit_update(void) +{ + longclock_t now = time_longclock(); + long msleft; + + if (cpuinterval_ms <= 0) { + return 0; + } + if (cmp_longclock(now, nexttimetoupdate) > 0) { + return update_cpu_interval(); + } + msleft = longclockto_ms(sub_longclock(nexttimetoupdate, now)); + if (msleft < 500) { + return update_cpu_interval(); + } + return 0; +} +int +cl_cpu_limit_disable(void) +{ + struct rlimit rlim; + getrlimit(RLIMIT_CPU, &rlim); + rlim.rlim_cur = rlim.rlim_max; + return setrlimit(RLIMIT_CPU, &rlim); +} diff --git a/lib/clplumbing/ipcsocket.c b/lib/clplumbing/ipcsocket.c new file mode 100644 index 0000000..14c3504 --- /dev/null +++ b/lib/clplumbing/ipcsocket.c @@ -0,0 +1,2767 @@ +/* + * ipcsocket unix domain socket implementation of IPC abstraction. + * + * Copyright (c) 2002 Xiaoxiang Liu + * + * Stream support (c) 2004,2006 David Lee + * Note: many of the variable/function names "*socket*" should be + * interpreted as having a more generic "ipc-channel-type" meaning. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include +#include +#include +#include + +#include +/* avoid including cib.h - used in gshi's "late message" code to avoid + * printing insanely large messages + */ +#define F_CIB_CALLDATA "cib_calldata" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_FILIO_H +# include +#endif +#ifdef HAVE_SYS_SYSLIMITS_H +# include +#endif +#ifdef HAVE_SYS_CRED_H +# include +#endif +#ifdef HAVE_SYS_UCRED_H +# include +#endif + +/* For 'getpeerucred()' (Solaris 10 upwards) */ +#ifdef HAVE_UCRED_H +# include +#endif + +#ifdef HAVE_SYS_SOCKET_H +# include +#endif + +/* + * Normally use "socket" code. But on some OSes alternatives may be + * preferred (or necessary). + */ +#define HB_IPC_SOCKET 1 +#define HB_IPC_STREAM 2 +/* #define HB_IPC_ANOTHER 3 */ + +#ifndef HB_IPC_METHOD +# if defined(SO_PEERCRED) || defined(HAVE_GETPEEREID) \ + || defined(SCM_CREDS) || defined(HAVE_GETPEERUCRED) +# define HB_IPC_METHOD HB_IPC_SOCKET +# elif defined(HAVE_STROPTS_H) +# define HB_IPC_METHOD HB_IPC_STREAM +# else +# error. Surely we have sockets or streams... +# endif +#endif + +#if HB_IPC_METHOD == HB_IPC_SOCKET +# include +# include +# include +#elif HB_IPC_METHOD == HB_IPC_STREAM +# include +#else +# error "IPC type invalid" +#endif + +#include +#include +#include +#include + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX 108 +#endif + +#if HB_IPC_METHOD == HB_IPC_SOCKET + +# define MAX_LISTEN_NUM 128 + +# ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +# endif + +# ifndef AF_LOCAL +# define AF_LOCAL AF_UNIX +# endif + +#endif /* HB_IPC_METHOD */ + +/*********************************************************************** + * + * Determine the IPC authentication scheme... More machine dependent than + * we'd like, but don't know any better way... + * + ***********************************************************************/ +#ifdef SO_PEERCRED +# define USE_SO_PEERCRED +#elif HAVE_GETPEEREID +# define USE_GETPEEREID +#elif defined(SCM_CREDS) +# define USE_SCM_CREDS +#elif HAVE_GETPEERUCRED /* e.g. Solaris 10 upwards */ +# define USE_GETPEERUCRED +#elif HB_IPC_METHOD == HB_IPC_STREAM +# define USE_STREAM_CREDS +#else +# define USE_DUMMY_CREDS +/* This will make it compile, but attempts to authenticate + * will fail. This is a stopgap measure ;-) + */ +#endif + +#if HB_IPC_METHOD == HB_IPC_SOCKET + +# ifdef USE_BINDSTAT_CREDS +# ifndef SUN_LEN +# define SUN_LEN(ptr) ((size_t) (offsetof (sockaddr_un, sun_path) + strlen ((ptr)->sun_path)) +# endif +# endif + +#endif /* HB_IPC_METHOD */ + +/* wait connection private data. */ +struct SOCKET_WAIT_CONN_PRIVATE{ + /* the path name wich the connection will be built on. */ + char path_name[UNIX_PATH_MAX]; +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* the domain socket. */ + int s; +#elif HB_IPC_METHOD == HB_IPC_STREAM + /* the streams pipe */ + int pipefds[2]; +#endif +}; + +/* channel private data. */ +struct SOCKET_CH_PRIVATE{ + /* the path name wich the connection will be built on. */ + char path_name[UNIX_PATH_MAX]; + /* the domain socket. */ + int s; + /* the size of expecting data for below buffered message buf_msg */ + int remaining_data; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* The address of our peer - used by USE_BINDSTAT_CREDS version of + * socket_verify_auth() + */ + struct sockaddr_un *peer_addr; +#elif HB_IPC_METHOD == HB_IPC_STREAM + uid_t farside_uid; + gid_t farside_gid; +#endif + + /* the buf used to save unfinished message */ + struct IPC_MESSAGE *buf_msg; +}; + +struct IPC_Stats { + long nsent; + long noutqueued; + long send_count; + long nreceived; + long ninqueued; + long recv_count; + int last_recv_errno; + int last_recv_rc; + int last_send_errno; + int last_send_rc; +}; + +static struct IPC_Stats SocketIPCStats = {0, 0, 0, 0}; +extern int debug_level; + +/* unix domain socket implementations of IPC functions. */ + +static int socket_resume_io(struct IPC_CHANNEL *ch); + +static struct IPC_MESSAGE* socket_message_new(struct IPC_CHANNEL*ch +, int msg_len); + +struct IPC_WAIT_CONNECTION *socket_wait_conn_new(GHashTable* ch_attrs); + +/* *** FIXME: This is also declared in 'ocf_ipc.c'. */ +struct IPC_CHANNEL* socket_client_channel_new(GHashTable *attrs); + +static struct IPC_CHANNEL* socket_server_channel_new(int sockfd); + +static struct IPC_CHANNEL * channel_new(int sockfd, int conntype, const char *pathname); +static int client_channel_new_auth(int sockfd); +static int verify_creds(struct IPC_AUTH *auth_info, uid_t uid, gid_t gid); + +typedef void (*DelProc)(IPC_Message*); + +static struct IPC_MESSAGE * ipcmsg_new(struct IPC_CHANNEL* ch, + const void* data, int len, void* private, DelProc d); + +static pid_t socket_get_farside_pid(int sockfd); + +extern int (*ipc_pollfunc_ptr)(struct pollfd *, nfds_t, int); + +static int socket_resume_io_read(struct IPC_CHANNEL *ch, int*, gboolean read1anyway); + +static struct IPC_OPS socket_ops; +static gboolean ipc_time_debug_flag = TRUE; + +void +set_ipc_time_debug_flag(gboolean flag) +{ + ipc_time_debug_flag = flag; +} + +#ifdef IPC_TIME_DEBUG + +extern struct ha_msg* wirefmt2msg(const char* s, size_t length, int flag); +void cl_log_message (int log_level, const struct ha_msg *m); +int timediff(longclock_t t1, longclock_t t2); +void ha_msg_del(struct ha_msg* msg); +void ipc_time_debug(IPC_Channel* ch, IPC_Message* ipcmsg, int whichpos); + +#define SET_ENQUEUE_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->enqueue_time, &t, sizeof(longclock_t)) +#define SET_SEND_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->send_time, &t, sizeof(longclock_t)) +#define SET_RECV_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->recv_time, &t, sizeof(longclock_t)) +#define SET_DEQUEUE_TIME(x,t) memcpy(&((struct SOCKET_MSG_HEAD*)x->msg_buf)->dequeue_time, &t, sizeof(longclock_t)) + +static +longclock_t +get_enqueue_time(IPC_Message *ipcmsg) +{ + longclock_t t; + + memcpy(&t, + &(((struct SOCKET_MSG_HEAD *)ipcmsg->msg_buf)->enqueue_time), + sizeof(longclock_t)); + + return t; +} + +int +timediff(longclock_t t1, longclock_t t2) +{ + longclock_t remain; + + remain = sub_longclock(t1, t2); + + return longclockto_ms(remain); +} + +void +ipc_time_debug(IPC_Channel* ch, IPC_Message* ipcmsg, int whichpos) +{ + int msdiff = 0; + longclock_t lnow = time_longclock(); + char positions[4][16]={ + "enqueue", + "send", + "recv", + "dequeue"}; + + if (ipc_time_debug_flag == FALSE) { + return ; + } + + if (ipcmsg->msg_body == NULL + || ipcmsg->msg_buf == NULL) { + cl_log(LOG_ERR, "msg_body =%p, msg_bu=%p", + ipcmsg->msg_body, ipcmsg->msg_buf); + abort(); + return; + } + + switch(whichpos) { + case MSGPOS_ENQUEUE: + SET_ENQUEUE_TIME(ipcmsg, lnow); + break; + case MSGPOS_SEND: + SET_SEND_TIME(ipcmsg, lnow); + goto checktime; + case MSGPOS_RECV: + SET_RECV_TIME(ipcmsg, lnow); + goto checktime; + case MSGPOS_DEQUEUE: + SET_DEQUEUE_TIME(ipcmsg, lnow); + + checktime: + msdiff = timediff(lnow, get_enqueue_time(ipcmsg)); + if (msdiff > MAXIPCTIME) { + struct ha_msg* hamsg = NULL; + cl_log(LOG_WARNING, + " message delayed from enqueue to %s %d ms " + "(enqueue-time=%lu, peer pid=%d) ", + positions[whichpos], + msdiff, + longclockto_ms(get_enqueue_time(ipcmsg)), + ch->farside_pid); + + (void)hamsg; +#if 0 + hamsg = wirefmt2msg(ipcmsg->msg_body, ipcmsg->msg_len, 0); + if (hamsg != NULL) { + struct ha_msg *crm_data = NULL; + crm_data = cl_get_struct( + hamsg, F_CRM_DATA); + + if(crm_data == NULL) { + crm_data = cl_get_struct( + hamsg, F_CIB_CALLDATA); + } + if(crm_data != NULL) { + cl_msg_remove_value( + hamsg, crm_data); + } + + cl_log_message(LOG_DEBUG, hamsg); + ha_msg_del(hamsg); + } else { + if (!ipcmsg) { + cl_log(LOG_ERR, + "IPC msg 0x%lx is unallocated" + , (gulong)ipcmsg); + return; + } + if (!ipcmsg->msg_body) { + cl_log(LOG_ERR, + "IPC msg body 0x%lx is unallocated" + , (gulong)ipcmsg->msg_body); + return; + } + } +#endif + + } + break; + default: + cl_log(LOG_ERR, "wrong position value in IPC:%d", whichpos); + return; + } +} +#endif + +void dump_ipc_info(const IPC_Channel* chan); + +#undef AUDIT_CHANNELS + +#ifndef AUDIT_CHANNELS +# define CHANAUDIT(ch) /*NOTHING */ +#else +# define CHANAUDIT(ch) socket_chan_audit(ch) +# define MAXPID 65535 + +static void +socket_chan_audit(const struct IPC_CHANNEL* ch) +{ + int badch = FALSE; + + struct SOCKET_CH_PRIVATE *chp; + struct stat b; + + if ((chp = ch->ch_private) == NULL) { + cl_log(LOG_CRIT, "Bad ch_private"); + badch = TRUE; + } + if (ch->ops != &socket_ops) { + cl_log(LOG_CRIT, "Bad socket_ops"); + badch = TRUE; + } + if (ch->ch_status == IPC_DISCONNECT) { + return; + } + if (!IPC_ISRCONN(ch)) { + cl_log(LOG_CRIT, "Bad ch_status [%d]", ch->ch_status); + badch = TRUE; + } + if (ch->farside_pid < 0 || ch->farside_pid > MAXPID) { + cl_log(LOG_CRIT, "Bad farside_pid"); + badch = TRUE; + } + if (fstat(chp->s, &b) < 0) { + badch = TRUE; + } else if ((b.st_mode & S_IFMT) != S_IFSOCK) { + cl_log(LOG_CRIT, "channel @ 0x%lx: not a socket" + , (unsigned long)ch); + badch = TRUE; + } + if (chp->remaining_data < 0) { + cl_log(LOG_CRIT, "Negative remaining_data"); + badch = TRUE; + } + if (chp->remaining_data < 0 || chp->remaining_data > MAXMSG) { + cl_log(LOG_CRIT, "Excessive/bad remaining_data"); + badch = TRUE; + } + if (chp->remaining_data && chp->buf_msg == NULL) { + cl_log(LOG_CRIT + , "inconsistent remaining_data [%ld]/buf_msg[0x%lx]" + , (long)chp->remaining_data, (unsigned long)chp->buf_msg); + badch = TRUE; + } + if (chp->remaining_data == 0 && chp->buf_msg != NULL) { + cl_log(LOG_CRIT + , "inconsistent remaining_data [%ld]/buf_msg[0x%lx] (2)" + , (long)chp->remaining_data, (unsigned long)chp->buf_msg); + badch = TRUE; + } + if (ch->send_queue == NULL || ch->recv_queue == NULL) { + cl_log(LOG_CRIT, "bad send/recv queue"); + badch = TRUE; + } + if (ch->recv_queue->current_qlen < 0 + || ch->recv_queue->current_qlen > ch->recv_queue->max_qlen) { + cl_log(LOG_CRIT, "bad recv queue"); + badch = TRUE; + } + if (ch->send_queue->current_qlen < 0 + || ch->send_queue->current_qlen > ch->send_queue->max_qlen) { + cl_log(LOG_CRIT, "bad send_queue"); + badch = TRUE; + } + if (badch) { + cl_log(LOG_CRIT, "Bad channel @ 0x%lx", (unsigned long)ch); + dump_ipc_info(ch); + abort(); + } +} +#endif + +#ifdef CHEAT_CHECKS +long SeqNums[32]; + +static long +cheat_get_sequence(IPC_Message* msg) +{ + const char header [] = "String-"; + size_t header_len = sizeof(header)-1; + char * body; + + if (msg == NULL || msg->msg_len < sizeof(header) + || msg->msg_len > sizeof(header) + 10 + || strncmp(msg->msg_body, header, header_len) != 0) { + return -1L; + } + body = msg->msg_body; + return atol(body+header_len); +} +static char SavedReadBody[32]; +static char SavedReceivedBody[32]; +static char SavedQueuedBody[32]; +static char SavedSentBody[32]; +#ifndef MIN +# define MIN(a,b) (a < b ? a : b) +#endif + +static void +save_body(struct IPC_MESSAGE *msg, char * savearea, size_t length) +{ + int mlen = strnlen(msg->msg_body, MIN(length, msg->msg_len)); + memcpy(savearea, msg->msg_body, mlen); + savearea[mlen] = EOS; +} + +static void +audit_readmsgq_msg(gpointer msg, gpointer user_data) +{ + long cheatseq = cheat_get_sequence(msg); + + if (cheatseq < SeqNums[1] || cheatseq > SeqNums[2]) { + cl_log(LOG_ERR + , "Read Q Message %ld not in range [%ld:%ld]" + , cheatseq, SeqNums[1], SeqNums[2]); + } +} + +static void +saveandcheck(struct IPC_CHANNEL * ch, struct IPC_MESSAGE* msg, char * savearea +, size_t savesize, long* lastseq, const char * text) +{ + long cheatseq = cheat_get_sequence(msg); + + save_body(msg, savearea, savesize); + if (*lastseq != 0 ) { + if (cheatseq != *lastseq +1) { + int j; + cl_log(LOG_ERR + , "%s packets out of sequence! %ld versus %ld [pid %d]" + , text, cheatseq, *lastseq, (int)getpid()); + dump_ipc_info(ch); + for (j=0; j < 4; ++j) { + cl_log(LOG_DEBUG + , "SeqNums[%d] = %ld" + , j, SeqNums[j]); + } + cl_log(LOG_ERR + , "SocketIPCStats.nsent = %ld" + , SocketIPCStats.nsent); + cl_log(LOG_ERR + , "SocketIPCStats.noutqueued = %ld" + , SocketIPCStats.noutqueued); + cl_log(LOG_ERR + , "SocketIPCStats.nreceived = %ld" + , SocketIPCStats.nreceived); + cl_log(LOG_ERR + , "SocketIPCStats.ninqueued = %ld" + , SocketIPCStats.ninqueued); + } + + } + g_list_foreach(ch->recv_queue->queue, audit_readmsgq_msg, NULL); + if (cheatseq > 0) { + *lastseq = cheatseq; + } +} + +# define CHECKFOO(which, ch, msg, area, text) { \ + saveandcheck(ch,msg,area,sizeof(area),SeqNums+which,text); \ + } +#else +# define CHECKFOO(which, ch, msg, area, text) /* Nothing */ +#endif + +static void +dump_msg(struct IPC_MESSAGE *msg, const char * label) +{ +#ifdef CHEAT_CHECKS + cl_log(LOG_DEBUG, "%s packet (length %d) [%s] %ld pid %d" + , label, (int)msg->msg_len, (char*)msg->msg_body + , cheat_get_sequence(msg), (int)getpid()); +#else + cl_log(LOG_DEBUG, "%s length %d [%s] pid %d" + , label, (int)msg->msg_len, (char*)msg->msg_body + , (int)getpid()); +#endif +} + +static void +dump_msgq_msg(gpointer data, gpointer user_data) +{ + dump_msg(data, user_data); +} + +void +dump_ipc_info(const IPC_Channel* chan) +{ + char squeue[] = "Send queue"; + char rqueue[] = "Receive queue"; +#ifdef CHEAT_CHECKS + cl_log(LOG_DEBUG, "Saved Last Body read[%s]", SavedReadBody); + cl_log(LOG_DEBUG, "Saved Last Body received[%s]", SavedReceivedBody); + cl_log(LOG_DEBUG, "Saved Last Body Queued[%s]", SavedQueuedBody); + cl_log(LOG_DEBUG, "Saved Last Body Sent[%s]", SavedSentBody); +#endif + g_list_foreach(chan->send_queue->queue, dump_msgq_msg, squeue); + g_list_foreach(chan->recv_queue->queue, dump_msgq_msg, rqueue); + CHANAUDIT(chan); +} + +/* destroy socket wait channel */ +static void +socket_destroy_wait_conn(struct IPC_WAIT_CONNECTION * wait_conn) +{ + struct SOCKET_WAIT_CONN_PRIVATE * wc = wait_conn->ch_private; + + if (wc != NULL) { +#if HB_IPC_METHOD == HB_IPC_SOCKET + if (wc->s >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing socket %d" + , __FUNCTION__, wc->s); + } + close(wc->s); + cl_poll_ignore(wc->s); + unlink(wc->path_name); + wc->s = -1; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + cl_poll_ignore(wc->pipefds[0]); + if (wc->pipefds[0] >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing pipe[0] %d" + , __FUNCTION__, wc->pipefds[0]); + } + wc->pipefds[0] = -1; + } + if (wc->pipefds[1] >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing pipe[1] %d" + , __FUNCTION__, wc->pipefds[1]); + } + wc->pipefds[0] = -1; + } + unlink(wc->path_name); +#endif + g_free(wc); + } + g_free((void*) wait_conn); +} + +/* return a fd which can be listened on for new connections. */ +static int +socket_wait_selectfd(struct IPC_WAIT_CONNECTION *wait_conn) +{ + struct SOCKET_WAIT_CONN_PRIVATE * wc = wait_conn->ch_private; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + return (wc == NULL ? -1 : wc->s); +#elif HB_IPC_METHOD == HB_IPC_STREAM + return (wc == NULL ? -1 : wc->pipefds[0]); +#endif +} + +/* socket accept connection. */ +static struct IPC_CHANNEL* +socket_accept_connection(struct IPC_WAIT_CONNECTION * wait_conn +, struct IPC_AUTH *auth_info) +{ + struct IPC_CHANNEL * ch = NULL; + int s; + int new_sock; + struct SOCKET_WAIT_CONN_PRIVATE* conn_private; + struct SOCKET_CH_PRIVATE * ch_private ; + int auth_result = IPC_FAIL; + int saveerrno=errno; + gboolean was_error = FALSE; +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* make peer_addr a pointer so it can be used by the + * USE_BINDSTAT_CREDS implementation of socket_verify_auth() + */ + struct sockaddr_un * peer_addr = NULL; + socklen_t sin_size; +#elif HB_IPC_METHOD == HB_IPC_STREAM + struct strrecvfd strrecvfd; +#endif + + /* get select fd */ + + s = wait_conn->ops->get_select_fd(wait_conn); + if (s < 0) { + cl_log(LOG_ERR, "get_select_fd: invalid fd"); + return NULL; + } + + /* Get client connection. */ +#if HB_IPC_METHOD == HB_IPC_SOCKET + peer_addr = g_new(struct sockaddr_un, 1); + *peer_addr->sun_path = '\0'; + sin_size = sizeof(struct sockaddr_un); + new_sock = accept(s, (struct sockaddr *)peer_addr, &sin_size); +#elif HB_IPC_METHOD == HB_IPC_STREAM + if (ioctl(s, I_RECVFD, &strrecvfd) == -1) { + new_sock = -1; + } + else { + new_sock = strrecvfd.fd; + } +#endif + saveerrno=errno; + if (new_sock == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + cl_perror("socket_accept_connection: accept(sock=%d)" + , s); + } + was_error = TRUE; + + } else { + if ((ch = socket_server_channel_new(new_sock)) == NULL) { + cl_log(LOG_ERR + , "socket_accept_connection:" + " Can't create new channel"); + was_error = TRUE; + } else { + conn_private=(struct SOCKET_WAIT_CONN_PRIVATE*) + ( wait_conn->ch_private); + ch_private = (struct SOCKET_CH_PRIVATE *)(ch->ch_private); + strncpy(ch_private->path_name,conn_private->path_name + , sizeof(conn_private->path_name)); + +#if HB_IPC_METHOD == HB_IPC_SOCKET + ch_private->peer_addr = peer_addr; +#elif HB_IPC_METHOD == HB_IPC_STREAM + ch_private->farside_uid = strrecvfd.uid; + ch_private->farside_gid = strrecvfd.gid; +#endif + } + } + + /* Verify the client authorization information. */ + if(was_error == FALSE) { + auth_result = ch->ops->verify_auth(ch, auth_info); + if (auth_result == IPC_OK) { + ch->ch_status = IPC_CONNECT; + ch->farside_pid = socket_get_farside_pid(new_sock); + return ch; + } + saveerrno=errno; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + g_free(peer_addr); + peer_addr = NULL; +#endif + errno=saveerrno; + return NULL; +} + +/* + * Called by socket_destroy(). Disconnect the connection + * and set ch_status to IPC_DISCONNECT. + * + * parameters : + * ch (IN) the pointer to the channel. + * + * return values : + * IPC_OK the connection is disconnected successfully. + * IPC_FAIL operation fails. +*/ + +static int +socket_disconnect(struct IPC_CHANNEL* ch) +{ + struct SOCKET_CH_PRIVATE* conn_info; + + conn_info = (struct SOCKET_CH_PRIVATE*) ch->ch_private; + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s(sock=%d, ch=0x%lx){" + , __FUNCTION__ + , conn_info->s, (unsigned long)ch); + } +#if 0 + if (ch->ch_status != IPC_DISCONNECT) { + cl_log(LOG_INFO, "forced disconnect for fd %d", conn_info->s); + } +#endif + if (ch->ch_status == IPC_CONNECT) { + socket_resume_io(ch); + } + + if (conn_info->s >= 0) { + if (debug_level > 1) { + cl_log(LOG_DEBUG + , "%s: closing socket %d" + , __FUNCTION__, conn_info->s); + } + close(conn_info->s); + cl_poll_ignore(conn_info->s); + conn_info->s = -1; + } + ch->ch_status = IPC_DISCONNECT; + if (debug_level > 1) { + cl_log(LOG_DEBUG, "}/*%s(sock=%d, ch=0x%lx)*/" + , __FUNCTION__, conn_info->s, (unsigned long)ch); + } + return IPC_OK; +} + +/* + * destroy a ipc queue and clean all memory space assigned to this queue. + * parameters: + * q (IN) the pointer to the queue which should be destroied. + * + * FIXME: This function does not free up messages that might + * be in the queue. + */ + +static void +socket_destroy_queue(struct IPC_QUEUE * q) +{ + g_list_free(q->queue); + + g_free((void *) q); +} + +static void +socket_destroy_channel(struct IPC_CHANNEL * ch) +{ + --ch->refcount; + if (ch->refcount > 0) { + return; + } + if (ch->ch_status == IPC_CONNECT) { + socket_resume_io(ch); + } + if (debug_level > 1) { + cl_log(LOG_DEBUG, "socket_destroy(ch=0x%lx){" + , (unsigned long)ch); + } + socket_disconnect(ch); + socket_destroy_queue(ch->send_queue); + socket_destroy_queue(ch->recv_queue); + + if (ch->pool) { + ipc_bufpool_unref(ch->pool); + } + + if (ch->ch_private != NULL) { +#if HB_IPC_METHOD == HB_IPC_SOCKET + struct SOCKET_CH_PRIVATE *priv = (struct SOCKET_CH_PRIVATE *) + ch->ch_private; + if(priv->peer_addr != NULL) { + if (*priv->peer_addr->sun_path) { + unlink(priv->peer_addr->sun_path); + } + g_free((void*)(priv->peer_addr)); + } +#endif + g_free((void*)(ch->ch_private)); + } + memset(ch, 0xff, sizeof(*ch)); + g_free((void*)ch); + if (debug_level > 1) { + cl_log(LOG_DEBUG, "}/*socket_destroy(ch=0x%lx)*/" + , (unsigned long)ch); + } +} + +static int +socket_check_disc_pending(struct IPC_CHANNEL* ch) +{ + int rc; + struct pollfd sockpoll; + + if (ch->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR, "check_disc_pending() already disconnected"); + return IPC_BROKEN; + } + if (ch->recv_queue->current_qlen > 0) { + return IPC_OK; + } + sockpoll.fd = ch->ops->get_recv_select_fd(ch); + sockpoll.events = POLLIN; + + rc = ipc_pollfunc_ptr(&sockpoll, 1, 0); + + if (rc < 0) { + cl_log(LOG_INFO + , "socket_check_disc_pending() bad poll call"); + ch->ch_status = IPC_DISCONNECT; + return IPC_BROKEN; + } + + if (sockpoll.revents & POLLHUP) { + if (sockpoll.revents & POLLIN) { + ch->ch_status = IPC_DISC_PENDING; + } else { +#if 1 + cl_log(LOG_INFO, "HUP without input"); +#endif + ch->ch_status = IPC_DISCONNECT; + return IPC_BROKEN; + } + } + if (sockpoll.revents & POLLIN) { + int dummy; + socket_resume_io_read(ch, &dummy, FALSE); + } + return IPC_OK; +} + +static int +socket_initiate_connection(struct IPC_CHANNEL * ch) +{ + struct SOCKET_CH_PRIVATE* conn_info; +#if HB_IPC_METHOD == HB_IPC_SOCKET + struct sockaddr_un peer_addr; /* connector's address information */ +#elif HB_IPC_METHOD == HB_IPC_STREAM +#endif + + conn_info = (struct SOCKET_CH_PRIVATE*) ch->ch_private; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* Prepare the socket */ + memset(&peer_addr, 0, sizeof(peer_addr)); + peer_addr.sun_family = AF_LOCAL; /* host byte order */ + + if (strlen(conn_info->path_name) >= sizeof(peer_addr.sun_path)) { + return IPC_FAIL; + } + strncpy(peer_addr.sun_path, conn_info->path_name, sizeof(peer_addr.sun_path)); + + /* Send connection request */ + if (connect(conn_info->s, (struct sockaddr *)&peer_addr + , sizeof(struct sockaddr_un)) == -1) { + return IPC_FAIL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + +#endif + + ch->ch_status = IPC_CONNECT; + ch->farside_pid = socket_get_farside_pid(conn_info->s); + return IPC_OK; +} + +static void +socket_set_high_flow_callback(IPC_Channel* ch, + flow_callback_t callback, + void* userdata) { + ch->high_flow_callback = callback; + ch->high_flow_userdata = userdata; +} + +static void +socket_set_low_flow_callback(IPC_Channel* ch, + flow_callback_t callback, + void* userdata) { + ch->low_flow_callback = callback; + ch->low_flow_userdata = userdata; +} + +static void +socket_check_flow_control(struct IPC_CHANNEL* ch, + int orig_qlen, + int curr_qlen) +{ + if (!IPC_ISRCONN(ch)) { + return; + } + + if (curr_qlen >= ch->high_flow_mark + && ch->high_flow_callback) { + ch->high_flow_callback(ch, ch->high_flow_userdata); + } + + if (curr_qlen <= ch->low_flow_mark + && orig_qlen > ch->low_flow_mark + && ch->low_flow_callback) { + ch->low_flow_callback(ch, ch->low_flow_userdata); + } +} + +static int +socket_send(struct IPC_CHANNEL * ch, struct IPC_MESSAGE* msg) +{ + int orig_qlen; + int diff; + struct IPC_MESSAGE* newmsg; + + if (msg->msg_len > MAXMSG) { + cl_log(LOG_ERR, "%s: sorry, cannot send messages " + "bigger than %d (requested %lu)", + __FUNCTION__, MAXMSG, (unsigned long)msg->msg_len); + return IPC_FAIL; + } + if (msg->msg_len < 0) { + cl_log(LOG_ERR, "socket_send: " + "invalid message"); + return IPC_FAIL; + } + + if (ch->ch_status != IPC_CONNECT) { + return IPC_FAIL; + } + + ch->ops->resume_io(ch); + + if (ch->send_queue->maxqlen_cnt && + time(NULL) - ch->send_queue->last_maxqlen_warn >= 60) { + cl_log(LOG_ERR, "%u messages dropped on a non-blocking channel (send queue maximum length %d)", + ch->send_queue->maxqlen_cnt, (int)ch->send_queue->max_qlen); + ch->send_queue->maxqlen_cnt = 0; + } + if ( !ch->should_send_block && + ch->send_queue->current_qlen >= ch->send_queue->max_qlen) { + if (!ch->send_queue->maxqlen_cnt) { + ch->send_queue->last_maxqlen_warn = time(NULL); + } + ch->send_queue->maxqlen_cnt++; + + if (ch->should_block_fail) { + return IPC_FAIL; + } else { + return IPC_OK; + } + } + + while (ch->send_queue->current_qlen >= ch->send_queue->max_qlen) { + if (ch->ch_status != IPC_CONNECT) { + cl_log(LOG_WARNING, "socket_send:" + " message queue exceeded and IPC not connected"); + return IPC_FAIL; + } + cl_shortsleep(); + ch->ops->resume_io(ch); + } + + /* add the message into the send queue */ + CHECKFOO(0,ch, msg, SavedQueuedBody, "queued message"); + SocketIPCStats.noutqueued++; + + diff = 0; + if (msg->msg_buf ) { + diff = (char*)msg->msg_body - (char*)msg->msg_buf; + } + if ( diff < (int)sizeof(struct SOCKET_MSG_HEAD) ) { + /* either we don't have msg->msg_buf set + * or we don't have enough bytes for socket head + * we delete this message and creates + * a new one and delete the old one + */ + + newmsg= socket_message_new(ch, msg->msg_len); + if (newmsg == NULL) { + cl_log(LOG_ERR, "socket_resume_io_write: " + "allocating memory for new ipc msg failed"); + return IPC_FAIL; + } + + memcpy(newmsg->msg_body, msg->msg_body, msg->msg_len); + + if(msg->msg_done) { + msg->msg_done(msg); + }; + msg = newmsg; + } +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch,msg, MSGPOS_ENQUEUE); +#endif + ch->send_queue->queue = g_list_append(ch->send_queue->queue, + msg); + orig_qlen = ch->send_queue->current_qlen++; + + socket_check_flow_control(ch, orig_qlen, orig_qlen +1 ); + + /* resume io */ + ch->ops->resume_io(ch); + return IPC_OK; +} + +static int +socket_recv(struct IPC_CHANNEL * ch, struct IPC_MESSAGE** message) +{ + GList *element; + + int nbytes; + int result; + + socket_resume_io(ch); + result = socket_resume_io_read(ch, &nbytes, TRUE); + + *message = NULL; + + if (ch->recv_queue->current_qlen == 0) { + return result != IPC_OK ? result : IPC_FAIL; + /*return IPC_OK;*/ + } + element = g_list_first(ch->recv_queue->queue); + + if (element == NULL) { + /* Internal accounting error, but correctable */ + cl_log(LOG_ERR + , "recv failure: qlen (%ld) > 0, but no message found." + , (long)ch->recv_queue->current_qlen); + ch->recv_queue->current_qlen = 0; + return IPC_FAIL; + } + *message = (struct IPC_MESSAGE *) (element->data); +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch, *message, MSGPOS_DEQUEUE); +#endif + + CHECKFOO(1,ch, *message, SavedReadBody, "read message"); + SocketIPCStats.nreceived++; + ch->recv_queue->queue = g_list_remove(ch->recv_queue->queue + , element->data); + ch->recv_queue->current_qlen--; + return IPC_OK; +} + +static int +socket_check_poll(struct IPC_CHANNEL * ch +, struct pollfd * sockpoll) +{ + if (ch->ch_status == IPC_DISCONNECT) { + return IPC_OK; + } + if (sockpoll->revents & POLLHUP) { + /* If input present, or this is an output-only poll... */ + if (sockpoll->revents & POLLIN + || (sockpoll-> events & POLLIN) == 0 ) { + ch->ch_status = IPC_DISC_PENDING; + return IPC_OK; + } +#if 1 + cl_log(LOG_INFO, "socket_check_poll(): HUP without input"); +#endif + ch->ch_status = IPC_DISCONNECT; + return IPC_BROKEN; + + } else if (sockpoll->revents & (POLLNVAL|POLLERR)) { + /* Have we already closed the socket? */ + if (fcntl(sockpoll->fd, F_GETFL) < 0) { + cl_perror("socket_check_poll(pid %d): bad fd [%d]" + , (int) getpid(), sockpoll->fd); + ch->ch_status = IPC_DISCONNECT; + return IPC_OK; + } + cl_log(LOG_ERR + , "revents failure: fd %d, flags 0x%x" + , sockpoll->fd, sockpoll->revents); + errno = EINVAL; + return IPC_FAIL; + } + return IPC_OK; +} + +static int +socket_waitfor(struct IPC_CHANNEL * ch +, gboolean (*finished)(struct IPC_CHANNEL * ch)) +{ + struct pollfd sockpoll; + + CHANAUDIT(ch); + if (finished(ch)) { + return IPC_OK; + } + + if (ch->ch_status == IPC_DISCONNECT) { + return IPC_BROKEN; + } + sockpoll.fd = ch->ops->get_recv_select_fd(ch); + + while (!finished(ch) && IPC_ISRCONN(ch)) { + int rc; + + sockpoll.events = POLLIN; + + /* Cannot call resume_io after the call to finished() + * and before the call to poll because we might + * change the state of the thing finished() is + * waiting for. + * This means that the poll call below would be + * not only pointless, but might + * make us hang forever waiting for this + * event which has already happened + */ + if (ch->send_queue->current_qlen > 0) { + sockpoll.events |= POLLOUT; + } + + rc = ipc_pollfunc_ptr(&sockpoll, 1, -1); + + if (rc < 0) { + return (errno == EINTR ? IPC_INTR : IPC_FAIL); + } + + rc = socket_check_poll(ch, &sockpoll); + if (sockpoll.revents & POLLIN) { + socket_resume_io(ch); + } + if (rc != IPC_OK) { + CHANAUDIT(ch); + return rc; + } + } + + CHANAUDIT(ch); + return IPC_OK; +} + +static int +socket_waitin(struct IPC_CHANNEL * ch) +{ + return socket_waitfor(ch, ch->ops->is_message_pending); +} +static gboolean +socket_is_output_flushed(struct IPC_CHANNEL * ch) +{ + return ! ch->ops->is_sending_blocked(ch); +} + +static int +socket_waitout(struct IPC_CHANNEL * ch) +{ + int rc; + CHANAUDIT(ch); + rc = socket_waitfor(ch, socket_is_output_flushed); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "socket_waitout failure: rc = %d", rc); + } else if (ch->ops->is_sending_blocked(ch)) { + cl_log(LOG_ERR, "socket_waitout output still blocked"); + } + CHANAUDIT(ch); + return rc; +} + +static gboolean +socket_is_message_pending(struct IPC_CHANNEL * ch) +{ + int nbytes; + + socket_resume_io_read(ch, &nbytes, TRUE); + ch->ops->resume_io(ch); + if (ch->recv_queue->current_qlen > 0) { + return TRUE; + } + + return !IPC_ISRCONN(ch); +} + +static gboolean +socket_is_output_pending(struct IPC_CHANNEL * ch) +{ + socket_resume_io(ch); + return ch->ch_status == IPC_CONNECT + && ch->send_queue->current_qlen > 0; +} + +static gboolean +socket_is_sendq_full(struct IPC_CHANNEL * ch) +{ + ch->ops->resume_io(ch); + return(ch->send_queue->current_qlen == ch->send_queue->max_qlen); +} + +static gboolean +socket_is_recvq_full(struct IPC_CHANNEL * ch) +{ + ch->ops->resume_io(ch); + return(ch->recv_queue->current_qlen == ch->recv_queue->max_qlen); +} + +static int +socket_get_conntype(struct IPC_CHANNEL* ch) +{ + return ch->conntype; +} + +static int +socket_assert_auth(struct IPC_CHANNEL *ch, GHashTable *auth) +{ + cl_log(LOG_ERR + , "the assert_auth function for domain socket is not implemented"); + return IPC_FAIL; +} + +static int +socket_resume_io_read(struct IPC_CHANNEL *ch, int* nbytes, gboolean read1anyway) +{ + struct SOCKET_CH_PRIVATE* conn_info; + int retcode = IPC_OK; + struct pollfd sockpoll; + int debug_loopcount = 0; + int debug_bytecount = 0; + size_t maxqlen = ch->recv_queue->max_qlen; + struct ipc_bufpool* pool = ch->pool; + int nmsgs = 0; + int spaceneeded; + *nbytes = 0; + + CHANAUDIT(ch); + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + if (ch->ch_status == IPC_DISCONNECT) { + return IPC_BROKEN; + } + + if (pool == NULL) { + ch->pool = pool = ipc_bufpool_new(0); + if (pool == NULL) { + cl_log(LOG_ERR, "socket_resume_io_read: " + "memory allocation for ipc pool failed"); + return IPC_FAIL; + } + } + + if (ipc_bufpool_full(pool, ch, &spaceneeded)) { + struct ipc_bufpool* newpool; + + newpool = ipc_bufpool_new(spaceneeded); + if (newpool == NULL) { + cl_log(LOG_ERR, "socket_resume_io_read: " + "memory allocation for a new ipc pool failed"); + return IPC_FAIL; + } + + ipc_bufpool_partial_copy(newpool, pool); + ipc_bufpool_unref(pool); + ch->pool = pool = newpool; + } + if (maxqlen <= 0 && read1anyway) { + maxqlen = 1; + } + if (ch->recv_queue->current_qlen < maxqlen && retcode == IPC_OK) { + void * msg_begin; + int msg_len; + int len; +#if HB_IPC_METHOD == HB_IPC_STREAM + struct strbuf d; + int flags, rc; +#endif + + CHANAUDIT(ch); + ++debug_loopcount; + + len = ipc_bufpool_spaceleft(pool); + msg_begin = pool->currpos; + + CHANAUDIT(ch); + + /* Now try to receive some data */ + +#if HB_IPC_METHOD == HB_IPC_SOCKET + msg_len = recv(conn_info->s, msg_begin, len, MSG_DONTWAIT); +#elif HB_IPC_METHOD == HB_IPC_STREAM + d.maxlen = len; + d.len = 0; + d.buf = msg_begin; + flags = 0; + rc = getmsg(conn_info->s, NULL, &d, &flags); + msg_len = (rc < 0) ? rc : d.len; +#endif + SocketIPCStats.last_recv_rc = msg_len; + SocketIPCStats.last_recv_errno = errno; + ++SocketIPCStats.recv_count; + + /* Did we get an error? */ + if (msg_len < 0) { + switch (errno) { + case EAGAIN: + if (ch->ch_status==IPC_DISC_PENDING) { + ch->ch_status =IPC_DISCONNECT; + retcode = IPC_BROKEN; + } + break; + + case ECONNREFUSED: + case ECONNRESET: + ch->ch_status = IPC_DISC_PENDING; + retcode= socket_check_disc_pending(ch); + break; + + default: + cl_perror("socket_resume_io_read" + ": unknown recv error, peerpid=%d", + ch->farside_pid); + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + break; + } + + } else if (msg_len == 0) { + ch->ch_status = IPC_DISC_PENDING; + if(ch->recv_queue->current_qlen <= 0) { + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + } + } else { + /* We read something! */ + /* Note that all previous cases break out of the loop */ + debug_bytecount += msg_len; + *nbytes = msg_len; + nmsgs = ipc_bufpool_update(pool, ch, msg_len, ch->recv_queue) ; + + if (nmsgs < 0) { + /* we didn't like the other side */ + cl_log(LOG_ERR, "socket_resume_io_read: " + "disconnecting the other side"); + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + } else { + SocketIPCStats.ninqueued += nmsgs; + } + } + } + + /* Check for errors uncaught by recv() */ + /* NOTE: It doesn't seem right we have to do this every time */ + /* FIXME?? */ + + memset(&sockpoll,0, sizeof(struct pollfd)); + if ((retcode == IPC_OK) + && (sockpoll.fd = conn_info->s) >= 0) { + /* Just check for errors, not for data */ + sockpoll.events = 0; + ipc_pollfunc_ptr(&sockpoll, 1, 0); + retcode = socket_check_poll(ch, &sockpoll); + } + + CHANAUDIT(ch); + if (retcode != IPC_OK) { + return retcode; + } + + return IPC_ISRCONN(ch) ? IPC_OK : IPC_BROKEN; +} + +static int +socket_resume_io_write(struct IPC_CHANNEL *ch, int* nmsg) +{ + int retcode = IPC_OK; + struct SOCKET_CH_PRIVATE* conn_info; + + CHANAUDIT(ch); + *nmsg = 0; + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + while (ch->ch_status == IPC_CONNECT + && retcode == IPC_OK + && ch->send_queue->current_qlen > 0) { + + GList * element; + struct IPC_MESSAGE * msg; + struct SOCKET_MSG_HEAD head; + struct IPC_MESSAGE* oldmsg = NULL; + int sendrc = 0; + struct IPC_MESSAGE* newmsg; + char* p; + unsigned int bytes_remaining; + int diff; + + CHANAUDIT(ch); + element = g_list_first(ch->send_queue->queue); + if (element == NULL) { + /* OOPS! - correct consistency problem */ + ch->send_queue->current_qlen = 0; + break; + } + msg = (struct IPC_MESSAGE *) (element->data); + + diff = 0; + if (msg->msg_buf ) { + diff = (char*)msg->msg_body - (char*)msg->msg_buf; + } + if ( diff < (int)sizeof(struct SOCKET_MSG_HEAD) ) { + /* either we don't have msg->msg_buf set + * or we don't have enough bytes for socket head + * we delete this message and creates + * a new one and delete the old one + */ + + newmsg= socket_message_new(ch, msg->msg_len); + if (newmsg == NULL) { + cl_log(LOG_ERR, "socket_resume_io_write: " + "allocating memory for new ipc msg failed"); + return IPC_FAIL; + } + + memcpy(newmsg->msg_body, msg->msg_body, msg->msg_len); + oldmsg = msg; + msg = newmsg; + } + + head.msg_len = msg->msg_len; + head.magic = HEADMAGIC; + memcpy(msg->msg_buf, &head, sizeof(struct SOCKET_MSG_HEAD)); + + if (ch->bytes_remaining == 0) { + /*we start to send a new message*/ +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch, msg, MSGPOS_SEND); +#endif + bytes_remaining = msg->msg_len + ch->msgpad; + p = msg->msg_buf; + } else { + bytes_remaining = ch->bytes_remaining; + p = ((char*)msg->msg_buf) + msg->msg_len + ch->msgpad + - bytes_remaining; + + } + + sendrc = 0; + + do { +#if HB_IPC_METHOD == HB_IPC_STREAM + struct strbuf d; + int msglen, putmsgrc; +#endif + + CHANAUDIT(ch); + +#if HB_IPC_METHOD == HB_IPC_SOCKET + sendrc = send(conn_info->s, p + , bytes_remaining, (MSG_DONTWAIT|MSG_NOSIGNAL)); +#elif HB_IPC_METHOD == HB_IPC_STREAM + d.maxlen = 0; + d.len = msglen = bytes_remaining; + d.buf = p; + putmsgrc = putmsg(conn_info->s, NULL, &d, 0); + sendrc = putmsgrc == 0 ? msglen : -1; +#endif + SocketIPCStats.last_send_rc = sendrc; + SocketIPCStats.last_send_errno = errno; + ++SocketIPCStats.send_count; + + if (sendrc <= 0) { + break; + } else { + p = p + sendrc; + bytes_remaining -= sendrc; + } + + } while(bytes_remaining > 0 ); + + ch->bytes_remaining = bytes_remaining; + + if (sendrc < 0) { + switch (errno) { + case EAGAIN: + retcode = IPC_OK; + break; + case EPIPE: + ch->ch_status = IPC_DISC_PENDING; + socket_check_disc_pending(ch); + retcode = IPC_BROKEN; + break; + default: + cl_perror("socket_resume_io_write" + ": send2 bad errno"); + ch->ch_status = IPC_DISCONNECT; + retcode = IPC_FAIL; + break; + } + break; + } else { + int orig_qlen; + + CHECKFOO(3,ch, msg, SavedSentBody, "sent message") + + if (oldmsg) { + if (msg->msg_done != NULL) { + msg->msg_done(msg); + } + msg=oldmsg; + } + + if(ch->bytes_remaining ==0) { + ch->send_queue->queue = g_list_remove(ch->send_queue->queue, msg); + if (msg->msg_done != NULL) { + msg->msg_done(msg); + } + + SocketIPCStats.nsent++; + orig_qlen = ch->send_queue->current_qlen--; + socket_check_flow_control(ch, orig_qlen, orig_qlen -1 ); + (*nmsg)++; + } + } + } + CHANAUDIT(ch); + if (retcode != IPC_OK) { + return retcode; + } + return IPC_ISRCONN(ch) ? IPC_OK : IPC_BROKEN; +} + +static int +socket_resume_io(struct IPC_CHANNEL *ch) +{ + int rc1 = IPC_OK; + int rc2 = IPC_OK; + int nwmsg = 1; + int nbytes_r = 1; + gboolean OKonce = FALSE; + + CHANAUDIT(ch); + if (!IPC_ISRCONN(ch)) { + return IPC_BROKEN; + } + + do { + if (nbytes_r > 0) { + rc1 = socket_resume_io_read(ch, &nbytes_r, FALSE); + } + if (nwmsg > 0) { + nwmsg = 0; + rc2 = socket_resume_io_write(ch, &nwmsg); + } + if (rc1 == IPC_OK || rc2 == IPC_OK) { + OKonce = TRUE; + } + } while ((nbytes_r > 0 || nwmsg > 0) && IPC_ISRCONN(ch)); + + if (IPC_ISRCONN(ch)) { + if (rc1 != IPC_OK) { + cl_log(LOG_ERR + , "socket_resume_io_read() failure"); + } + if (rc2 != IPC_OK && IPC_CONNECT == ch->ch_status) { + cl_log(LOG_ERR + , "socket_resume_io_write() failure"); + } + } else { + return (OKonce ? IPC_OK : IPC_BROKEN); + } + + return (rc1 != IPC_OK ? rc1 : rc2); +} + +static int +socket_get_recv_fd(struct IPC_CHANNEL *ch) +{ + struct SOCKET_CH_PRIVATE* chp = ch ->ch_private; + + return (chp == NULL ? -1 : chp->s); +} + +static int +socket_get_send_fd(struct IPC_CHANNEL *ch) +{ + return socket_get_recv_fd(ch); +} + +static void +socket_adjust_buf(struct IPC_CHANNEL *ch, int optname, unsigned q_len) +{ + const char *direction = optname == SO_SNDBUF ? "snd" : "rcv"; + int fd = socket_get_send_fd(ch); + unsigned byte; + + /* Arbitrary scaling. + * DEFAULT_MAX_QLEN is 64, default socket buf is often 64k to 128k, + * at least on those linux I checked. + * Keep that ratio, and allow for some overhead. */ + if (q_len == 0) + /* client does not want anything, + * reduce system buffers as well */ + byte = 4096; + else if (q_len < 512) + byte = (32 + q_len) * 1024; + else + byte = q_len * 1024; + + if (0 == setsockopt(fd, SOL_SOCKET, optname, &byte, sizeof(byte))) { + if (debug_level > 1) { + cl_log(LOG_DEBUG, "adjusted %sbuf size to %u", + direction, byte); + } + } else { + /* If this fails, you may need to adjust net.core.rmem_max, + * ...wmem_max, or equivalent */ + cl_log(LOG_WARNING, "adjust %sbuf size to %u failed: %s", + direction, byte, strerror(errno)); + } +} + +static int +socket_set_send_qlen (struct IPC_CHANNEL* ch, int q_len) +{ + /* This seems more like an assertion failure than a normal error */ + if (ch->send_queue == NULL) { + return IPC_FAIL; + } + socket_adjust_buf(ch, SO_SNDBUF, q_len); + ch->send_queue->max_qlen = q_len; + return IPC_OK; +} + +static int +socket_set_recv_qlen (struct IPC_CHANNEL* ch, int q_len) +{ + /* This seems more like an assertion failure than a normal error */ + if (ch->recv_queue == NULL) { + return IPC_FAIL; + } + socket_adjust_buf(ch, SO_RCVBUF, q_len); + ch->recv_queue->max_qlen = q_len; + return IPC_OK; +} + +static int ipcmsg_count_allocated = 0; +static int ipcmsg_count_freed = 0; +void socket_ipcmsg_dump_stats(void); +void +socket_ipcmsg_dump_stats(void) { + cl_log(LOG_INFO, "ipcsocket ipcmsg allocated=%d, freed=%d, diff=%d", + ipcmsg_count_allocated, + ipcmsg_count_freed, + ipcmsg_count_allocated - ipcmsg_count_freed); +} + +static void +socket_del_ipcmsg(IPC_Message* m) +{ + if (m == NULL) { + cl_log(LOG_ERR, "socket_del_ipcmsg:" + "msg is NULL"); + return; + } + + if (m->msg_body) { + memset(m->msg_body, 0, m->msg_len); + } + if (m->msg_buf) { + g_free(m->msg_buf); + } + + memset(m, 0, sizeof(*m)); + g_free(m); + + ipcmsg_count_freed ++; +} + +static IPC_Message* +socket_new_ipcmsg(IPC_Channel* ch, const void* data, int len, void* private) +{ + IPC_Message* hdr; + + if (ch == NULL || len < 0) { + cl_log(LOG_ERR, "socket_new_ipcmsg:" + " invalid parameter"); + return NULL; + } + + if (ch->msgpad > MAX_MSGPAD) { + cl_log(LOG_ERR, "socket_new_ipcmsg: too many pads " + "something is wrong"); + return NULL; + } + + hdr = ipcmsg_new(ch, data, len, private, socket_del_ipcmsg); + + if (hdr) ipcmsg_count_allocated ++; + + return hdr; +} + +static +struct IPC_MESSAGE * +ipcmsg_new(struct IPC_CHANNEL * ch, const void* data, int len, void* private, + DelProc delproc) +{ + struct IPC_MESSAGE * hdr; + char* copy = NULL; + char* buf; + char* body; + + if ((hdr = g_new(struct IPC_MESSAGE, 1)) == NULL) { + return NULL; + } + memset(hdr, 0, sizeof(*hdr)); + + if (len > 0) { + if ((copy = (char*)g_malloc(ch->msgpad + len)) == NULL) { + g_free(hdr); + return NULL; + } + if (data) { + memcpy(copy + ch->msgpad, data, len); + } + buf = copy; + body = copy + ch->msgpad;; + } else { + len = 0; + buf = body = NULL; + } + hdr->msg_len = len; + hdr->msg_buf = buf; + hdr->msg_body = body; + hdr->msg_ch = ch; + hdr->msg_done = delproc; + hdr->msg_private = private; + + return hdr; +} + +static int +socket_get_chan_status(IPC_Channel* ch) +{ + socket_resume_io(ch); + return ch->ch_status; +} + +/* socket object of the function table */ +static struct IPC_WAIT_OPS socket_wait_ops = { + socket_destroy_wait_conn, + socket_wait_selectfd, + socket_accept_connection, +}; + +/* + * create a new ipc queue whose length = 0 and inner queue = NULL. + * return the pointer to a new ipc queue or NULL is the queue can't be created. + */ + +static struct IPC_QUEUE* +socket_queue_new(void) +{ + struct IPC_QUEUE *temp_queue; + + /* temp queue with length = 0 and inner queue = NULL. */ + temp_queue = g_new(struct IPC_QUEUE, 1); + temp_queue->current_qlen = 0; + temp_queue->max_qlen = DEFAULT_MAX_QLEN; + temp_queue->queue = NULL; + temp_queue->last_maxqlen_warn = 0; + temp_queue->maxqlen_cnt = 0; + return temp_queue; +} + +/* + * socket_wait_conn_new: + * Called by ipc_wait_conn_constructor to get a new socket + * waiting connection. + * (better explanation of this role might be nice) + * + * Parameters : + * ch_attrs (IN) the attributes used to create this connection. + * + * Return : + * the pointer to the new waiting connection or NULL if the connection + * can't be created. + * + * NOTE : + * for domain socket implementation, the only attribute needed is path name. + * so the user should + * create the hash table like this: + * GHashTable * attrs; + * attrs = g_hash_table_new(g_str_hash, g_str_equal); + * g_hash_table_insert(attrs, PATH_ATTR, path_name); + * here PATH_ATTR is defined as "path". + * + * NOTE : + * The streams implementation uses "Streams Programming Guide", Solaris 8, + * as its guide (sample code near end of "Configuration" chapter 11). + */ +struct IPC_WAIT_CONNECTION * +socket_wait_conn_new(GHashTable *ch_attrs) +{ + struct IPC_WAIT_CONNECTION * temp_ch; + char *path_name; + char *mode_attr; + int s, flags; + struct SOCKET_WAIT_CONN_PRIVATE *wait_private; + mode_t s_mode; +#if HB_IPC_METHOD == HB_IPC_SOCKET + struct sockaddr_un my_addr; +#elif HB_IPC_METHOD == HB_IPC_STREAM + int pipefds[2]; +#endif + + path_name = (char *) g_hash_table_lookup(ch_attrs, IPC_PATH_ATTR); + mode_attr = (char *) g_hash_table_lookup(ch_attrs, IPC_MODE_ATTR); + + if (mode_attr != NULL) { + s_mode = (mode_t)strtoul((const char *)mode_attr, NULL, 8); + } else { + s_mode = 0777; + } + if (path_name == NULL) { + return NULL; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* prepare the unix domain socket */ + if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + cl_perror("socket_wait_conn_new: socket() failure"); + return NULL; + } + + if (unlink(path_name) < 0 && errno != ENOENT) { + cl_perror("socket_wait_conn_new: unlink failure(%s)", + path_name); + } + memset(&my_addr, 0, sizeof(my_addr)); + my_addr.sun_family = AF_LOCAL; /* host byte order */ + + if (strlen(path_name) >= sizeof(my_addr.sun_path)) { + close(s); + return NULL; + } + + strncpy(my_addr.sun_path, path_name, sizeof(my_addr.sun_path)); + + if (bind(s, (struct sockaddr *)&my_addr, sizeof(my_addr)) == -1) { + cl_perror("socket_wait_conn_new: trying to create in %s bind:" + , path_name); + close(s); + return NULL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + /* Set up the communication channel the clients will use to us (server) */ + if (pipe(pipefds) == -1) { + cl_perror("pipe() failure"); + return NULL; + } + + /* Let clients have unique connections to us */ + if (ioctl(pipefds[1], I_PUSH, "connld") == -1) { + cl_perror("ioctl(%d, I_PUSH, \"connld\") failure", pipefds[1]); + return NULL; + } + + if (unlink(path_name) < 0 && errno != ENOENT) { + cl_perror("socket_wait_conn_new: unlink failure(%s)", + path_name); + } + + if (mkfifo(path_name, s_mode) == -1) { + cl_perror("socket_wait_conn_new: mkfifo(%s, ...) failure", path_name); + return NULL; + } + + if (fattach(pipefds[1], path_name) == -1) { + cl_perror("socket_wait_conn_new: fattach(..., %s) failure", path_name); + return NULL; + } + + /* the pseudo-socket is the other part of the pipe */ + s = pipefds[0]; +#endif + + /* Change the permission of the socket */ + if (chmod(path_name,s_mode) < 0) { + cl_perror("socket_wait_conn_new: failure trying to chmod %s" + , path_name); + close(s); + return NULL; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* listen to the socket */ + if (listen(s, MAX_LISTEN_NUM) == -1) { + cl_perror("socket_wait_conn_new: listen(MAX_LISTEN_NUM)"); + close(s); + return NULL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + +#endif + + flags = fcntl(s, F_GETFL); + if (flags == -1) { + cl_perror("socket_wait_conn_new: cannot read file descriptor flags"); + close(s); + return NULL; + } + flags |= O_NONBLOCK; + if (fcntl(s, F_SETFL, flags) < 0) { + cl_perror("socket_wait_conn_new: cannot set O_NONBLOCK"); + close(s); + return NULL; + } + + wait_private = g_new(struct SOCKET_WAIT_CONN_PRIVATE, 1); +#if HB_IPC_METHOD == HB_IPC_SOCKET + wait_private->s = s; +#elif HB_IPC_METHOD == HB_IPC_STREAM + wait_private->pipefds[0] = pipefds[0]; + wait_private->pipefds[1] = pipefds[1]; +#endif + strncpy(wait_private->path_name, path_name, sizeof(wait_private->path_name)); + temp_ch = g_new(struct IPC_WAIT_CONNECTION, 1); + temp_ch->ch_private = (void *) wait_private; + temp_ch->ch_status = IPC_WAIT; + temp_ch->ops = (struct IPC_WAIT_OPS *)&socket_wait_ops; + + return temp_ch; +} + +/* + * will be called by ipc_channel_constructor to create a new socket channel. + * parameters : + * attrs (IN) the hash table of the attributes used to create this channel. + * + * return: + * the pointer to the new waiting channel or NULL if the channel can't be created. +*/ + +struct IPC_CHANNEL * +socket_client_channel_new(GHashTable *ch_attrs) { + char *path_name; + int sockfd; + + /* + * I don't really understand why the client and the server use different + * parameter names... + * + * It's a really bad idea to store both integers and strings + * in the same table. + * + * Maybe we need an internal function with a different set of parameters? + */ + + /* + * if we want to seperate them. I suggest + * + * user call ipc_channel_constructor(ch_type,attrs) to create a new channel. + * ipc_channel_constructor() call socket_channel_new(GHashTable*)to + * create a new socket channel. + * + * wait_conn->accept_connection() will call another function to create a + * new channel. This function will take socketfd as the parameter to + * create a socket channel. + */ + + path_name = (char *) g_hash_table_lookup(ch_attrs, IPC_PATH_ATTR); + if (path_name == NULL) { + return NULL; + } + +#if HB_IPC_METHOD == HB_IPC_SOCKET + /* prepare the socket */ + if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + cl_perror("socket_client_channel_new: socket"); + return NULL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + sockfd = open(path_name, O_RDWR|O_NONBLOCK); + if (sockfd == -1) { + cl_perror("socket_client_channel_new: open(%s, ...) failure", path_name); + return NULL; + } +#endif + + if (client_channel_new_auth(sockfd) < 0) { + close(sockfd); + return NULL; + } + return channel_new(sockfd, IPC_CLIENT, path_name); +} + +static +int client_channel_new_auth(int sockfd) { +#ifdef USE_BINDSTAT_CREDS + char rand_id[16]; + char uuid_str_tmp[40]; + struct sockaddr_un sock_addr; + + /* Prepare the socket */ + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sun_family = AF_UNIX; + + /* make sure socket paths never clash */ + uuid_generate(rand_id); + uuid_unparse(rand_id, uuid_str_tmp); + + snprintf(sock_addr.sun_path, sizeof(sock_addr.sun_path), + "%s/%s", HA_VARLIBHBDIR, uuid_str_tmp); + + unlink(sock_addr.sun_path); + if(bind(sockfd, (struct sockaddr*)&sock_addr, SUN_LEN(&sock_addr)) < 0) { + perror("Client bind() failure"); + return 0; + } +#endif + + return 0; +} + +static +struct IPC_CHANNEL * +socket_server_channel_new(int sockfd) { + return channel_new(sockfd, IPC_SERVER, "?"); +} + +static +struct IPC_CHANNEL * +channel_new(int sockfd, int conntype, const char *path_name) { + struct IPC_CHANNEL * temp_ch; + struct SOCKET_CH_PRIVATE* conn_info; + int flags; + + if (path_name == NULL || strlen(path_name) >= sizeof(conn_info->path_name)) { + return NULL; + } + + temp_ch = g_new(struct IPC_CHANNEL, 1); + if (temp_ch == NULL) { + cl_log(LOG_ERR, "channel_new: allocating memory for channel failed"); + return NULL; + } + memset(temp_ch, 0, sizeof(struct IPC_CHANNEL)); + + conn_info = g_new(struct SOCKET_CH_PRIVATE, 1); + + flags = fcntl(sockfd, F_GETFL); + if (flags == -1) { + cl_perror("channel_new: cannot read file descriptor flags"); + g_free(conn_info); conn_info = NULL; + g_free(temp_ch); + if (conntype == IPC_CLIENT) close(sockfd); + return NULL; + } + flags |= O_NONBLOCK; + if (fcntl(sockfd, F_SETFL, flags) < 0) { + cl_perror("channel_new: cannot set O_NONBLOCK"); + g_free(conn_info); conn_info = NULL; + g_free(temp_ch); + if (conntype == IPC_CLIENT) close(sockfd); + return NULL; + } + + conn_info->s = sockfd; + conn_info->remaining_data = 0; + conn_info->buf_msg = NULL; +#if HB_IPC_METHOD == HB_IPC_SOCKET + conn_info->peer_addr = NULL; +#endif + strncpy(conn_info->path_name, path_name, sizeof(conn_info->path_name)); + +#ifdef DEBUG + cl_log(LOG_INFO, "Initializing socket %d to DISCONNECT", sockfd); +#endif + temp_ch->ch_status = IPC_DISCONNECT; + temp_ch->ch_private = (void*) conn_info; + temp_ch->ops = (struct IPC_OPS *)&socket_ops; + temp_ch->msgpad = sizeof(struct SOCKET_MSG_HEAD); + temp_ch->bytes_remaining = 0; + temp_ch->should_send_block = FALSE; + temp_ch->should_block_fail = TRUE; + temp_ch->send_queue = socket_queue_new(); + temp_ch->recv_queue = socket_queue_new(); + temp_ch->pool = NULL; + temp_ch->high_flow_mark = temp_ch->send_queue->max_qlen; + temp_ch->low_flow_mark = -1; + temp_ch->conntype = conntype; + temp_ch->refcount = 0; + temp_ch->farside_uid = -1; + temp_ch->farside_gid = -1; + + return temp_ch; +} + +/* + * Create a new pair of pre-connected IPC channels similar to + * the result of pipe(2), or socketpair(2). + */ + +int +ipc_channel_pair(IPC_Channel* channels[2]) +{ + int sockets[2]; + int rc; + int j; + const char *pname; + +#if HB_IPC_METHOD == HB_IPC_SOCKET + pname = "[socketpair]"; + + if ((rc = socketpair(AF_LOCAL, SOCK_STREAM, 0, sockets)) < 0) { + return IPC_FAIL; + } +#elif HB_IPC_METHOD == HB_IPC_STREAM + pname = "[pipe]"; + + if ((rc = pipe(sockets)) < 0) { + return IPC_FAIL; + } + rc = 0; + for (j=0; j < 2; ++j) { + if (fcntl(sockets[j], F_SETFL, O_NONBLOCK) < 0) { + cl_perror("ipc_channel_pair: cannot set O_NONBLOCK"); + rc = -1; + } + } + if (rc < 0) { + close(sockets[0]); + close(sockets[1]); + return IPC_FAIL; + } +#endif + + if ((channels[0] = socket_server_channel_new(sockets[0])) == NULL) { + close(sockets[0]); + close(sockets[1]); + return IPC_FAIL; + } + if ((channels[1] = socket_server_channel_new(sockets[1])) == NULL) { + close(sockets[0]); + close(sockets[1]); + channels[0]->ops->destroy(channels[0]); + return IPC_FAIL; + } + for (j=0; j < 2; ++j) { + struct SOCKET_CH_PRIVATE* p = channels[j]->ch_private; + channels[j]->ch_status = IPC_CONNECT; + channels[j]->conntype = IPC_PEER; + /* Valid, but not terribly meaningful */ + channels[j]->farside_pid = getpid(); + strncpy(p->path_name, pname, sizeof(p->path_name)); + } + + return IPC_OK; +} + +/* brief free the memory space allocated to msg and destroy msg. */ + +static void +socket_free_message(struct IPC_MESSAGE * msg) { +#if 0 + memset(msg->msg_body, 0xff, msg->msg_len); +#endif + if (msg->msg_buf) { + g_free(msg->msg_buf); + } else { + g_free(msg->msg_body); + } +#if 0 + memset(msg, 0xff, sizeof(*msg)); +#endif + g_free((void *)msg); +} + +/* + * create a new ipc message whose msg_body's length is msg_len. + * + * parameters : + * msg_len (IN) the length of this message body in this message. + * + * return : + * the pointer to the new message or NULL if the message can't be created. + */ + +static struct IPC_MESSAGE* +socket_message_new(struct IPC_CHANNEL *ch, int msg_len) +{ + return ipcmsg_new(ch, NULL, msg_len, NULL, socket_free_message); +} + +/*********************************************************************** + * + * IPC authentication schemes... More machine dependent than + * we'd like, but don't know any better way... + * + ***********************************************************************/ + +static int +verify_creds(struct IPC_AUTH *auth_info, uid_t uid, gid_t gid) +{ + int ret = IPC_FAIL; + + if (!auth_info || (!auth_info->uid && !auth_info->gid)) { + return IPC_OK; + } + if ( auth_info->uid + && (g_hash_table_lookup(auth_info->uid + , GUINT_TO_POINTER((guint)uid)) != NULL)) { + ret = IPC_OK; + } else if (auth_info->gid + && (g_hash_table_lookup(auth_info->gid + , GUINT_TO_POINTER((guint)gid)) != NULL)) { + ret = IPC_OK; + } + return ret; +} + +/*********************************************************************** + * SO_PEERCRED VERSION... (Linux) + ***********************************************************************/ + +#ifdef USE_SO_PEERCRED +/* verify the authentication information. */ +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE * conn_info; + int ret = IPC_FAIL; + struct ucred cred; + socklen_t n = sizeof(cred); + + if (ch == NULL || ch->ch_private == NULL) { + return IPC_FAIL; + } + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + } + + /* Get the credential information for our peer */ + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + if (getsockopt(conn_info->s, SOL_SOCKET, SO_PEERCRED, &cred, &n) != 0 + || (size_t)n != sizeof(cred)) { + return ret; + } + + ch->farside_uid = cred.uid; + ch->farside_gid = cred.gid; + if (ret == IPC_OK) { + return ret; + } +#if 0 + cl_log(LOG_DEBUG, "SO_PEERCRED returned [%d, (%ld:%ld)]" + , cred.pid, (long)cred.uid, (long)cred.uid); + cl_log(LOG_DEBUG, "Verifying authentication: cred.uid=%d cred.gid=%d" + , cred.uid, cred.gid); + cl_log(LOG_DEBUG, "Verifying authentication: uidptr=0x%lx gidptr=0x%lx" + , (unsigned long)auth_info->uid + , (unsigned long)auth_info->gid); +#endif + /* verify the credential information. */ + return verify_creds(auth_info, cred.uid, cred.gid); +} + +/* get farside pid for our peer process */ + +static +pid_t +socket_get_farside_pid(int sockfd) +{ + socklen_t n; + struct ucred *cred; + pid_t f_pid; + + /* Get the credential information from peer */ + n = sizeof(struct ucred); + cred = g_new(struct ucred, 1); + if (getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, cred, &n) != 0) { + g_free(cred); + return -1; + } + + f_pid = cred->pid; + g_free(cred); + return f_pid; +} +#endif /* SO_PEERCRED version */ + +#ifdef USE_GETPEEREID +/* + * This is implemented in OpenBSD and FreeBSD. + * + * It's not a half-bad interface... + * + * This should probably be our standard way of doing it, and put it + * as a replacement library. That would simplify things... + */ + +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE *conn_info; + uid_t euid; + gid_t egid; + int ret = IPC_FAIL; + + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + } + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + if (getpeereid(conn_info->s, &euid, &egid) < 0) { + cl_perror("getpeereid() failure"); + return ret; + } + + ch->farside_uid = euid; + ch->farside_gid = egid; + + /* verify the credential information. */ + return verify_creds(auth_info, euid, egid); +} + +static +pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif /* USE_GETPEEREID */ + +/*********************************************************************** + * SCM_CREDS VERSION... (*BSD systems) + ***********************************************************************/ +#ifdef USE_SCM_CREDS +/* FIXME! Need to implement SCM_CREDS mechanism for BSD-based systems + * This isn't an emergency, but should be done in the future... + * Hint: * Postgresql does both types of authentication... + * see src/backend/libpq/auth.c + * Not clear its SO_PEERCRED implementation works though ;-) + */ + +/* Done.... Haven't tested yet. */ +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct msghdr msg; + /* Credentials structure */ + +#define EXTRASPACE 0 + +#ifdef HAVE_STRUCT_CMSGCRED + /* FreeBSD */ + typedef struct cmsgcred Cred; +# define crRuid cmcred_uid +# define crEuid cmcred_euid +# define crRgid cmcred_gid +# define crEgid cmcred_groups[0] /* Best guess */ +# define crpid cmcred_pid +# define crngrp cmcred_ngroups +# define crgrps cmcred_groups + +#elif HAVE_STRUCT_FCRED + /* Stevens' book */ + typedef struct fcred Cred; +# define crRuid fc_uid +# define crRgid fc_rgid +# define crEgid fc_gid +# define crngrp fc_ngroups +# define crgrps fc_groups + +#elif HAVE_STRUCT_SOCKCRED + /* NetBSD */ + typedef struct sockcred Cred; +# define crRuid sc_uid +# define crEuid sc_euid +# define crRgid sc_gid +# define crEgid sc_egid +# define crngrp sc_ngroups +# define crgrps sc_groups +# undef EXTRASPACE +# define EXTRASPACE SOCKCREDSIZE(ngroups) + +#elif HAVE_STRUCT_CRED + typedef struct cred Cred; +#define cruid c_uid + +#elif HAVE_STRUCT_UCRED + typedef struct ucred Cred; + + /* reuse this define for the moment */ +# if HAVE_STRUCT_UCRED_DARWIN +# define crEuid cr_uid +# define crEgid cr_groups[0] /* Best guess */ +# define crgrps cr_groups +# define crngrp cr_ngroups +# else +# define crEuid c_uid +# define crEgid c_gid +# endif +#else +# error "No credential type found!" +#endif + + struct SOCKET_CH_PRIVATE *conn_info; + int ret = IPC_FAIL; + char buf; + + /* Compute size without padding */ + #define CMSGSIZE (sizeof(struct cmsghdr)+(sizeof(Cred))+EXTRASPACE) + + union { + char mem[CMSGSIZE]; + struct cmsghdr hdr; + Cred credu; + }cmsgmem; + Cred cred; + + /* Point to start of first structure */ + struct cmsghdr *cmsg = &cmsgmem.hdr; + + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + } + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = g_new(struct iovec, 1); + msg.msg_iovlen = 1; + msg.msg_control = (char *) cmsg; + msg.msg_controllen = CMSGSIZE; + memset(cmsg, 0, sizeof(cmsgmem)); + + /* + * The one character which is received here is not meaningful; its + * purpose is only to make sure that recvmsg() blocks long enough for + * the other side to send its credentials. + */ + msg.msg_iov->iov_base = &buf; + msg.msg_iov->iov_len = 1; + + if (recvmsg(conn_info->s, &msg, 0) < 0 + || cmsg->cmsg_len < CMSGSIZE + || cmsg->cmsg_type != SCM_CREDS) { + cl_perror("can't get credential information from peer"); + return ret; + } + + /* Avoid alignment issues - just copy it! */ + memcpy(&cred, CMSG_DATA(cmsg), sizeof(cred)); + + ch->farside_uid = cred.crEuid; + ch->farside_gid = cred.crEgid; + if (ret == IPC_OK) { + return ret; + } + + /* verify the credential information. */ + return verify_creds(auth_info, cred.crEuid, cred.crEgid); +} + +/* + * FIXME! Need to implement SCM_CREDS mechanism for BSD-based systems + * this is similar to the SCM_CREDS mechanism for verify_auth() function. + * here we just want to get the pid of the other side from the credential + * information. + */ + +static +pid_t +socket_get_farside_pid(int sock) +{ + /* FIXME! */ + return -1; +} +#endif /* SCM_CREDS version */ + +/*********************************************************************** + * Bind/Stat VERSION... (Supported on OSX/Darwin and 4.3+BSD at least...) + * + * This is for use on systems such as OSX-Darwin where + * none of the other options is available. + * + * This implementation has been adapted from "Advanced Programming + * in the Unix Environment", Section 15.5.2, by W. Richard Stevens. + * + */ +#ifdef USE_BINDSTAT_CREDS + +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + int len = 0; + int ret = IPC_FAIL; + struct stat stat_buf; + struct sockaddr_un *peer_addr = NULL; + struct SOCKET_CH_PRIVATE *ch_private = NULL; + + if(ch != NULL) { + ch_private = (struct SOCKET_CH_PRIVATE *)(ch->ch_private); + if(ch_private != NULL) { + peer_addr = ch_private->peer_addr; + } + } + + if(ch == NULL) { + cl_log(LOG_ERR, "No channel to authenticate"); + return IPC_FAIL; + + } else if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + ret = IPC_OK; /* no restriction for authentication */ + + } + + if(ch_private == NULL) { + cl_log(LOG_ERR, "No channel private data available"); + return ret; + + } else if(peer_addr == NULL) { + cl_log(LOG_ERR, "No peer information available"); + return ret; + } + + len = SUN_LEN(peer_addr); + + if(len < 1) { + cl_log(LOG_ERR, "No peer information available"); + return ret; + } + peer_addr->sun_path[len] = 0; + stat(peer_addr->sun_path, &stat_buf); + + ch->farside_uid = stat_buf.st_uid; + ch->farside_gid = stat_buf.st_gid; + if (ret == IPC_OK) { + return ret; + } + + if ((auth_info->uid == NULL || g_hash_table_size(auth_info->uid) == 0) + && auth_info->gid != NULL + && g_hash_table_size(auth_info->gid) != 0) { + cl_log(LOG_WARNING, + "GID-Only IPC security is not supported" + " on this platform."); + return IPC_BROKEN; + } + + /* verify the credential information. */ + return verify_creds(auth_info, stat_buf.st_uid, stat_buf.st_gid); +} + +static pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif /* Bind/stat version */ + +/*********************************************************************** + * USE_STREAM_CREDS VERSION... (e.g. Solaris pre-10) + ***********************************************************************/ +#ifdef USE_STREAM_CREDS +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE *conn_info; + + if (ch == NULL || ch->ch_private == NULL) { + return IPC_FAIL; + } + + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + ch->farside_uid = conn_info->farside_uid; + ch->farside_gid = conn_info->farside_gid; + + /* verify the credential information. */ + return verify_creds(auth_info, + conn_info->farside_uid, conn_info->farside_gid); +} + +static +pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif + +/*********************************************************************** + * GETPEERUCRED VERSION... (e.g. Solaris 10 upwards) + ***********************************************************************/ + +#ifdef USE_GETPEERUCRED +/* verify the authentication information. */ +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + struct SOCKET_CH_PRIVATE *conn_info; + ucred_t *ucred = NULL; + int rc = IPC_FAIL; + + if (ch == NULL || ch->ch_private == NULL) { + return IPC_FAIL; + } + + conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + + if (auth_info == NULL + || (auth_info->uid == NULL && auth_info->gid == NULL)) { + rc = IPC_OK; /* no restriction for authentication */ + } + + if (getpeerucred(conn_info->s, &ucred) < 0) { + cl_perror("getpeereid() failure"); + return rc; + } + + ch->farside_uid = ucred_geteuid(ucred); + ch->farside_gid = ucred_getegid(ucred); + if (rc == IPC_OK) { + return rc; + } + + /* verify the credential information. */ + rc = verify_creds(auth_info, + ucred_geteuid(ucred), ucred_getegid(ucred)); + ucred_free(ucred); + return rc; +} + +static +pid_t +socket_get_farside_pid(int sockfd) +{ + ucred_t *ucred = NULL; + pid_t pid; + + if (getpeerucred(sockfd, &ucred) < 0) { + cl_perror("getpeereid() failure"); + return IPC_FAIL; + } + + pid = ucred_getpid(ucred); + + ucred_free(ucred); + + return pid; +} +#endif + +/*********************************************************************** + * DUMMY VERSION... (other systems...) + * + * Other options that seem to be out there include + * SCM_CREDENTIALS and LOCAL_CREDS + * There are some kludgy things you can do with SCM_RIGHTS + * to pass an fd which could only be opened by the user id to + * validate the user id, but I don't know of a similar kludge which + * would work for group ids. And, even the uid one will fail + * if normal users are allowed to give away (chown) files. + * + * Unfortunately, this set of authentication routines have become + * very important to this API and its users (like heartbeat). + * + ***********************************************************************/ + +#ifdef USE_DUMMY_CREDS +static int +socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) +{ + return IPC_FAIL; +} + +static +pid_t +socket_get_farside_pid(int sock) +{ + return -1; +} +#endif /* Dummy version */ + +/* socket object of the function table */ +static struct IPC_OPS socket_ops = { + socket_destroy_channel, + socket_initiate_connection, + socket_verify_auth, + socket_assert_auth, + socket_send, + socket_recv, + socket_waitin, + socket_waitout, + socket_is_message_pending, + socket_is_output_pending, + socket_resume_io, + socket_get_send_fd, + socket_get_recv_fd, + socket_set_send_qlen, + socket_set_recv_qlen, + socket_set_high_flow_callback, + socket_set_low_flow_callback, + socket_new_ipcmsg, + socket_get_chan_status, + socket_is_sendq_full, + socket_is_recvq_full, + socket_get_conntype, + socket_disconnect, +}; diff --git a/lib/clplumbing/ipctest.c b/lib/clplumbing/ipctest.c new file mode 100644 index 0000000..333d3a0 --- /dev/null +++ b/lib/clplumbing/ipctest.c @@ -0,0 +1,1377 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#undef _GNU_SOURCE /* in case it was defined on the command line */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* libgen.h: for 'basename()' on Solaris */ +#include +#include +#include +#include +#include +#include + +#define MAXERRORS 1000 +#define MAXERRORS_RECV 10 + +typedef int (*TestFunc_t)(IPC_Channel*chan, int count); + +static int channelpair(TestFunc_t client, TestFunc_t server, int count); +#if 0 +static void clientserverpair(IPC_Channel* channels[2]); +#endif + +static int echoserver(IPC_Channel*, int repcount); +static int echoclient(IPC_Channel*, int repcount); +static int asyn_echoserver(IPC_Channel*, int repcount); +static int asyn_echoclient(IPC_Channel*, int repcount); +static int mainloop_server(IPC_Channel* chan, int repcount); +static int mainloop_client(IPC_Channel* chan, int repcount); + +static int checksock(IPC_Channel* channel); +static void checkifblocked(IPC_Channel* channel); + +static int (*PollFunc)(struct pollfd * fds, unsigned int, int) += (int (*)(struct pollfd * fds, unsigned int, int)) poll; +static gboolean checkmsg(IPC_Message* rmsg, const char * who, int rcount); + +static const char *procname; + +static const int iter_def = 10000; /* number of iterations */ +static int verbosity; /* verbosity level */ + +/* + * The ipc interface can be invoked as either: + * 1. pair (pipe/socketpair); + * 2. separate connect/accept (like server with multiple independent clients). + * + * If number of clients is given as 0, the "pair" mechanism is used, + * otherwise the client/server mechanism. + */ +/* *** CLIENTS_MAX currently 1 while coding *** */ +#define CLIENTS_MAX 1 /* max. number of independent clients */ +static int clients_def; /* number of independent clients */ + +static int +channelpair(TestFunc_t clientfunc, TestFunc_t serverfunc, int count) +{ + IPC_Channel* channels[2]; + int rc = 0; + int waitstat = 0; + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main process", + procname, (int)getpid(), __LINE__); + } + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + default: /* Parent */ + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main waiting...", + procname, (int)getpid(), __LINE__); + } + while (wait(&waitstat) > 0) { + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + } + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main ended rc: %d", + procname, (int)getpid(), __LINE__, rc); + } + if (rc > 127) { + rc = 127; + } + exit(rc); + break; + case 0: /* Child */ + break; + } + /* Child continues here... */ + if (ipc_channel_pair(channels) != IPC_OK) { + cl_perror("Can't create ipc channel pair"); + exit(1); + } + checksock(channels[0]); + checksock(channels[1]); + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + + case 0: /* echo "client" Child */ + channels[1]->ops->destroy(channels[1]); + channels[1] = NULL; + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client starting...", + procname, (int)getpid(), __LINE__); + } + rc = clientfunc(channels[0], count); + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client ended rc:%d", + procname, (int)getpid(), __LINE__, rc); + } + exit (rc > 127 ? 127 : rc); + break; + + default: + break; + } + channels[0]->ops->destroy(channels[0]); + channels[0] = NULL; + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server starting...", + procname, (int)getpid(), __LINE__); + } + rc = serverfunc(channels[1], count); + wait(&waitstat); + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server ended rc:%d", + procname, (int)getpid(), __LINE__, rc); + } + return(rc); +} + +/* server with many clients */ +static int +clientserver(TestFunc_t clientfunc, TestFunc_t serverfunc, int count, int clients) +{ + IPC_Channel* channel; + int rc = 0; + int waitstat = 0; + struct IPC_WAIT_CONNECTION *wconn; + char path[] = IPC_PATH_ATTR; + char commpath[] = "/tmp/foobar"; /* *** CHECK/FIX: Is this OK? */ + GHashTable * wattrs; + int i; + pid_t pid; + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main process", + procname, (int)getpid(), __LINE__); + } + + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + default: /* Parent */ + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main waiting...", + procname, (int)getpid(), __LINE__); + } + while ((pid = wait(&waitstat)) > 0) { + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + } + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: main ended rc: %d", + procname, (int)getpid(), __LINE__, rc); + } + if (rc > 127) { + rc = 127; + } + exit(rc); + break; + case 0: /* Child */ + break; + } + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d:", + procname, (int)getpid(), __LINE__); + } + + /* set up a server */ + wattrs = g_hash_table_new(g_str_hash, g_str_equal); + if (! wattrs) { + cl_perror("g_hash_table_new() failed"); + exit(1); + } + g_hash_table_insert(wattrs, path, commpath); + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d:", + procname, (int)getpid(), __LINE__); + } + + wconn = ipc_wait_conn_constructor(IPC_ANYTYPE, wattrs); + if (! wconn) { + cl_perror("could not establish server"); + exit(1); + } + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d:", + procname, (int)getpid(), __LINE__); + } + + /* spawn the clients */ + for (i = 1; i <= clients; i++) { + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: fork client %d of %d", + procname, (int)getpid(), __LINE__, i, clients); + } + switch (fork()) { + case -1: + cl_perror("can't fork"); + exit(1); + break; + + case 0: /* echo "client" Child */ + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client %d starting...", + procname, (int)getpid(), __LINE__, i); + } + channel = ipc_channel_constructor(IPC_ANYTYPE, wattrs); + if (channel == NULL) { + cl_perror("client: channel creation failed"); + exit(1); + } + + rc = channel->ops->initiate_connection(channel); + if (rc != IPC_OK) { + cl_perror("channel[1] failed to connect"); + exit(1); + } + checksock(channel); + rc = clientfunc(channel, count); + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client %d ended rc:%d", + procname, (int)getpid(), __LINE__, rc, i); + } + exit (rc > 127 ? 127 : rc); + break; + + default: + break; + } + } + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server starting...", + procname, (int)getpid(), __LINE__); + } + /* accept on server */ + /* *** + * Two problems (or more) here: + * 1. What to do if no incoming call pending? + * At present, fudge by sleeping a little so client gets started. + * 2. How to handle multiple clients? + * Would need to be able to await both new connections and + * data on existing connections. + * At present, fudge CLIENTS_MAX as 1. + * *** + */ + sleep(1); /* *** */ + channel = wconn->ops->accept_connection(wconn, NULL); + if (channel == NULL) { + cl_perror("server: acceptance failed"); + } + + checksock(channel); + + rc = serverfunc(channel, count); + + /* server finished: tidy up */ + wconn->ops->destroy(wconn); + + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: server ended rc:%d", + procname, (int)getpid(), __LINE__, rc); + } + + /* reap the clients */ + for (i = 1; i <= clients; i++) { + pid_t pid; + + pid = wait(&waitstat); + if (verbosity >= 1) { + cl_log(LOG_DEBUG, "%s[%d]%d: client %d reaped:%d", + procname, (int)getpid(), __LINE__, + (int) pid, WIFEXITED(waitstat)); + } + if (WIFEXITED(waitstat)) { + rc += WEXITSTATUS(waitstat); + }else{ + rc += 1; + } + } + + return(rc); +} + +static void +echomsgbody(void * body, int n, int niter, size_t * len) +{ + char *str = body; + int l; + + l = snprintf(str, n-1, "String-%d", niter); + if (l < (n-1)) { + memset(&str[l], 'a', (n - (l+1))); + } + str[n-1] = '\0'; + *len = n; +} + +static void +checkifblocked(IPC_Channel* chan) +{ + if (chan->ops->is_sending_blocked(chan)) { + cl_log(LOG_INFO, "Sending is blocked."); + chan->ops->resume_io(chan); + } +} + +#ifdef CHEAT_CHECKS +extern long SeqNums[32]; +#endif + +static int +transport_tests(int iterations, int clients) +{ + int rc = 0; + +#ifdef CHEAT_CHECKS + memset(SeqNums, 0, sizeof(SeqNums)); +#endif + rc += (clients <= 0) + ? channelpair(echoclient, echoserver, iterations) + : clientserver(echoclient, echoserver, iterations, clients); + +#ifdef CHEAT_CHECKS + memset(SeqNums, 0, sizeof(SeqNums)); +#endif + rc += (clients <= 0) + ? channelpair(asyn_echoclient, asyn_echoserver, iterations) + : clientserver(asyn_echoclient, asyn_echoserver, iterations, clients); + +#ifdef CHEAT_CHECKS + memset(SeqNums, 0, sizeof(SeqNums)); +#endif + rc += (clients <= 0) + ? channelpair(mainloop_client, mainloop_server, iterations) + : clientserver(mainloop_client, mainloop_server, iterations, clients); + + return rc; +} + +static int data_size = 20; + +int +main(int argc, char ** argv) +{ + int argflag, argerrs; + int iterations; + int clients; + int rc = 0; + + /* + * Check and process arguments. + * -v: verbose + * -i: number of iterations + * -c: number of clients (invokes client/server mechanism) + * -s: data-size + */ + procname = basename(argv[0]); + + argerrs = 0; + iterations = iter_def; + clients = clients_def; + while ((argflag = getopt(argc, argv, "i:vuc:s:")) != EOF) { + switch (argflag) { + case 'i': /* iterations */ + iterations = atoi(optarg); + break; + case 'v': /* verbosity */ + verbosity++; + break; + case 'c': /* number of clients */ + clients = atoi(optarg); + if (clients < 1 || clients > CLIENTS_MAX) { + fprintf(stderr, "number of clients out of range" + "(1 to %d)\n", CLIENTS_MAX); + argerrs++; + } + break; + case 's': /* data size */ + data_size = atoi(optarg); + if (data_size < 0) { + fprintf(stderr, "data size must be >=0\n"); + argerrs++; + } + if (data_size > MAXMSG) { + fprintf(stderr, "maximum data size is %d\n", MAXMSG); + argerrs++; + } + break; + default: + argerrs++; + break; + } + } + if (argerrs) { + fprintf(stderr, + "Usage: %s [-v] [-i iterations] [-c clients] [-s size]\n" + "\t-v : verbose\n" + "\t-i : iterations (default %d)\n" + "\t-c : number of clients (default %d; nonzero invokes client/server)\n" + "\t-s : data size (default 20 bytes)\n", + procname, iter_def, clients_def); + exit(1); + } + + cl_log_set_entity(procname); + cl_log_enable_stderr(TRUE); + + + + rc += transport_tests(iterations, clients); + +#if 0 + /* Broken for the moment - need to fix it long term */ + cl_log(LOG_INFO, "NOTE: Enabling poll(2) replacement code."); + PollFunc = cl_poll; + g_main_set_poll_func(cl_glibpoll); + ipc_set_pollfunc(cl_poll); + + rc += transport_tests(5 * iterations, clients); +#endif + + cl_log(LOG_INFO, "TOTAL errors: %d", rc); + + return (rc > 127 ? 127 : rc); +} + +static int +checksock(IPC_Channel* channel) +{ + + if (!channel) { + cl_log(LOG_ERR, "Channel null"); + return 1; + } + if (!IPC_ISRCONN(channel)) { + cl_log(LOG_ERR, "Channel status is %d" + ", not IPC_CONNECT", channel->ch_status); + return 1; + } + return 0; +} + +static void +EOFcheck(IPC_Channel* chan) +{ + int fd = chan->ops->get_recv_select_fd(chan); + struct pollfd pf[1]; + int rc; + + cl_log(LOG_INFO, "channel state: %d", chan->ch_status); + + if (chan->recv_queue->current_qlen > 0) { + cl_log(LOG_INFO, "EOF Receive queue has %ld messages in it" + , (long)chan->recv_queue->current_qlen); + } + if (fd <= 0) { + cl_log(LOG_INFO, "EOF receive fd: %d", fd); + } + + + pf[0].fd = fd; + pf[0].events = POLLIN|POLLHUP; + pf[0].revents = 0; + + rc = poll(pf, 1, 0); + + if (rc < 0) { + cl_perror("failed poll(2) call in EOFcheck"); + return; + } + + /* Got input? */ + if (pf[0].revents & POLLIN) { + cl_log(LOG_INFO, "EOF socket %d (still) has input ready (real poll)" + , fd); + } + if ((pf[0].revents & ~(POLLIN|POLLHUP)) != 0) { + cl_log(LOG_INFO, "EOFcheck poll(2) bits: 0x%lx" + , (unsigned long)pf[0].revents); + } + pf[0].fd = fd; + pf[0].events = POLLIN|POLLHUP; + pf[0].revents = 0; + rc = PollFunc(pf, 1, 0); + if (rc < 0) { + cl_perror("failed PollFunc() call in EOFcheck"); + return; + } + + /* Got input? */ + if (pf[0].revents & POLLIN) { + cl_log(LOG_INFO, "EOF socket %d (still) has input ready (PollFunc())" + , fd); + } + if ((pf[0].revents & ~(POLLIN|POLLHUP)) != 0) { + cl_log(LOG_INFO, "EOFcheck PollFunc() bits: 0x%lx" + , (unsigned long)pf[0].revents); + } +} + +static int +echoserver(IPC_Channel* wchan, int repcount) +{ + char *str; + int j; + int errcount = 0; + IPC_Message wmsg; + IPC_Message* rmsg = NULL; + + if (!(str = malloc(data_size))) { + cl_log(LOG_ERR, "Out of memory"); + exit(1); + } + + memset(&wmsg, 0, sizeof(wmsg)); + wmsg.msg_private = NULL; + wmsg.msg_done = NULL; + wmsg.msg_body = str; + wmsg.msg_buf = NULL; + wmsg.msg_ch = wchan; + + cl_log(LOG_INFO, "Echo server: %d reps pid %d.", repcount, getpid()); + for (j=1; j <= repcount + ;++j, rmsg != NULL && (rmsg->msg_done(rmsg),1)) { + int rc; + + echomsgbody(str, data_size, j, &(wmsg.msg_len)); + if ((rc = wchan->ops->send(wchan, &wmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echotest: send failed %d rc iter %d" + , rc, j); + ++errcount; + continue; + } + + /*fprintf(stderr, "+"); */ + wchan->ops->waitout(wchan); + checkifblocked(wchan); + /*fprintf(stderr, "S"); */ + + /* Try and induce a failure... */ + if (j == repcount) { + sleep(1); + } + + while ((rc = wchan->ops->waitin(wchan)) == IPC_INTR); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "echotest server: waitin failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("waitin"); + exit(1); + } + + /*fprintf(stderr, "-"); */ + if ((rc = wchan->ops->recv(wchan, &rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echotest server: recv failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("recv"); + ++errcount; + rmsg=NULL; + continue; + } + /*fprintf(stderr, "s"); */ + if (rmsg->msg_len != wmsg.msg_len) { + cl_log(LOG_ERR + , "echotest: length mismatch [%lu,%lu] iter %d" + , (unsigned long)rmsg->msg_len + , (unsigned long)wmsg.msg_len, j); + ++errcount; + continue; + } + if (strncmp(rmsg->msg_body, wmsg.msg_body, wmsg.msg_len) + != 0) { + cl_log(LOG_ERR + , "echotest: data mismatch. iteration %d" + , j); + ++errcount; + continue; + } + + } + cl_log(LOG_INFO, "echoserver: %d errors", errcount); +#if 0 + cl_log(LOG_INFO, "destroying channel 0x%lx", (unsigned long)wchan); +#endif + wchan->ops->destroy(wchan); wchan = NULL; + + free(str); + + return errcount; +} +static int +echoclient(IPC_Channel* rchan, int repcount) +{ + int j; + int errcount = 0; + IPC_Message* rmsg; + + + + cl_log(LOG_INFO, "Echo client: %d reps pid %d." + , repcount, (int)getpid()); + for (j=1; j <= repcount ;++j) { + + int rc; + + while ((rc = rchan->ops->waitin(rchan)) == IPC_INTR); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "echotest client: waitin failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("waitin"); + exit(1); + } + /*fprintf(stderr, "/"); */ + + if ((rc = rchan->ops->recv(rchan, &rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echoclient: recv failed %d rc iter %d" + " errno=%d" + , rc, j, errno); + cl_perror("recv"); + ++errcount; + if (errcount > MAXERRORS_RECV) { + cl_log(LOG_ERR, + "echoclient: errcount excessive: %d: abandoning", + errcount); + exit(1); + } + --j; + rmsg=NULL; + continue; + } + /*fprintf(stderr, "c"); */ + if ((rc = rchan->ops->send(rchan, rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "echoclient: send failed %d rc iter %d" + , rc, j); + cl_log(LOG_INFO, "Message being sent: %s" + , (char*)rmsg->msg_body); + ++errcount; + continue; + } + /*fprintf(stderr, "%%"); */ + rchan->ops->waitout(rchan); + checkifblocked(rchan); + /*fprintf(stderr, "C"); */ + } + cl_log(LOG_INFO, "echoclient: %d errors", errcount); +#if 0 + cl_log(LOG_INFO, "destroying channel 0x%lx", (unsigned long)rchan); +#endif + rchan->ops->destroy(rchan); rchan = NULL; + return errcount; +} + +void dump_ipc_info(IPC_Channel* chan); + +static int +checkinput(IPC_Channel* chan, const char * where, int* rdcount, int maxcount) +{ + IPC_Message* rmsg = NULL; + int errs = 0; + int rc; + + while (chan->ops->is_message_pending(chan) + && errs < 10 && *rdcount < maxcount) { + + if (chan->ch_status == IPC_DISCONNECT && *rdcount < maxcount){ + cl_log(LOG_ERR + , "checkinput1[0x%lx %s]: EOF in iter %d" + , (unsigned long)chan, where, *rdcount); + EOFcheck(chan); + } + + if (rmsg != NULL) { + rmsg->msg_done(rmsg); + rmsg = NULL; + } + + if ((rc = chan->ops->recv(chan, &rmsg)) != IPC_OK) { + if (chan->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR + , "checkinput2[0x%lx %s]: EOF in iter %d" + , (unsigned long)chan, where, *rdcount); + EOFcheck(chan); + return errs; + } + cl_log(LOG_ERR + , "checkinput[%s]: recv" + " failed: rc %d rdcount %d errno=%d" + , where, rc, *rdcount, errno); + cl_perror("recv"); + rmsg=NULL; + ++errs; + continue; + } + *rdcount += 1; + if (!checkmsg(rmsg, where, *rdcount)) { + dump_ipc_info(chan); + ++errs; + } + if (*rdcount < maxcount && chan->ch_status == IPC_DISCONNECT){ + cl_log(LOG_ERR + , "checkinput3[0x%lx %s]: EOF in iter %d" + , (unsigned long)chan, where, *rdcount); + EOFcheck(chan); + } + + } + return errs; +} + +static void +async_high_flow_callback(IPC_Channel* ch, void* userdata) +{ + int* stopsending = userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + *stopsending = 1; + +} + +static void +async_low_flow_callback(IPC_Channel* ch, void* userdata) +{ + + int* stopsending = userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + *stopsending = 0; + +} + + +static int +asyn_echoserver(IPC_Channel* wchan, int repcount) +{ + int rdcount = 0; + int wrcount = 0; + int errcount = 0; + int blockedcount = 0; + IPC_Message* wmsg; + const char* w = "asyn_echoserver"; + int stopsending = 0; + + cl_log(LOG_INFO, "Asyn echo server: %d reps pid %d." + , repcount, (int)getpid()); + + (void)async_high_flow_callback; + (void)async_low_flow_callback; + + + wchan->ops->set_high_flow_callback(wchan, async_high_flow_callback, &stopsending); + wchan->ops->set_low_flow_callback(wchan, async_low_flow_callback, &stopsending); + + wchan->low_flow_mark = 2; + wchan->high_flow_mark = 20; + + while (rdcount < repcount) { + int rc; + + while (wrcount < repcount && blockedcount < 10 + && wchan->ch_status != IPC_DISCONNECT + ){ + + if (!stopsending){ + ++wrcount; + if (wrcount > repcount) { + break; + } + wmsg = wchan->ops->new_ipcmsg(wchan, NULL, data_size, NULL); + echomsgbody(wmsg->msg_body, data_size, wrcount, &wmsg->msg_len); + if ((rc = wchan->ops->send(wchan, wmsg)) != IPC_OK){ + + cl_log(LOG_INFO, "channel sstatus in echo server is %d", + wchan->ch_status); + if (wchan->ch_status != IPC_CONNECT) { + cl_log(LOG_ERR + , "asyn_echoserver: send failed" + " %d rc iter %d" + , rc, wrcount); + ++errcount; + continue; + }else {/*send failed because of channel busy + * roll back + */ + --wrcount; + } + } + + if (wchan->ops->is_sending_blocked(wchan)) { + /* fprintf(stderr, "b"); */ + ++blockedcount; + }else{ + blockedcount = 0; + } + } + + + errcount += checkinput(wchan, w, &rdcount, repcount); + if (wrcount < repcount + && wchan->ch_status == IPC_DISCONNECT) { + ++errcount; + break; + } + } + +/* cl_log(LOG_INFO, "async_echoserver: wrcount =%d rdcount=%d B", wrcount, rdcount); */ + + wchan->ops->waitout(wchan); + errcount += checkinput(wchan, w, &rdcount, repcount); + if (wrcount >= repcount && rdcount < repcount) { + while ((rc = wchan->ops->waitin(wchan)) == IPC_INTR); + + if (rc != IPC_OK) { + cl_log(LOG_ERR + , "asyn_echoserver: waitin()" + " failed %d rc rdcount %d errno=%d" + , rc, rdcount, errno); + cl_perror("waitin"); + exit(1); + } + } + if (wchan->ch_status == IPC_DISCONNECT + && rdcount < repcount) { + cl_log(LOG_ERR, + "asyn_echoserver: EOF in iter %d (wrcount=%d)", + rdcount, wrcount); + EOFcheck(wchan); + ++errcount; + break; + } + + blockedcount = 0; + + } + + cl_log(LOG_INFO, "asyn_echoserver: %d errors", errcount); +#if 0 + cl_log(LOG_INFO, "%d destroying channel 0x%lx", getpid(), (unsigned long)wchan); +#endif + wchan->ops->destroy(wchan); wchan = NULL; + return errcount; +} + +static int +asyn_echoclient(IPC_Channel* chan, int repcount) +{ + int rdcount = 0; + int wrcount = 0; + int errcount = 0; + IPC_Message* rmsg; + int rfd = chan->ops->get_recv_select_fd(chan); + int wfd = chan->ops->get_send_select_fd(chan); + gboolean rdeqwr = (rfd == wfd); + + + cl_log(LOG_INFO, "Async Echo client: %d reps pid %d." + , repcount, (int)getpid()); + ipc_set_pollfunc(PollFunc); + + while (rdcount < repcount && errcount < repcount) { + + int rc; + struct pollfd pf[2]; + int nfd = 1; + + pf[0].fd = rfd; + pf[0].events = POLLIN|POLLHUP; + + + if (chan->ops->is_sending_blocked(chan)) { + if (rdeqwr) { + pf[0].events |= POLLOUT; + }else{ + nfd = 2; + pf[1].fd = wfd; + pf[1].events = POLLOUT|POLLHUP; + } + } + + /* Have input? */ + /* fprintf(stderr, "i"); */ + while (chan->ops->is_message_pending(chan) + && rdcount < repcount) { + /*fprintf(stderr, "r"); */ + + if ((rc = chan->ops->recv(chan, &rmsg)) != IPC_OK) { + if (!IPC_ISRCONN(chan)) { + cl_log(LOG_ERR + , "Async echoclient: disconnect" + " iter %d", rdcount+1); + ++errcount; + return errcount; + } + cl_log(LOG_ERR + , "Async echoclient: recv" + " failed %d rc iter %d errno=%d" + , rc, rdcount+1, errno); + cl_perror("recv"); + rmsg=NULL; + ++errcount; + cl_log(LOG_INFO, "sleep(1)"); + sleep(1); + continue; + } + /*fprintf(stderr, "c"); */ + ++rdcount; + + + do { + rc = chan->ops->send(chan, rmsg); + + }while (rc != IPC_OK && chan->ch_status == IPC_CONNECT); + + if (chan->ch_status != IPC_CONNECT){ + ++errcount; + cl_perror("send"); + cl_log(LOG_ERR + , "Async echoclient: send failed" + " rc %d, iter %d", rc, rdcount); + cl_log(LOG_INFO, "Message being sent: %s" + , (char*)rmsg->msg_body); + if (!IPC_ISRCONN(chan)) { + cl_log(LOG_ERR + , "Async echoclient: EOF(2)" + " iter %d", rdcount+1); + EOFcheck(chan); + return errcount; + } + continue; + + } + + + ++wrcount; + /*fprintf(stderr, "x"); */ + } + if (rdcount >= repcount) { + break; + } + /* + * At this point it is possible that the POLLOUT bit + * being on is no longer necessary, but this will only + * cause an extra (false) output poll iteration at worst... + * This is because (IIRC) both is_sending_blocked(), and + * is_message_pending() both perform a resume_io(). + * This might be confusing, but -- oh well... + */ + + /* + fprintf(stderr, "P"); + cl_log(LOG_INFO, "poll[%d, 0x%x]" + , pf[0].fd, pf[0].events); + cl_log(LOG_DEBUG, "poll[%d, 0x%x]..." + , pf[0].fd, pf[0].events); + fprintf(stderr, "%%"); + cl_log(LOG_DEBUG, "CallingPollFunc()"); + */ + rc = PollFunc(pf, nfd, -1); + + /* Bad poll? */ + if (rc <= 0) { + cl_perror("Async echoclient: bad poll rc." + " %d rc iter %d", rc, rdcount); + ++errcount; + continue; + } + + /* Error indication? */ + if ((pf[0].revents & (POLLERR|POLLNVAL)) != 0) { + cl_log(LOG_ERR + , "Async echoclient: bad poll revents." + " revents: 0x%x iter %d", pf[0].revents, rdcount); + ++errcount; + continue; + } + + /* HUP without input... Premature EOF... */ + if ((pf[0].revents & POLLHUP) + && ((pf[0].revents&POLLIN) == 0)) { + cl_log(LOG_ERR + , "Async echoclient: premature pollhup." + " revents: 0x%x iter %d", pf[0].revents, rdcount); + EOFcheck(chan); + ++errcount; + continue; + } + + /* Error indication? */ + if (nfd > 1 + && (pf[1].revents & (POLLERR|POLLNVAL)) != 0) { + cl_log(LOG_ERR + , "Async echoclient: bad poll revents[1]." + " revents: 0x%x iter %d", pf[1].revents, rdcount); + ++errcount; + continue; + } + + /* Output unblocked (only) ? */ + if (pf[nfd-1].revents & POLLOUT) { + /*fprintf(stderr, "R");*/ + chan->ops->resume_io(chan); + }else if ((pf[0].revents & POLLIN) == 0) { + /* Neither I nor O available... */ + cl_log(LOG_ERR + , "Async echoclient: bad events." + " revents: 0x%x iter %d", pf[0].revents, rdcount); + ++errcount; + } + } + cl_poll_ignore(rfd); + cl_poll_ignore(wfd); + cl_log(LOG_INFO, "Async echoclient: %d errors, %d reads, %d writes", + errcount, rdcount, wrcount); +#if 0 + cl_log(LOG_INFO, "%d destroying channel 0x%lx",getpid(), (unsigned long)chan); +#endif + + + chan->ops->waitout(chan); + + chan->ops->destroy(chan); chan = NULL; + return errcount; +} + + +struct iterinfo { + int wcount; + int rcount; + int errcount; + IPC_Channel* chan; + int max; + gboolean sendingsuspended; +}; + +static GMainLoop* loop = NULL; + + + + +static gboolean +s_send_msg(gpointer data) +{ + struct iterinfo*i = data; + IPC_Message* wmsg; + int rc; + + ++i->wcount; + + wmsg = i->chan->ops->new_ipcmsg(i->chan, NULL, data_size, NULL); + echomsgbody(wmsg->msg_body, data_size, i->wcount, &wmsg->msg_len); + + /*cl_log(LOG_INFO, "s_send_msg: sending out %d", i->wcount);*/ + + if ((rc = i->chan->ops->send(i->chan, wmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "s_send_msg: send failed" + " %d rc iter %d" + , rc, i->wcount); + cl_log(LOG_ERR + , "s_send_msg: channel status: %d qlen: %ld" + , i->chan->ch_status + , (long)i->chan->send_queue->current_qlen); + ++i->errcount; + if (i->chan->ch_status != IPC_CONNECT) { + cl_log(LOG_ERR, "s_send_msg: Exiting."); + return FALSE; + } + if (i->errcount >= MAXERRORS) { + g_main_quit(loop); + return FALSE; + } + } + return !i->sendingsuspended?i->wcount < i->max: FALSE; +} + + + + +static void +mainloop_low_flow_callback(IPC_Channel* ch, void* userdata) +{ + + struct iterinfo* i = (struct iterinfo*) userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + if (i->sendingsuspended){ + i->sendingsuspended = FALSE; + g_idle_add(s_send_msg, i); + } + + return; + +} + +static void +mainloop_high_flow_callback(IPC_Channel* ch, void* userdata) +{ + struct iterinfo* i = (struct iterinfo*) userdata; + + if (userdata == NULL){ + cl_log(LOG_ERR, "userdata is NULL"); + return; + } + + i->sendingsuspended = TRUE; + +} + + +static gboolean +s_rcv_msg(IPC_Channel* chan, gpointer data) +{ + struct iterinfo*i = data; + + i->errcount += checkinput(chan, "s_rcv_msg", &i->rcount, i->max); + + if (chan->ch_status == IPC_DISCONNECT + || i->rcount >= i->max || i->errcount > MAXERRORS) { + if (i->rcount < i->max) { + ++i->errcount; + cl_log(LOG_INFO, "Early exit from s_rcv_msg"); + } + g_main_quit(loop); + return FALSE; + } + + return TRUE; +} + +static gboolean +checkmsg(IPC_Message* rmsg, const char * who, int rcount) +{ + char *str; + size_t len; + + if (!(str = malloc(data_size))) { + cl_log(LOG_ERR, "Out of memory"); + exit(1); + } + + echomsgbody(str, data_size, rcount, &len); + + if (rmsg->msg_len != len) { + cl_log(LOG_ERR + , "checkmsg[%s]: length mismatch" + " [expected %u, got %lu] iteration %d" + , who, (unsigned)len + , (unsigned long)rmsg->msg_len + , rcount); + cl_log(LOG_ERR + , "checkmsg[%s]: expecting [%s]" + , who, str); + cl_log(LOG_ERR + , "checkmsg[%s]: got [%s] instead" + , who, (const char *)rmsg->msg_body); + return FALSE; + } + if (strncmp(rmsg->msg_body, str, len) != 0) { + cl_log(LOG_ERR + , "checkmsg[%s]: data mismatch" + ". input iteration %d" + , who, rcount); + cl_log(LOG_ERR + , "checkmsg[%s]: expecting [%s]" + , who, str); + cl_log(LOG_ERR + , "checkmsg[%s]: got [%s] instead" + , who, (const char *)rmsg->msg_body); + return FALSE; +#if 0 + }else if (strcmp(who, "s_rcv_msg") == 0) { +#if 0 + + || strcmp(who, "s_echo_msg") == 0) { +#endif + cl_log(LOG_ERR + , "checkmsg[%s]: data Good" + "! input iteration %d" + , who, rcount); +#endif + } + + free(str); + + return TRUE; +} + +static gboolean +s_echo_msg(IPC_Channel* chan, gpointer data) +{ + struct iterinfo* i = data; + int rc; + IPC_Message* rmsg; + + while (chan->ops->is_message_pending(chan)) { + if (chan->ch_status == IPC_DISCONNECT) { + break; + } + + if ((rc = chan->ops->recv(chan, &rmsg)) != IPC_OK) { + cl_log(LOG_ERR + , "s_echo_msg: recv failed %d rc iter %d" + " errno=%d" + , rc, i->rcount+1, errno); + cl_perror("recv"); + ++i->errcount; + goto retout; + } + i->rcount++; + if (!checkmsg(rmsg, "s_echo_msg", i->rcount)) { + ++i->errcount; + } + + + + /*cl_log(LOG_INFO, "s_echo_msg: rcount= %d, wcount =%d", i->rcount, i->wcount);*/ + + + do { + rc = chan->ops->send(chan, rmsg); + + }while (rc != IPC_OK && chan->ch_status == IPC_CONNECT); + + if (chan->ch_status != IPC_CONNECT){ + cl_log(LOG_ERR, + "s_echo_msg: send failed %d rc iter %d qlen %ld", + rc, i->rcount, (long)chan->send_queue->current_qlen); + cl_perror("send"); + i->errcount ++; + + } + + i->wcount+=1; + /*cl_log(LOG_INFO, "s_echo_msg: end of this ite");*/ + } + retout: + /*fprintf(stderr, "%%");*/ + if (i->rcount >= i->max || chan->ch_status == IPC_DISCONNECT + || i->errcount > MAXERRORS) { + chan->ops->waitout(chan); + g_main_quit(loop); + return FALSE; + } + return TRUE; +} + +static void +init_iterinfo(struct iterinfo * i, IPC_Channel* chan, int max) +{ + memset(i, 0, sizeof(*i)); + i->chan = chan; + i->max = max; + i->sendingsuspended = FALSE; +} + +static int +mainloop_server(IPC_Channel* chan, int repcount) +{ + struct iterinfo info; + guint sendmsgsrc; + + + + loop = g_main_new(FALSE); + init_iterinfo(&info, chan, repcount); + + chan->ops->set_high_flow_callback(chan, mainloop_high_flow_callback, &info); + chan->ops->set_low_flow_callback(chan, mainloop_low_flow_callback, &info); + chan->high_flow_mark = 20; + chan->low_flow_mark = 2; + + sendmsgsrc = g_idle_add(s_send_msg, &info); + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, chan + , FALSE, s_rcv_msg, &info, NULL); + cl_log(LOG_INFO, "Mainloop echo server: %d reps pid %d.", repcount, (int)getpid()); + g_main_run(loop); + g_main_destroy(loop); + g_source_remove(sendmsgsrc); + loop = NULL; + cl_log(LOG_INFO, "Mainloop echo server: %d errors", info.errcount); + return info.errcount; +} +static int +mainloop_client(IPC_Channel* chan, int repcount) +{ + struct iterinfo info; + loop = g_main_new(FALSE); + init_iterinfo(&info, chan, repcount); + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, chan + , FALSE, s_echo_msg, &info, NULL); + cl_log(LOG_INFO, "Mainloop echo client: %d reps pid %d.", repcount, (int)getpid()); + g_main_run(loop); + g_main_destroy(loop); + loop = NULL; + cl_log(LOG_INFO, "Mainloop echo client: %d errors, %d read %d written" + , info.errcount, info.rcount, info.wcount); + return info.errcount; +} diff --git a/lib/clplumbing/ipctransient.h b/lib/clplumbing/ipctransient.h new file mode 100644 index 0000000..9c1746c --- /dev/null +++ b/lib/clplumbing/ipctransient.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007 Andrew Beekhof + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#undef _GNU_SOURCE /* in case it was defined on the command line */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXERRORS 1000 +#define MAX_IPC_FAIL 10 +#define FIFO_LEN 1024 + +extern const char *procname; + +extern const char *commdir; + +void trans_getargs(int argc, char **argv); + +void default_ipctest_input_destroy(gpointer user_data); + +IPC_Message * create_simple_message(const char *text, IPC_Channel *ch); diff --git a/lib/clplumbing/ipctransientclient.c b/lib/clplumbing/ipctransientclient.c new file mode 100644 index 0000000..080acf2 --- /dev/null +++ b/lib/clplumbing/ipctransientclient.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2004 Andrew Beekhof + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#define MAX_MESSAGES 3 +static char *messages[MAX_MESSAGES]; + +IPC_Message *create_simple_message(const char *text, IPC_Channel *ch); +IPC_Channel *init_client_ipctest_comms( + const char *child, gboolean (*dispatch)( + IPC_Channel* source_data, gpointer user_data), + void *user_data); +gboolean transient_client_callback(IPC_Channel* server, void* private_data); +void client_send_message( + const char *message_text, IPC_Channel *server_channel, int iteration); + +#define MAXTSTMSG 1000 + +int +main(int argc, char ** argv) +{ + int lpc =0, iteration=0; + GMainLoop* client_main = NULL; + IPC_Channel *server_channel = NULL; + + trans_getargs(argc, argv); + + cl_log_set_entity(procname); + cl_log_enable_stderr(TRUE); + + /* give the server a chance to start */ + cl_log(LOG_INFO, "#--#--#--#--# Beginning test run %d against server %d...", lpc, iteration); + client_main = g_main_new(FALSE); + + /* connect, send messages */ + server_channel = init_client_ipctest_comms("echo", transient_client_callback, client_main); + + if(server_channel == NULL) { + cl_log(LOG_ERR, "[Client %d] Could not connect to server", lpc); + return 1; + } + + for(lpc = 0; lpc < MAX_MESSAGES; lpc++) { + messages[lpc] = (char *)malloc(sizeof(char)*MAXTSTMSG); + } + snprintf(messages[0], MAXTSTMSG + , "%s_%ld%c", "hello", (long)getpid(), '\0'); + snprintf(messages[1], MAXTSTMSG + , "%s_%ld%c", "hello_world", (long)getpid(), '\0'); + snprintf(messages[2], MAXTSTMSG + , "%s_%ld%c", "hello_world_again", (long)getpid(), '\0'); + + for(lpc = 0; lpc < MAX_MESSAGES; lpc++) { + client_send_message(messages[lpc], server_channel, lpc); + } + + server_channel->ops->waitout(server_channel); + + /* wait for the reply by creating a mainloop and running it until + * the callbacks are invoked... + */ + + cl_log(LOG_DEBUG, "Waiting for replies from the echo server"); + g_main_run(client_main); + cl_log(LOG_INFO, "[Iteration %d] Client %d completed successfully", iteration, lpc); + + return 0; +} + + +IPC_Channel * +init_client_ipctest_comms(const char *child, + gboolean (*dispatch)(IPC_Channel* source_data + ,gpointer user_data), + void *user_data) +{ + IPC_Channel *ch; + GHashTable * attrs; + int local_sock_len = 2; /* 2 = '/' + '\0' */ + char *commpath = NULL; + static char path[] = IPC_PATH_ATTR; + + local_sock_len += strlen(child); + local_sock_len += strlen(commdir); + + commpath = (char*)malloc(sizeof(char)*local_sock_len); + if (commpath == NULL){ + cl_log(LOG_ERR, "%s: allocating memory failed", __FUNCTION__); + return NULL; + } + sprintf(commpath, "%s/%s", commdir, child); + commpath[local_sock_len - 1] = '\0'; + + cl_log(LOG_DEBUG, "[Client] Attempting to talk on: %s", commpath); + + attrs = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(attrs, path, commpath); + ch = ipc_channel_constructor(IPC_ANYTYPE, attrs); + g_hash_table_destroy(attrs); + + if (ch == NULL) { + cl_log(LOG_ERR, "[Client] Could not access channel on: %s", commpath); + return NULL; + } else if(ch->ops->initiate_connection(ch) != IPC_OK) { + cl_log(LOG_ERR, "[Client] Could not init comms on: %s", commpath); + return NULL; + } + + G_main_add_IPC_Channel(G_PRIORITY_LOW, + ch, FALSE, dispatch, user_data, + default_ipctest_input_destroy); + + return ch; +} + + +gboolean +transient_client_callback(IPC_Channel* server, void* private_data) +{ + int lpc = 0; + IPC_Message *msg = NULL; + char *buffer = NULL; + static int received_responses = 0; + + GMainLoop *mainloop = (GMainLoop*)private_data; + + while(server->ops->is_message_pending(server) == TRUE) { + if (server->ch_status == IPC_DISCONNECT) { + /* The message which was pending for us is the + * new status of IPC_DISCONNECT */ + break; + } + if(server->ops->recv(server, &msg) != IPC_OK) { + cl_log(LOG_ERR, "[Client] Error while invoking recv()"); + perror("[Client] Receive failure:"); + return FALSE; + } + + if (msg != NULL) { + buffer = (char*)msg->msg_body; + cl_log(LOG_DEBUG, "[Client] Got text [text=%s]", buffer); + received_responses++; + + if(lpc < MAX_MESSAGES && strcmp(messages[lpc], buffer) != 0) + { + cl_log(LOG_ERR, "[Client] Received someone else's message [%s] instead of [%s]", buffer, messages[lpc]); + } + else if(lpc >= MAX_MESSAGES) + { + cl_log(LOG_ERR, "[Client] Receivedan extra message [%s]", buffer); + } + + lpc++; + msg->msg_done(msg); + } else { + cl_log(LOG_ERR, "[Client] No message this time"); + } + } + + if(server->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR, "[Client] Client received HUP"); + return FALSE; + } + + cl_log(LOG_DEBUG, "[Client] Processed %d IPC messages this time, %d total", lpc, received_responses); + + if(received_responses > 2) { + cl_log(LOG_INFO, "[Client] Processed %d IPC messages, all done.", received_responses); + received_responses = 0; + g_main_quit(mainloop); + cl_log(LOG_INFO, "[Client] Exiting."); + return FALSE; + } + + return TRUE; +} + +void +client_send_message(const char *message_text, + IPC_Channel *server_channel, + int iteration) +{ + IPC_Message *a_message = NULL; + if(server_channel->ch_status != IPC_CONNECT) { + cl_log(LOG_WARNING, "[Client %d] Channel is in state %d before sending message [%s]", + iteration, server_channel->ch_status, message_text); + return; + } + + a_message = create_simple_message(message_text, server_channel); + if(a_message == NULL) { + cl_log(LOG_ERR, "Could not create message to send"); + } else { + cl_log(LOG_DEBUG, "[Client %d] Sending message: %s", iteration, (char*)a_message->msg_body); + while(server_channel->ops->send(server_channel, a_message) == IPC_FAIL) { + cl_log(LOG_ERR, "[Client %d] IPC channel is blocked", iteration); + cl_shortsleep(); + } + + if(server_channel->ch_status != IPC_CONNECT) { + cl_log(LOG_WARNING, + "[Client %d] Channel is in state %d after sending message [%s]", + iteration, server_channel->ch_status, message_text); + } + } +} diff --git a/lib/clplumbing/ipctransientlib.c b/lib/clplumbing/ipctransientlib.c new file mode 100644 index 0000000..7a6721e --- /dev/null +++ b/lib/clplumbing/ipctransientlib.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2004 Andrew Beekhof + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +/* for basename() on some OSes (e.g. Solaris) */ +#include + +#define WORKING_DIR HA_VARLIBHBDIR + +const char *procname = NULL; + +const char *commdir = WORKING_DIR; + +void +trans_getargs(int argc, char **argv) +{ + int argflag, argerrs; + + procname = basename(argv[0]); + + argerrs = 0; + while ((argflag = getopt(argc, argv, "C:")) != EOF) { + switch (argflag) { + case 'C': /* directory to commpath */ + commdir = optarg; + break; + default: + argerrs++; + break; + } + } + if (argerrs) { + fprintf(stderr, + "Usage: %s [-C commdir]\n" + "\t-C : directory to commpath (default %s)\n", + procname, WORKING_DIR); + exit(1); + } + +} + +void +default_ipctest_input_destroy(gpointer user_data) +{ + cl_log(LOG_INFO, "default_ipctest_input_destroy:received HUP"); +} + +IPC_Message * +create_simple_message(const char *text, IPC_Channel *ch) +{ + IPC_Message *ack_msg = NULL; + char *copy_text = NULL; + + if(text == NULL) { + cl_log(LOG_ERR, "ERROR: can't create IPC_Message with no text"); + return NULL; + } else if(ch == NULL) { + cl_log(LOG_ERR, "ERROR: can't create IPC_Message with no channel"); + return NULL; + } + + ack_msg = (IPC_Message *)malloc(sizeof(IPC_Message)); + if (ack_msg == NULL){ + cl_log(LOG_ERR, "create_simple_message:" + "allocating memory for IPC_Message failed"); + return NULL; + } + + memset(ack_msg, 0, sizeof(IPC_Message)); + + copy_text = strdup(text); + + ack_msg->msg_private = NULL; + ack_msg->msg_done = NULL; + ack_msg->msg_body = copy_text; + ack_msg->msg_ch = ch; + + ack_msg->msg_len = strlen(text)+1; + + return ack_msg; +} diff --git a/lib/clplumbing/ipctransientserver.c b/lib/clplumbing/ipctransientserver.c new file mode 100644 index 0000000..d7ee61d --- /dev/null +++ b/lib/clplumbing/ipctransientserver.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2004 Andrew Beekhof + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +gboolean transient_server_callback(IPC_Channel *client, gpointer user_data); +gboolean transient_server_connect(IPC_Channel *client_channel, gpointer user_data); +int init_server_ipc_comms(const char *child, + gboolean (*channel_client_connect)(IPC_Channel *newclient, gpointer user_data), + void (*channel_input_destroy)(gpointer user_data), + gboolean usenormalpoll); + +int +main(int argc, char ** argv) +{ + int iteration = 0; + GMainLoop* mainloop = NULL; + + trans_getargs(argc, argv); + + cl_log_set_entity(procname); + cl_log_enable_stderr(TRUE); + + init_server_ipc_comms("echo", transient_server_connect, default_ipctest_input_destroy, FALSE); + + /* wait for the reply by creating a mainloop and running it until + * the callbacks are invoked... + */ + mainloop = g_main_new(FALSE); + + cl_log(LOG_INFO, "#--#--#--# Echo Server %d is active...", iteration); + g_main_run(mainloop); + cl_log(LOG_INFO, "#--#--#--# Echo Server %d is stopped...", iteration); + + return 0; +} + + +int +init_server_ipc_comms(const char *child, + gboolean (*channel_client_connect)(IPC_Channel *newclient, gpointer user_data), + void (*channel_input_destroy)(gpointer user_data), + gboolean usenormalpoll) +{ + /* the clients wait channel is the other source of events. + * This source delivers the clients connection events. + * listen to this source at a relatively lower priority. + */ + mode_t mask; + IPC_WaitConnection *wait_ch; + GHashTable * attrs; + int local_sock_len = 2; /* 2 = '/' + '\0' */ + char *commpath = NULL; + static char path[] = IPC_PATH_ATTR; + + local_sock_len += strlen(child); + local_sock_len += strlen(commdir); + + commpath = (char*)malloc(sizeof(char)*local_sock_len); + if (commpath == NULL){ + cl_log(LOG_ERR, "%s: allocating memory failed", __FUNCTION__); + exit(1); + } + snprintf(commpath, local_sock_len, "%s/%s", commdir, child); + commpath[local_sock_len - 1] = '\0'; + + attrs = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(attrs, path, commpath); + + mask = umask(0); + wait_ch = ipc_wait_conn_constructor(IPC_ANYTYPE, attrs); + if (wait_ch == NULL){ + cl_perror("[Server] Can't create wait channel of type %s", IPC_ANYTYPE); + exit(1); + } + mask = umask(mask); + g_hash_table_destroy(attrs); + + G_main_add_IPC_WaitConnection(G_PRIORITY_LOW, + wait_ch, + NULL, + FALSE, + channel_client_connect, + wait_ch, /* user data passed to ?? */ + channel_input_destroy); + + cl_log(LOG_INFO, "[Server] Listening on: %s", commpath); + +/* if (!usenormalpoll) { */ +/* g_main_set_poll_func(cl_glibpoll); */ +/* ipc_set_pollfunc(cl_poll); */ +/* } */ + return 0; +} + +gboolean +transient_server_callback(IPC_Channel *client, gpointer user_data) +{ + int lpc = 0; + IPC_Message *msg = NULL; + char *buffer = NULL; + IPC_Message *reply = NULL; + int llpc = 0; + + cl_log(LOG_DEBUG, "channel: %p", client); + + cl_log(LOG_DEBUG, "Client status %d (disconnect=%d)", client->ch_status, IPC_DISCONNECT); + + while(client->ops->is_message_pending(client)) { + if (client->ch_status == IPC_DISCONNECT) { + /* The message which was pending for us is that + * the IPC status is now IPC_DISCONNECT */ + break; + } + if(client->ops->recv(client, &msg) != IPC_OK) { + cl_perror("[Server] Receive failure"); + return FALSE; + } + + if (msg != NULL) { + lpc++; + buffer = (char*)g_malloc(msg->msg_len+1); + memcpy(buffer,msg->msg_body, msg->msg_len); + buffer[msg->msg_len] = '\0'; + cl_log(LOG_DEBUG, "[Server] Got xml [text=%s]", buffer); + + reply = create_simple_message(strdup(buffer), client); + if (!reply) { + cl_log(LOG_ERR, "[Server] Could allocate reply msg."); + return FALSE; + } + + llpc = 0; + while(llpc++ < MAX_IPC_FAIL && client->ops->send(client, reply) == IPC_FAIL) { + cl_log(LOG_WARNING, "[Server] ipc channel blocked"); + cl_shortsleep(); + } + + if(lpc == MAX_IPC_FAIL) { + cl_log(LOG_ERR, "[Server] Could not send IPC, message. Channel is dead."); + free(reply); + return FALSE; + } + + cl_log(LOG_DEBUG, "[Server] Sent reply"); + msg->msg_done(msg); + } else { + cl_log(LOG_ERR, "[Server] No message this time"); + continue; + } + } + + cl_log(LOG_DEBUG, "[Server] Processed %d messages", lpc); + + cl_log(LOG_DEBUG, "[Server] Client status %d", client->ch_status); + if(client->ch_status != IPC_CONNECT) { + cl_log(LOG_INFO, "[Server] Server received HUP from child"); + return FALSE; + } + + return TRUE; +} + + +gboolean +transient_server_connect(IPC_Channel *client_channel, gpointer user_data) +{ + /* assign the client to be something, or put in a hashtable */ + cl_log(LOG_DEBUG, "A client tried to connect... and there was much rejoicing."); + + if(client_channel == NULL) { + cl_log(LOG_ERR, "[Server] Channel was NULL"); + } else if(client_channel->ch_status == IPC_DISCONNECT) { + cl_log(LOG_ERR, "[Server] Channel was disconnected"); + } else { + cl_log(LOG_DEBUG, "[Server] Client is %s %p", client_channel == NULL?"NULL":"valid", client_channel); + cl_log(LOG_DEBUG, "[Server] Client status %d (disconnect=%d)", client_channel->ch_status, IPC_DISCONNECT); + + cl_log(LOG_DEBUG, "[Server] Adding IPC Channel to main thread."); + G_main_add_IPC_Channel(G_PRIORITY_LOW, + client_channel, + FALSE, + transient_server_callback, + NULL, + default_ipctest_input_destroy); + } + + return TRUE; +} diff --git a/lib/clplumbing/longclock.c b/lib/clplumbing/longclock.c new file mode 100644 index 0000000..594c9c5 --- /dev/null +++ b/lib/clplumbing/longclock.c @@ -0,0 +1,275 @@ +/* + * Longclock operations + * + * Copyright (c) 2002 International Business Machines + * Author: Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +static unsigned Hz = 0; +static longclock_t Lc_Hz; +static double d_Hz; + + +const longclock_t zero_longclock = 0UL; + +#ifndef CLOCK_T_IS_LONG_ENOUGH +# undef time_longclock +#endif + +#ifdef HAVE_LONGCLOCK_ARITHMETIC +# undef msto_longclock +# undef longclockto_ms +# undef secsto_longclock +# undef add_longclock +# undef sub_longclock +# undef cmp_longclock +#endif + + +unsigned +hz_longclock(void) +{ + if (Hz == 0) { + /* Compute various hz-related constants */ + + Hz = sysconf(_SC_CLK_TCK); + Lc_Hz = (longclock_t)Hz; + d_Hz = (double) Hz; + } + return Hz; +} + +#ifdef TIMES_ALLOWS_NULL_PARAM +# define TIMES_PARAM NULL +#else + static struct tms dummy_longclock_tms_struct; +# define TIMES_PARAM &dummy_longclock_tms_struct +#endif + +unsigned long +cl_times(void) /* Make times(2) behave rationally on Linux */ +{ + clock_t ret; +#ifndef DISABLE_TIMES_KLUDGE + int save_errno = errno; + + /* + * times(2) really returns an unsigned value ... + * + * We don't check to see if we got back the error value (-1), because + * the only possibility for an error would be if the address of + * longclock_dummy_tms_struct was invalid. Since it's a + * compiler-generated address, we assume that errors are impossible. + * And, unfortunately, it is quite possible for the correct return + * from times(2) to be exactly (clock_t)-1. Sigh... + * + */ + errno = 0; +#endif /* DISABLE_TIMES_KLUDGE */ + ret = times(TIMES_PARAM); + +#ifndef DISABLE_TIMES_KLUDGE +/* + * This is to work around a bug in the system call interface + * for times(2) found in glibc on Linux (and maybe elsewhere) + * It changes the return values from -1 to -4096 all into + * -1 and then dumps the -(return value) into errno. + * + * This totally bizarre behavior seems to be widespread in + * versions of Linux and glibc. + * + * Many thanks to Wolfgang Dumhs + * for finding and documenting this bizarre behavior. + */ + if (errno != 0) { + ret = (clock_t) (-errno); + } + errno = save_errno; +#endif /* DISABLE_TIMES_KLUDGE */ + + /* sizeof(long) may be larger than sizeof(clock_t). + * Don't jump from 0x7fffffff to 0xffffffff80000000 + * because of sign extension. + * We do expect sizeof(clock_t) <= sizeof(long), however. + */ + BUILD_BUG_ON(sizeof(clock_t) > sizeof(unsigned long)); +#define CLOCK_T_MAX (~0UL >> (8*(sizeof(unsigned long) - sizeof(clock_t)))) + return (unsigned long)ret & CLOCK_T_MAX; +} + +#ifdef CLOCK_T_IS_LONG_ENOUGH +longclock_t +time_longclock(void) +{ + /* See note below about deliberately ignoring errors... */ + return (longclock_t)cl_times(); +} + +#else /* clock_t is shorter than 64 bits */ + +#define BITSPERBYTE 8 +#define WRAPSHIFT (BITSPERBYTE*sizeof(clock_t)) +#define WRAPAMOUNT (((longclock_t) 1) << WRAPSHIFT) +#define MINJUMP ((CLOCK_T_MAX/100UL)*99UL) + +longclock_t +time_longclock(void) +{ + /* Internal note: This updates the static fields; care should be + * taken to not call a function like cl_log (which internally + * calls time_longclock() as well) just before this happens, + * because then this can recurse infinitely; that is why the + * cl_log call is where it is; found by Simon Graham. */ + static gboolean calledbefore = FALSE; + static unsigned long lasttimes = 0L; + static unsigned long callcount = 0L; + static longclock_t lc_wrapcount = 0L; + unsigned long timesval; + + ++callcount; + + timesval = cl_times(); + + if (calledbefore && timesval < lasttimes) { + unsigned long jumpbackby = lasttimes - timesval; + + if (jumpbackby < MINJUMP) { + /* Kernel weirdness */ + cl_log(LOG_CRIT + , "%s: clock_t from times(2) appears to" + " have jumped backwards (in error)!" + , __FUNCTION__); + cl_log(LOG_CRIT + , "%s: old value was %lu" + ", new value is %lu, diff is %lu, callcount %lu" + , __FUNCTION__ + , (unsigned long)lasttimes + , (unsigned long)timesval + , (unsigned long)jumpbackby + , callcount); + /* Assume jump back was the error and ignore it */ + /* (i.e., hope it goes away) */ + }else{ + /* Normal looking wraparound */ + /* update last time BEFORE loging as log call + can call this routine recursively leading + to double update of wrapcount! */ + + lasttimes = timesval; + lc_wrapcount += WRAPAMOUNT; + + cl_log(LOG_INFO + , "%s: clock_t wrapped around (uptime)." + , __FUNCTION__); + } + } + else { + lasttimes = timesval; + calledbefore = TRUE; + } + return (lc_wrapcount | timesval); +} +#endif /* ! CLOCK_T_IS_LONG_ENOUGH */ + +longclock_t +msto_longclock(unsigned long ms) +{ + unsigned long secs = ms / 1000UL; + unsigned long msec = ms % 1000; + longclock_t result; + + (void)(Hz == 0 && hz_longclock()); + + if (ms == 0) { + return (longclock_t)0UL; + } + result = secs * Lc_Hz + (msec * Lc_Hz)/1000; + + if (result == 0) { + result = 1; + } + return result; +} + +longclock_t +secsto_longclock(unsigned long Secs) +{ + longclock_t secs = Secs; + + (void)(Hz == 0 && hz_longclock()); + + return secs * Lc_Hz; +} + +longclock_t +dsecsto_longclock(double v) +{ + (void)(Hz == 0 && hz_longclock()); + + return (longclock_t) ((v * d_Hz)+0.5); + +} + +unsigned long +longclockto_ms(longclock_t t) +{ + (void)(Hz == 0 && hz_longclock()); + + if (t == 0) { + return 0UL; + } + return (unsigned long) ((t*1000UL)/Lc_Hz); +} +#ifndef CLOCK_T_IS_LONG_ENOUGH +long +longclockto_long(longclock_t t) +{ + return ((long)(t)); +} + +longclock_t +add_longclock(longclock_t l, longclock_t r) +{ + return l + r; +} + +longclock_t +sub_longclock(longclock_t l, longclock_t r) +{ + return l - r; +} + +int +cmp_longclock(longclock_t l, longclock_t r) +{ + if (l < r) { + return -1; + } + if (l > r) { + return 1; + } + return 0; +} +#endif /* CLOCK_T_IS_LONG_ENOUGH */ diff --git a/lib/clplumbing/md5.c b/lib/clplumbing/md5.c new file mode 100644 index 0000000..b893483 --- /dev/null +++ b/lib/clplumbing/md5.c @@ -0,0 +1,335 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Cleaned up for heartbeat by + * Mitja Sarp + * Sun Jiang Dong + * Pan Jia Ming + * + */ + +#include + +#ifdef HAVE_STDINT_H +#include +#endif +#include /* for sprintf() */ +#include /* for memcpy() */ +#include /* for stupid systems */ +#include /* for ntohl() */ +#include +#include + +#define MD5_DIGESTSIZE 16 +#define MD5_BLOCKSIZE 64 + +typedef struct MD5Context_st { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +}MD5Context; + +#define md5byte unsigned char + +struct MD5Context { + uint32_t buf[4]; + uint32_t bytes[2]; + uint32_t in[16]; +}; + +void MD5Init(MD5Context *context); +void MD5Update(MD5Context *context, md5byte const *buf, unsigned len); +void MD5Final(unsigned char digest[16], MD5Context *context); +void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + + +#ifdef CONFIG_BIG_ENDIAN +static inline void byteSwap(uint32_t * buf, uint32_t len); + +static inline void +byteSwap(uint32_t * buf, uint32_t len) +{ + int i; + for (i = 0; i < len; i ++) { + uint32_t tmp = buf[i]; + buf[i] = ( (uint32_t) ((unsigned char *) &tmp)[0] ) | + (((uint32_t) ((unsigned char *) &tmp)[1]) << 8) | + (((uint32_t) ((unsigned char *) &tmp)[2]) << 16) | + (((uint32_t) ((unsigned char *) &tmp)[3]) << 24); + } +} +#elif defined(CONFIG_LITTLE_ENDIAN) + #define byteSwap(buf,words) +#else + #error "Neither CONFIG_BIG_ENDIAN nor CONFIG_LITTLE_ENDIAN defined!" +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301ul; + ctx->buf[1] = 0xefcdab89ul; + ctx->buf[2] = 0x98badcfeul; + ctx->buf[3] = 0x10325476ul; + + ctx->bytes[0] = 0; + ctx->bytes[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len) +{ + uint32_t t; + + /* Update byte count */ + + t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) + ctx->bytes[1]++; /* Carry from low to high */ + + t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */ + if (t > len) { + memcpy((md5byte *)ctx->in + 64 - t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((md5byte *)ctx->in + 64 - t, buf, t); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += t; + len -= t; + + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(md5byte digest[16], MD5Context *ctx) +{ + int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ + md5byte *p = (md5byte *)ctx->in + count; + + /* Set the first char of padding to 0x80. There is always room. */ + *p++ = 0x80; + + /* Bytes of padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count + 8); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + p = (md5byte *)ctx->in; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->in, 14); + + /* Append length in bits and transform */ + ctx->in[14] = ctx->bytes[0] << 3; + ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + MD5Transform(ctx->buf, ctx->in); + + byteSwap(ctx->buf, 16); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) ((x) ^ (y) ^ (z)) +#define F4(x, y, z) ((y) ^ ((x) | ~(z))) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) \ + (w += f(x,y,z) + (in), (w) = ((w)<<(s) | (w)>>(32-(s))) + (x)) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478ul, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756ul, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070dbul, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceeeul, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faful, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62aul, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613ul, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501ul, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8ul, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7aful, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1ul, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7beul, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122ul, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193ul, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438eul, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821ul, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562ul, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340ul, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51ul, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aaul, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105dul, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453ul, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681ul, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8ul, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6ul, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6ul, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87ul, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14edul, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905ul, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8ul, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9ul, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8aul, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942ul, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681ul, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122ul, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380cul, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44ul, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9ul, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60ul, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70ul, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6ul, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127faul, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085ul, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05ul, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039ul, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5ul, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8ul, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665ul, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244ul, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97ul, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7ul, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039ul, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3ul, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92ul, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47dul, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1ul, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4ful, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0ul, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314ul, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1ul, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82ul, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235ul, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bbul, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391ul, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +int MD5( const unsigned char *data + , unsigned long len + , unsigned char *digest) +{ + MD5Context context; + + MD5Init(&context); + MD5Update(&context, data, len); + MD5Final(digest, &context); + + return 0; +} + +int HMAC( const unsigned char * key + , unsigned int key_len + , const unsigned char * text + , unsigned long textlen + , unsigned char * digest) +{ + MD5Context context; + /* inner padding - key XORd with ipad */ + unsigned char k_ipad[65]; + /* outer padding - * key XORd with opad */ + unsigned char k_opad[65]; + unsigned char tk[MD5_DIGESTSIZE]; + int i; + + /* if key is longer than MD5_BLOCKSIZE bytes reset it to key=MD5(key) */ + if (key_len > MD5_BLOCKSIZE) { + MD5Context tctx; + MD5Init(&tctx); + MD5Update(&tctx, (const unsigned char *)key, key_len); + MD5Final(tk, &tctx); + + key = (unsigned char *)tk; + key_len = MD5_DIGESTSIZE; + } + /* start out by storing key in pads */ + memset(k_ipad, 0, sizeof k_ipad); + memset(k_opad, 0, sizeof k_opad); + memcpy(k_ipad, key, key_len); + memcpy(k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i=0; i +#include +#include +#include +#include +#include +#include + + +/* + * A slightly safer version of mkstemp(3) + * + * In this version, the file is initially created mode 0, and then chmod-ed + * to the requested permissions. This guarantees that the file is never + * open to others beyond the specified permissions at any time. + */ +int +mkstemp_mode(char* template, mode_t filemode) +{ + + mode_t maskval; + int fd; + + maskval = umask(0777); + + /* created file should now be mode 0000 */ + fd = mkstemp(template); + + umask(maskval); /* cannot fail :-) */ + + if (fd >= 0) { + if (chmod(template, filemode) < 0) { + int save = errno; + close(fd); + errno = save; + fd = -1; + } + } + return fd; +} diff --git a/lib/clplumbing/netstring_test.c b/lib/clplumbing/netstring_test.c new file mode 100644 index 0000000..1f498ec --- /dev/null +++ b/lib/clplumbing/netstring_test.c @@ -0,0 +1,255 @@ +/* + * netstring_test: Test program for testing the heartbeat binary/struct API + * + * Copyright (C) 2000 Guochun Shi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * A heartbeat API test program... + */ + +void NodeStatus(const char * node, const char * status, void * private); +void LinkStatus(const char * node, const char *, const char *, void*); +void gotsig(int nsig); + +void +NodeStatus(const char * node, const char * status, void * private) +{ + cl_log(LOG_NOTICE, "Status update: Node %s now has status %s" + , node, status); +} + +void +LinkStatus(const char * node, const char * lnk, const char * status +, void * private) +{ + cl_log(LOG_NOTICE, "Link Status update: Link %s/%s now has status %s" + , node, lnk, status); +} + +int quitnow = 0; +void gotsig(int nsig) +{ + (void)nsig; + quitnow = 1; +} + +#define BUFSIZE 16 +extern int netstring_format; + +int +main(int argc, char ** argv) +{ + struct ha_msg* reply; + struct ha_msg* pingreq = NULL; + unsigned fmask; + ll_cluster_t* hb; + int msgcount=0; + char databuf[BUFSIZE]; + int i; +#if 0 + char * ctmp; + const char * cval; + int j; +#endif + + netstring_format = 0; + + cl_log_set_entity(argv[0]); + cl_log_enable_stderr(TRUE); + cl_log_set_facility(LOG_USER); + hb = ll_cluster_new("heartbeat"); + cl_log(LOG_INFO, "PID=%ld", (long)getpid()); + cl_log(LOG_INFO, "Signing in with heartbeat"); + if (hb->llc_ops->signon(hb, "ping")!= HA_OK) { + cl_log(LOG_ERR, "Cannot sign on with heartbeat"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(1); + } + + if (hb->llc_ops->set_nstatus_callback(hb, NodeStatus, NULL) !=HA_OK){ + cl_log(LOG_ERR, "Cannot set node status callback"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(2); + } + + if (hb->llc_ops->set_ifstatus_callback(hb, LinkStatus, NULL)!=HA_OK){ + cl_log(LOG_ERR, "Cannot set if status callback"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(3); + } + +#if 0 + fmask = LLC_FILTER_RAW; +#else + fmask = LLC_FILTER_DEFAULT; +#endif + /* This isn't necessary -- you don't need this call - it's just for testing... */ + cl_log(LOG_INFO, "Setting message filter mode"); + if (hb->llc_ops->setfmode(hb, fmask) != HA_OK) { + cl_log(LOG_ERR, "Cannot set filter mode"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(4); + } + + CL_SIGINTERRUPT(SIGINT, 1); + CL_SIGNAL(SIGINT, gotsig); + + pingreq = ha_msg_new(0); + ha_msg_add(pingreq, F_TYPE, "ping"); + { + struct ha_msg *childmsg; + struct ha_msg *grandchildmsg; + + for(i = 0 ;i < BUFSIZE;i ++){ + databuf[i] = 1 + i ; + } + databuf[4] = 0; + + ha_msg_addbin(pingreq, "data",databuf , BUFSIZE); + + + childmsg = ha_msg_new(0); + ha_msg_add(childmsg, "name","testchild"); + ha_msg_addbin(childmsg, "data",databuf , BUFSIZE); + + grandchildmsg = ha_msg_new(0); + ha_msg_add(grandchildmsg, "name","grandchild"); + ha_msg_addstruct(childmsg, "child",grandchildmsg); + + if( ha_msg_addstruct(pingreq, "child", childmsg) != HA_OK){ + cl_log(LOG_ERR, "adding a child message to the message failed"); + exit(1); + } + + } + + cl_log(LOG_INFO, "printing out the pingreq message:"); + + ha_log_message(pingreq); + if (hb->llc_ops->sendclustermsg(hb, pingreq) == HA_OK) { + cl_log(LOG_INFO, "Sent ping request to cluster"); + }else{ + cl_log(LOG_ERR, "PING request FAIL to cluster"); + } + errno = 0; + for(; !quitnow && (reply=hb->llc_ops->readmsg(hb, 1)) != NULL;) { + const char * type; + const char * orig; + ++msgcount; + if ((type = ha_msg_value(reply, F_TYPE)) == NULL) { + type = "?"; + } + if ((orig = ha_msg_value(reply, F_ORIG)) == NULL) { + orig = "?"; + } + cl_log(LOG_INFO, " "); + cl_log(LOG_NOTICE, "Got message %d of type [%s] from [%s]" + , msgcount, type, orig); + + if (strcmp(type, "ping") ==0) { + int datalen = 0; + const char *data; + struct ha_msg *childmsg; + + cl_log(LOG_INFO, "****************************************"); + ha_log_message(reply); + + data = cl_get_binary(reply, "data", &datalen); + if(data){ + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "%d of data received,data=%s", datalen,data); + for(i = 0; i < datalen; i++){ + if( databuf[i] != data[i]){ + cl_log(LOG_ERR, "data does not match at %d",i); + break; + } + } + if(i == datalen){ + cl_log(LOG_INFO,"data matches"); + } + }else { + cl_log(LOG_WARNING, "cl_get_binary failed"); + } + + childmsg = cl_get_struct(reply,"child"); + if(childmsg){ + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "child message found"); + ha_log_message(childmsg); + }else{ + cl_log(LOG_WARNING, "cl_get_struct failed"); + } + + } + +#if 1 + { + struct ha_msg *cpmsg; + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "****************************************************"); + cl_log(LOG_INFO, "Testing ha_msg_copy():"); + cpmsg = ha_msg_copy(reply); + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "orginal message is :"); + cl_log(LOG_INFO, " "); + ha_log_message(reply); + cl_log(LOG_INFO, " "); + cl_log(LOG_INFO, "copied message is: "); + cl_log(LOG_INFO, " "); + ha_log_message(cpmsg); + ha_msg_del(cpmsg); + } + + ha_msg_del(reply); reply=NULL; +#endif + } + + if (!quitnow) { + cl_log(LOG_ERR, "read_hb_msg returned NULL"); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + } + if (hb->llc_ops->signoff(hb, TRUE) != HA_OK) { + cl_log(LOG_ERR, "Cannot sign off from heartbeat."); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(10); + } + if (hb->llc_ops->delete(hb) != HA_OK) { + cl_log(LOG_ERR, "Cannot delete API object."); + cl_log(LOG_ERR, "REASON: %s", hb->llc_ops->errmsg(hb)); + exit(11); + } + return 0; +} diff --git a/lib/clplumbing/ocf_ipc.c b/lib/clplumbing/ocf_ipc.c new file mode 100644 index 0000000..c243934 --- /dev/null +++ b/lib/clplumbing/ocf_ipc.c @@ -0,0 +1,594 @@ +/* + * + * ocf_ipc.c: IPC abstraction implementation. + * + * + * Copyright (c) 2002 Xiaoxiang Liu + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int num_pool_allocated = 0; +static int num_pool_freed = 0; + +#ifdef IPC_TIME_DEBUG +struct ha_msg; +void cl_log_message (int log_level, const struct ha_msg *m); +int timediff(longclock_t t1, longclock_t t2); +void ha_msg_del(struct ha_msg* msg); +void ipc_time_debug(IPC_Channel* ch, IPC_Message* ipcmsg, int whichpos); +#endif + +struct IPC_WAIT_CONNECTION * socket_wait_conn_new(GHashTable* ch_attrs); +struct IPC_CHANNEL * socket_client_channel_new(GHashTable* ch_attrs); + +int (*ipc_pollfunc_ptr)(struct pollfd*, unsigned int, int) += (int (*)(struct pollfd*, unsigned int, int)) poll; + +/* Set the IPC poll function to the given function */ +void +ipc_set_pollfunc(int (*pf)(struct pollfd*, unsigned int, int)) +{ + ipc_pollfunc_ptr = pf; +} + +struct IPC_WAIT_CONNECTION * +ipc_wait_conn_constructor(const char * ch_type, GHashTable* ch_attrs) +{ + if (strcmp(ch_type, "domain_socket") == 0 + || strcmp(ch_type, IPC_UDS_CRED) == 0 + || strcmp(ch_type, IPC_ANYTYPE) == 0 + || strcmp(ch_type, IPC_DOMAIN_SOCKET) == 0) { + return socket_wait_conn_new(ch_attrs); + } + return NULL; +} + +struct IPC_CHANNEL * +ipc_channel_constructor(const char * ch_type, GHashTable* ch_attrs) +{ + if (strcmp(ch_type, "domain_socket") == 0 + || strcmp(ch_type, IPC_UDS_CRED) == 0 + || strcmp(ch_type, IPC_ANYTYPE) == 0 + || strcmp(ch_type, IPC_DOMAIN_SOCKET) == 0) { + + return socket_client_channel_new(ch_attrs); + } + return NULL; +} + +static int +gnametonum(const char * gname, int gnlen) +{ + char grpname[64]; + struct group* grp; + + if (isdigit((int) gname[0])) { + return atoi(gname); + } + if (gnlen >= (int)sizeof(grpname)) { + return -1; + } + strncpy(grpname, gname, gnlen); + grpname[gnlen] = EOS; + if ((grp = getgrnam(grpname)) == NULL) { + cl_log(LOG_ERR + , "Invalid group name [%s]", grpname); + return -1; + } + return (int)grp->gr_gid; +} + +static int +unametonum(const char * lname, int llen) +{ + char loginname[64]; + struct passwd* pwd; + + if (llen >= (int)sizeof(loginname)) { + cl_log(LOG_ERR + , "user id name [%s] is too long", loginname); + return -1; + } + strncpy(loginname, lname, llen); + loginname[llen] = EOS; + + if (isdigit((int) loginname[0])) { + return atoi(loginname); + } + if ((pwd = getpwnam(loginname)) == NULL) { + cl_log(LOG_ERR + , "Invalid user id name [%s]", loginname); + return -1; + } + return (int)pwd->pw_uid; +} + +static GHashTable* +make_id_table(const char * list, int listlen, int (*map)(const char *, int)) +{ + GHashTable* ret; + const char * id; + const char * lastid = list + listlen; + int idlen; + int idval; + static int one = 1; + + ret = g_hash_table_new(g_direct_hash, g_direct_equal); + + id = list; + while (id < lastid && *id != EOS) { + idlen = strcspn(id, ","); + if (id+idlen >= lastid) { + idlen = (lastid - id); + } + idval = map(id, idlen); + if (idval < 0) { + g_hash_table_destroy(ret); + return NULL; + } +#if 0 + cl_log(LOG_DEBUG + , "Adding [ug]id %*s [%d] to authorization g_hash_table" + , idlen, id, idval); +#endif + g_hash_table_insert(ret, GUINT_TO_POINTER(idval), &one); + id += idlen; + if (id < lastid) { + id += strspn(id, ","); + } + } + return ret; +} + +struct IPC_AUTH* +ipc_str_to_auth(const char* uidlist, int uidlen, const char* gidlist, int gidlen) +{ + struct IPC_AUTH* auth; + + auth = malloc(sizeof(struct IPC_AUTH)); + if (auth == NULL) { + cl_log(LOG_ERR, "Out of memory for IPC_AUTH"); + return NULL; + } + + memset(auth, 0, sizeof(*auth)); + + if (uidlist) { + auth->uid = make_id_table(uidlist, uidlen, unametonum); + if (auth->uid == NULL) { + cl_log(LOG_ERR, + "Bad uid list [%*s]", + uidlen, uidlist); + goto errout; + } + } + if (gidlist) { + auth->gid = make_id_table(gidlist, gidlen, gnametonum); + if (auth->gid == NULL) { + cl_log(LOG_ERR , + "Bad gid list [%*s]", + gidlen, gidlist); + goto errout; + } + } + return auth; + + errout: + if (auth->uid) { + g_hash_table_destroy(auth->uid); + auth->uid = NULL; + } + if (auth->gid) { + g_hash_table_destroy(auth->gid); + auth->gid = NULL; + } + free(auth); + auth = NULL; + return NULL; +} + +struct IPC_AUTH * +ipc_set_auth(uid_t * a_uid, gid_t * a_gid, int num_uid, int num_gid) +{ + struct IPC_AUTH *temp_auth; + int i; + static int v = 1; + + temp_auth = malloc(sizeof(struct IPC_AUTH)); + if (temp_auth == NULL) { + cl_log(LOG_ERR, "%s: memory allocation failed",__FUNCTION__); + return NULL; + } + temp_auth->uid = g_hash_table_new(g_direct_hash, g_direct_equal); + temp_auth->gid = g_hash_table_new(g_direct_hash, g_direct_equal); + + if (num_uid > 0) { + for (i=0; iuid, GINT_TO_POINTER((gint)a_uid[i]) + , &v); + } + } + + if (num_gid > 0) { + for (i=0; igid, GINT_TO_POINTER((gint)a_gid[i]) + , &v); + } + } + + return temp_auth; +} + +void +ipc_destroy_auth(struct IPC_AUTH *auth) +{ + if (auth != NULL) { + if (auth->uid) { + g_hash_table_destroy(auth->uid); + } + if (auth->gid) { + g_hash_table_destroy(auth->gid); + } + free((void *)auth); + } +} + +static void +ipc_bufpool_display(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + return; + } + cl_log(LOG_INFO, "pool: refcount=%d, startpos=%p, currpos=%p," + "consumepos=%p, endpos=%p, size=%d", + pool->refcount, pool->startpos, + pool->currpos, pool->consumepos, + pool->endpos, pool->size); +} + +void +ipc_bufpool_dump_stats(void) +{ + cl_log(LOG_INFO, "num_pool_allocated=%d, num_pool_freed=%d, diff=%d", + num_pool_allocated, + num_pool_freed, + num_pool_allocated - num_pool_freed); +} + +#define POOLHDR_SIZE \ + (sizeof(struct ipc_bufpool) + 2*sizeof(struct SOCKET_MSG_HEAD)) + +struct ipc_bufpool* +ipc_bufpool_new(int size) +{ + struct ipc_bufpool* pool; + int totalsize; + + /* there are memories for two struct SOCKET_MSG_HEAD + * one for the big message, the other one for the next + * message. This code prevents allocating + * <4k> <4k> ... + * from happening when a client sends big messages + * constantly*/ + + totalsize = size + POOLHDR_SIZE; + + if (totalsize < POOL_SIZE) { + totalsize = POOL_SIZE; + } + + if (totalsize > MAXMSG + POOLHDR_SIZE) { + cl_log(LOG_INFO, "ipc_bufpool_new: " + "asking for buffer with size %d; " + "corrupted data len???", totalsize); + return NULL; + } + + pool = (struct ipc_bufpool*)malloc(totalsize+1); + if (pool == NULL) { + cl_log(LOG_ERR, "%s: memory allocation failed", __FUNCTION__); + return NULL; + } + memset(pool, 0, totalsize); + pool->refcount = 1; + pool->startpos = pool->currpos = pool->consumepos = + ((char*)pool) + sizeof(struct ipc_bufpool); + + pool->endpos = ((char*)pool) + totalsize; + pool->size = totalsize; + + num_pool_allocated ++ ; + + return pool; +} + +void +ipc_bufpool_del(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + return; + } + + if (pool->refcount > 0) { + cl_log(LOG_ERR," ipc_bufpool_del:" + " IPC buffer pool reference count > 0"); + return; + } + + memset(pool, 0, pool->size); + free(pool); + num_pool_freed ++ ; +} + +int +ipc_bufpool_spaceleft(struct ipc_bufpool* pool) +{ + if( pool == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_spaceleft:" + " invalid input argument"); + return 0; + } + return pool->endpos - pool->currpos; +} + +/* brief free the memory space allocated to msg and destroy msg. */ + +static void +ipc_bufpool_msg_done(struct IPC_MESSAGE * msg) +{ + struct ipc_bufpool* pool; + + if (msg == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_msg_done: invalid input"); + return; + } + + pool = (struct ipc_bufpool*)msg->msg_private; + + ipc_bufpool_unref(pool); + free(msg); +} + +static struct IPC_MESSAGE* +ipc_bufpool_msg_new(void) +{ + struct IPC_MESSAGE * temp_msg; + + temp_msg = malloc(sizeof(struct IPC_MESSAGE)); + if (temp_msg == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_msg_new:" + "allocating new msg failed"); + return NULL; + } + + memset(temp_msg, 0, sizeof(struct IPC_MESSAGE)); + + return temp_msg; +} + +static void +ipcmsg_display(IPC_Message* ipcmsg) +{ + if (ipcmsg == NULL) { + cl_log(LOG_ERR, "ipcmsg is NULL"); + return; + } + cl_log(LOG_INFO, "ipcmsg: msg_len=%lu, msg_buf=%p, msg_body=%p," + "msg_done=%p, msg_private=%p, msg_ch=%p", + (unsigned long)ipcmsg->msg_len, + ipcmsg->msg_buf, + ipcmsg->msg_body, + ipcmsg->msg_done, + ipcmsg->msg_private, + ipcmsg->msg_ch); +} + +/* after a recv call, we have new data + * in the pool buf, we need to update our + * pool struct to consume it + * + */ + +int +ipc_bufpool_update(struct ipc_bufpool* pool, + struct IPC_CHANNEL * ch, + int msg_len, + IPC_Queue* rqueue) +{ + IPC_Message* ipcmsg; + struct SOCKET_MSG_HEAD localhead; + struct SOCKET_MSG_HEAD* head = &localhead; + int nmsgs = 0 ; + + if (rqueue == NULL) { + cl_log(LOG_ERR, "ipc_update_bufpool:" + "invalid input"); + return 0; + } + + pool->currpos += msg_len; + + while(TRUE) { + /*not enough data for head*/ + if ((int)(pool->currpos - pool->consumepos) < (int)ch->msgpad) { + break; + } + + memcpy(head, pool->consumepos, sizeof(struct SOCKET_MSG_HEAD)); + + if (head->magic != HEADMAGIC) { + GList* last = g_list_last(rqueue->queue); + cl_log(LOG_ERR, "ipc_bufpool_update: " + "magic number in head does not match. " + "Something very bad happened, farside pid =%d", + ch->farside_pid); + cl_log(LOG_ERR, "magic=%x, expected value=%x", head->magic, HEADMAGIC); + ipc_bufpool_display(pool); + cl_log(LOG_INFO, "nmsgs=%d", nmsgs); + /*print out the last message in queue*/ + if (last) { + IPC_Message* m = (IPC_Message*)last; + ipcmsg_display(m); + } + return -1; + } + + if ( head->msg_len > MAXMSG) { + cl_log(LOG_ERR, "ipc_update_bufpool:" + "msg length is corruptted(%d)", + head->msg_len); + break; + } + + if (pool->consumepos + ch->msgpad + head->msg_len + > pool->currpos) { + break; + } + + ipcmsg = ipc_bufpool_msg_new(); + if (ipcmsg == NULL) { + cl_log(LOG_ERR, "ipc_update_bufpool:" + "allocating memory for new ipcmsg failed"); + break; + + } + ipcmsg->msg_buf = pool->consumepos; + ipcmsg->msg_body = pool->consumepos + ch->msgpad; + ipcmsg->msg_len = head->msg_len; + ipcmsg->msg_private = pool; + ipcmsg->msg_done = ipc_bufpool_msg_done; +#ifdef IPC_TIME_DEBUG + ipc_time_debug(ch,ipcmsg, MSGPOS_RECV); +#endif + rqueue->queue = g_list_append(rqueue->queue, ipcmsg); + rqueue->current_qlen ++; + nmsgs++; + + pool->consumepos += ch->msgpad + head->msg_len; + ipc_bufpool_ref(pool); + } + return nmsgs; +} + +gboolean +ipc_bufpool_full(struct ipc_bufpool* pool, + struct IPC_CHANNEL* ch, + int* dataspaceneeded) +{ + struct SOCKET_MSG_HEAD localhead; + struct SOCKET_MSG_HEAD* head = &localhead; + + *dataspaceneeded = 0; + /* not enough space for head */ + if ((int)(pool->endpos - pool->consumepos) < (int)ch->msgpad) { + return TRUE; + } + + /*enough space for head*/ + if ((int)(pool->currpos - pool->consumepos) >= (int)ch->msgpad) { + memcpy(head, pool->consumepos, sizeof(struct SOCKET_MSG_HEAD)); + + /* not enough space for data*/ + if ( pool->consumepos + ch->msgpad + head->msg_len >= pool->endpos) { + *dataspaceneeded = head->msg_len; + return TRUE; + } + } + + /* Either we are sure we have enough space + * or we cannot tell because we have not received + * head yet. But we are sure we have enough space + * for head + */ + return FALSE; +} + +int +ipc_bufpool_partial_copy(struct ipc_bufpool* dstpool, + struct ipc_bufpool* srcpool) +{ + struct SOCKET_MSG_HEAD localhead; + struct SOCKET_MSG_HEAD *head = &localhead; + int space_needed; + int nbytes; + + if (dstpool == NULL + || srcpool == NULL) { + cl_log(LOG_ERR, "ipc_bufpool_partial_ipcmsg_cp:" + "invalid input"); + return IPC_FAIL; + } + + if (srcpool->currpos - srcpool->consumepos >= + (ssize_t)sizeof(struct SOCKET_MSG_HEAD)) { + + memcpy(head, srcpool->consumepos, sizeof(struct SOCKET_MSG_HEAD)); + space_needed = head->msg_len + sizeof(*head); + + if (space_needed > ipc_bufpool_spaceleft(dstpool)) { + cl_log(LOG_ERR, "ipc_bufpool_partial_ipcmsg_cp:" + " not enough space left in dst pool,spaced needed=%d", + space_needed); + return IPC_FAIL; + } + } + + nbytes = srcpool->currpos - srcpool->consumepos; + memcpy(dstpool->consumepos, srcpool->consumepos,nbytes); + + srcpool->currpos = srcpool->consumepos; + dstpool->currpos = dstpool->consumepos + nbytes; + + return IPC_OK; +} + +void +ipc_bufpool_ref(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + cl_log(LOG_ERR, "ref_pool:" + " invalid input"); + return; + } + pool->refcount ++; +} + +void +ipc_bufpool_unref(struct ipc_bufpool* pool) +{ + if (pool == NULL) { + cl_log(LOG_ERR, "unref_pool:" + " invalid input"); + return; + } + pool->refcount --; + if (pool->refcount <= 0) { + ipc_bufpool_del(pool); + } +} diff --git a/lib/clplumbing/proctrack.c b/lib/clplumbing/proctrack.c new file mode 100644 index 0000000..f6a9df2 --- /dev/null +++ b/lib/clplumbing/proctrack.c @@ -0,0 +1,515 @@ +/* + * Process tracking object. + * + * Copyright (c) 2002 International Business Machines + * Author: Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUGPROCTRACK debugproctrack + + +int debugproctrack = 0; +static int LoggingIsEnabled = 1; +static GHashTable* ProcessTable = NULL; +static void InitProcTable(void); +static void ForEachProcHelper(gpointer key, gpointer value +, void * helper); +static gboolean TrackedProcTimeoutFunction(gpointer p); + +static void +InitProcTable() +{ + if (ProcessTable) { + return; + } + + ProcessTable = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +/* Create/Log a new tracked process */ +void +NewTrackedProc(pid_t pid, int isapgrp, ProcTrackLogType loglevel +, void * privatedata, ProcTrack_ops* ops) +{ + ProcTrack* p = g_new(ProcTrack, 1); + + InitProcTable(); + p->pid = pid; + p->isapgrp = isapgrp; + p->loglevel = loglevel; + p->privatedata = privatedata; + p->ops = ops; + p->startticks = time_longclock(); + p->starttime = time(NULL); + p->timerid = 0; + p->timeoutseq = -1; + p->killinfo = NULL; + + g_hash_table_insert(ProcessTable, GINT_TO_POINTER(pid), p); + + /* Tell them that someone registered a process */ + if (p->ops->procregistered) { + p->ops->procregistered(p); + } +} + +static struct signal_info_s { + int signo; + const char * sigdefine; + const char* sigwords; +} signal_info [] = { + +#ifdef SIGHUP + {SIGHUP, "SIGHUP", "Hangup"}, +#endif +#ifdef SIGINT + {SIGINT, "SIGINT", "Interrupt"}, +#endif +#ifdef SIGQUIT + {SIGQUIT, "SIGQUIT", "Quit"}, +#endif +#ifdef SIGILL + {SIGILL, "SIGILL", "Illegal instruction"}, +#endif +#ifdef SIGTRAP + {SIGTRAP, "SIGTRAP", "Trace"}, +#endif +#ifdef SIGABRT + {SIGABRT, "SIGABRT", "Abort"}, +#endif +#ifdef SIGIOT + {SIGIOT, "SIGIOT", "IOT trap"}, +#endif +#ifdef SIGBUS + {SIGBUS, "SIGBUS", "BUS error"}, +#endif +#ifdef SIGFPE + {SIGFPE, "SIGFPE", "Floating-point exception"}, +#endif +#ifdef SIGKILL + {SIGKILL, "SIGKILL", "Kill, unblockable"}, +#endif +#ifdef SIGUSR1 + {SIGUSR1, "SIGUSR1", "User-defined signal 1"}, +#endif +#ifdef SIGSEGV + {SIGSEGV, "SIGSEGV", "Segmentation violation"}, +#endif +#ifdef SIGUSR2 + {SIGUSR2, "SIGUSR2", "User-defined signal 2"}, +#endif +#ifdef SIGPIPE + {SIGPIPE, "SIGPIPE", "Broken pipe (POSIX)"}, +#endif +#ifdef SIGALRM + {SIGALRM, "SIGALRM", "Alarm clock (POSIX)"}, +#endif +#ifdef SIGTERM + {SIGTERM, "SIGTERM", "Termination (ANSI)"}, +#endif +#ifdef SIGSTKFLT + {SIGSTKFLT, "SIGSTKFLT", "Stack fault"}, +#endif +#ifdef SIGCHLD + {SIGCHLD, "SIGCHLD", "Child status has changed"}, +#endif +#ifdef SIGCLD + {SIGCLD, "SIGCLD ", "Child status has changed"}, +#endif +#ifdef SIGCONT + {SIGCONT, "SIGCONT", "Continue"}, +#endif +#ifdef SIGSTOP + {SIGSTOP, "SIGSTOP", "Stop, unblockable"}, +#endif +#ifdef SIGTSTP + {SIGTSTP, "SIGTSTP", "Keyboard stop"}, +#endif +#ifdef SIGTTIN + {SIGTTIN, "SIGTTIN", "Background read from tty"}, +#endif +#ifdef SIGTTOU + {SIGTTOU, "SIGTTOU", "Background write to tty"}, +#endif +#ifdef SIGURG + {SIGURG, "SIGURG ", "Urgent condition on socket"}, +#endif +#ifdef SIGXCPU + {SIGXCPU, "SIGXCPU", "CPU limit exceeded"}, +#endif +#ifdef SIGXFSZ + {SIGXFSZ, "SIGXFSZ", "File size limit exceeded"}, +#endif +#ifdef SIGVTALRM + {SIGVTALRM, "SIGVTALRM", "Virtual alarm clock"}, +#endif +#ifdef SIGPROF + {SIGPROF, "SIGPROF", "Profiling alarm clock"}, +#endif +#ifdef SIGWINCH + {SIGWINCH, "SIGWINCH", "Window size change"}, +#endif +#ifdef SIGPOLL + {SIGPOLL, "SIGPOLL", "Pollable event occurred"}, +#endif +#ifdef SIGIO + {SIGIO, "SIGIO", "I/O now possible"}, +#endif +#ifdef SIGPWR + {SIGPWR, "SIGPWR", "Power failure restart"}, +#endif +#ifdef SIGSYS + {SIGSYS, "SIGSYS", "Bad system call"}, +#endif +}; +static const char * +signal_name(int signo, const char ** sigdescription) +{ + int j; + for (j=0; j < DIMOF(signal_info); ++j) { + if (signal_info[j].signo == signo) { + if (sigdescription) { + *sigdescription = signal_info[j].sigwords; + } + return signal_info[j].sigdefine; + } + } + if (sigdescription) { + *sigdescription = NULL; + } + return NULL; +} + +/* returns TRUE if 'pid' was registered */ +int +ReportProcHasDied(int pid, int status) +{ + ProcTrack* p; + int signo=0; + int deathbyexit=0; + int deathbysig=0; + int exitcode=0; + int doreport = 0; + int debugreporting = 0; + const char * type; + ProcTrackLogType level; +#ifdef WCOREDUMP + int didcoredump = 0; +#endif + if ((p = GetProcInfo(pid)) == NULL) { + if (DEBUGPROCTRACK) { + cl_log(LOG_DEBUG + , "Process %d died (%d) but is not tracked." + , pid, status); + } + type = "untracked process"; + level = PT_LOGNONE; + }else{ + type = p->ops->proctype(p); + level = p->loglevel; + } + + if (WIFEXITED(status)) { + deathbyexit=1; + exitcode = WEXITSTATUS(status); + }else if (WIFSIGNALED(status)) { + deathbysig=1; + signo = WTERMSIG(status); + doreport=1; + } + switch(level) { + case PT_LOGVERBOSE: doreport=1; + break; + + case PT_LOGNONE: doreport = 0; + break; + + case PT_LOGNORMAL: break; + } + + if (!LoggingIsEnabled) { + doreport = 0; + } +#ifdef WCOREDUMP + if (WCOREDUMP(status)) { + /* Force a report on all core dumping processes */ + didcoredump=1; + doreport=1; + } +#endif + if (DEBUGPROCTRACK && !doreport) { + doreport = 1; + debugreporting = 1; + } + + if (doreport) { + if (deathbyexit) { + cl_log((exitcode == 0 ? LOG_INFO : LOG_WARNING) + , "Managed %s process %d exited with return code %d." + , type, pid, exitcode); + }else if (deathbysig) { + const char * signame = NULL; + const char * sigwords = NULL; + int logtype; + signame = signal_name(signo, &sigwords); + logtype = (debugreporting ? LOG_INFO : LOG_WARNING); + /* + * Processes being killed isn't an error if + * we're only logging because of debugging. + */ + if (signame && sigwords) { + cl_log(logtype + , "Managed %s process %d killed by" + " signal %d [%s - %s]." + , type, pid, signo + , signame, sigwords); + }else{ + cl_log(logtype + , "Managed %s process %d killed by signal %d." + , type, pid, signo); + } + }else{ + cl_log(LOG_ERR, "Managed %s process %d went away" + " strangely (!)" + , type, pid); + } + } +#ifdef WCOREDUMP + if (didcoredump) { + /* We report ALL core dumps without exception */ + cl_log(LOG_ERR, "Managed %s process %d dumped core" + , type, pid); + } +#endif + + if (p) { + RemoveTrackedProcTimeouts(pid); + /* + * From clplumbing/proctrack.h: + * (ProcTrack* p, int status, int signo, int exitcode + * , int waslogged); + */ + p->ops->procdied(p, status, signo, exitcode, doreport); + if (p->privatedata) { + /* They may have forgotten to free something... */ + cl_log(LOG_ERR, "Managed %s process %d did not" + " clean up private data!" + , type, pid); + } + g_hash_table_remove(ProcessTable, GINT_TO_POINTER(pid)); + g_free(p); + } + + return doreport; +} + +/* Return information associated with the given PID (or NULL) */ +ProcTrack* +GetProcInfo(pid_t pid) +{ + return (ProcessTable + ? g_hash_table_lookup(ProcessTable, GINT_TO_POINTER(pid)) + : NULL); +} + +/* "info" is 0-terminated (terminated by a 0 signal) */ +int +SetTrackedProcTimeouts(pid_t pid, ProcTrackKillInfo* info) +{ + long mstimeout; + ProcTrack* pinfo; + pinfo = GetProcInfo(pid); + + if (pinfo == NULL) { + return 0; + } + + pinfo->timeoutseq = 0; + pinfo->killinfo = info; + mstimeout = pinfo->killinfo[0].mstimeout; + pinfo->timerid = Gmain_timeout_add(mstimeout + , TrackedProcTimeoutFunction + , GINT_TO_POINTER(pid)); + return pinfo->timerid; +} + +void +RemoveTrackedProcTimeouts(pid_t pid) +{ + ProcTrack* pinfo; + pinfo = GetProcInfo(pid); + + if (pinfo == NULL) { + return; + } + + if (pinfo->killinfo && pinfo->timerid) { + Gmain_timeout_remove(pinfo->timerid); + } + pinfo->killinfo = NULL; + pinfo->timerid = 0; +} + +static gboolean +TrackedProcTimeoutFunction(gpointer p) +{ + /* This is safe - Pids are relatively small ints */ + pid_t pid = POINTER_TO_SIZE_T(p); /*pointer cast as int*/ + ProcTrack* pinfo; + int nsig; + long mstimeout; + int hadprivs; + + pinfo = GetProcInfo(pid); + + if (pinfo == NULL) { + cl_log(LOG_ERR, "%s: bad pinfo in call (pid %d)", __FUNCTION__, pid); + return FALSE; + } + if (pinfo->timeoutseq < 0 || pinfo->killinfo == NULL) { + cl_log(LOG_ERR + , "%s: bad call (pid %d): killinfo (%d, 0x%lx)" + , __FUNCTION__, pid + , pinfo->timeoutseq + , (unsigned long)POINTER_TO_SIZE_T(pinfo->killinfo)); + return FALSE; + } + + pinfo->timerid = 0; + nsig = pinfo->killinfo[pinfo->timeoutseq].signalno; + + if (nsig == 0) { + if (CL_PID_EXISTS(pid)) { + cl_log(LOG_ERR + , "%s: %s process (PID %d) will not die!" + , __FUNCTION__ + , pinfo->ops->proctype(pinfo) + , (int)pid); + } + return FALSE; + } + pinfo->timeoutseq++; + cl_log(LOG_WARNING, "%s process (PID %d) timed out (try %d)" + ". Killing with signal %s (%d)." + , pinfo->ops->proctype(pinfo), (int)pid + , pinfo->timeoutseq + , signal_name(nsig, NULL) + , nsig); + + if (pinfo->isapgrp && nsig > 0) { + pid = -pid; + } + + if (!(hadprivs = cl_have_full_privs())) { + return_to_orig_privs(); + } + if (kill(pid, nsig) < 0) { + if (errno == ESRCH) { + /* Mission accomplished! */ + cl_log(LOG_INFO, "%s process (PID %d) died before killing (try %d)" + , pinfo->ops->proctype(pinfo), (int)pid + , pinfo->timeoutseq); + return FALSE; + }else{ + cl_perror("%s: kill(%d,%d) failed" + , __FUNCTION__, pid, nsig); + } + } + if (!hadprivs) { + return_to_dropped_privs(); + } + mstimeout = pinfo->killinfo[pinfo->timeoutseq].mstimeout; + pinfo->timerid = Gmain_timeout_add(mstimeout + , TrackedProcTimeoutFunction + , p); + if (pinfo->timerid <= 0) { + cl_log(LOG_ERR, "%s: Could not add new kill timer [%u]" + , __FUNCTION__, pinfo->timerid); + kill(pid, SIGKILL); + } + if (debugproctrack) { + cl_log(LOG_DEBUG, "%s process (PID %d) scheduled to be killed again" + " (try %d) in %ld ms [timerid %u]" + , pinfo->ops->proctype(pinfo), (int)pid + , pinfo->timeoutseq + , mstimeout + , pinfo->timerid); + } + return FALSE; +} + +/* Helper struct to allow us to stuff 3 args into one ;-) */ +struct prochelper { + ProcTrack_ops* type; + ProcTrackFun fun; + void* data; +}; + +/* Helper function to call user's function with right args... */ +static void +ForEachProcHelper(gpointer key, gpointer value, void * helper) +{ + ProcTrack* p = value; + struct prochelper* ph = helper; + + if (ph->type != NULL && ph->type != p->ops) { + return; + } + + ph->fun(p, ph->data); +} +/* + * Iterate over the set of tracked processes. + * If proctype is NULL, then walk through them all, otherwise only those + * of the given type. + */ +void +ForEachProc(ProcTrack_ops* proctype, ProcTrackFun f, void * data) +{ + struct prochelper ph; + + InitProcTable(); + ph.fun = f; + ph.type = proctype; + ph.data = data; + g_hash_table_foreach(ProcessTable, ForEachProcHelper, &ph); +} + +void +DisableProcLogging() +{ + LoggingIsEnabled = 0; +} + +void +EnableProcLogging() +{ + LoggingIsEnabled = 1; +} diff --git a/lib/clplumbing/realtime.c b/lib/clplumbing/realtime.c new file mode 100644 index 0000000..9271204 --- /dev/null +++ b/lib/clplumbing/realtime.c @@ -0,0 +1,354 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +/* The BSD's do not use malloc.h directly. */ +/* They use stdlib.h instead */ +#ifndef BSD +#ifdef HAVE_MALLOC_H +# include +#endif +#endif +#include +#ifdef _POSIX_MEMLOCK +# include +# include +# include +#endif +#ifdef _POSIX_PRIORITY_SCHEDULING +# include +#endif +#include +#include +#include +#include +#include +#include + +static gboolean cl_realtimepermitted = TRUE; +static void cl_rtmalloc_setup(void); + +#define HOGRET 0xff +/* + * Slightly wacko recursive function to touch requested amount + * of stack so we have it pre-allocated inside our realtime code + * as per suggestion from mlockall(2) + */ +#ifdef _POSIX_MEMLOCK +static unsigned char +cl_stack_hogger(unsigned char * inbuf, int kbytes) +{ + unsigned char buf[1024]; + + if (inbuf == NULL) { + memset(buf, HOGRET, sizeof(buf)); + }else{ + memcpy(buf, inbuf, sizeof(buf)); + } + + if (kbytes > 0) { + return cl_stack_hogger(buf, kbytes-1); + }else{ + return buf[sizeof(buf)-1]; + } +/* #else + return HOGRET; +*/ +} +#endif +/* + * We do things this way to hopefully defeat "smart" malloc code which + * handles large mallocs as special cases using mmap(). + */ +static void +cl_malloc_hogger(int kbytes) +{ + long size = kbytes * 1024; + int chunksize = 1024; + long nchunks = (int)(size / chunksize); + int chunkbytes = nchunks * sizeof(void *); + void** chunks; + int j; + +#ifdef HAVE_MALLOPT +# ifdef M_MMAP_MAX + /* Keep malloc from using mmap */ + mallopt(M_MMAP_MAX, 0); +#endif +# ifdef M_TRIM_THRESHOLD + /* Keep malloc from giving memory back to the system */ + mallopt(M_TRIM_THRESHOLD, -1); +# endif +#endif + chunks=malloc(chunkbytes); + if (chunks == NULL) { + cl_log(LOG_INFO, "Could not preallocate (%d) bytes" + , chunkbytes); + return; + } + memset(chunks, 0, chunkbytes); + + for (j=0; j < nchunks; ++j) { + chunks[j] = malloc(chunksize); + if (chunks[j] == NULL) { + cl_log(LOG_INFO, "Could not preallocate (%d) bytes" + , chunksize); + }else{ + memset(chunks[j], 0, chunksize); + } + } + for (j=0; j < nchunks; ++j) { + if (chunks[j]) { + free(chunks[j]); + chunks[j] = NULL; + } + } + free(chunks); + chunks = NULL; +} + +/* + * Make us behave like a soft real-time process. + * We need scheduling priority and being locked in memory. + * If you ask us nicely, we'll even grow the stack and heap + * for you before locking you into memory ;-). + */ +void +cl_make_realtime(int spolicy, int priority, int stackgrowK, int heapgrowK) +{ +#ifdef DEFAULT_REALTIME_POLICY + struct sched_param sp; + int staticp; +#endif + + if (heapgrowK > 0) { + cl_malloc_hogger(heapgrowK); + } + +#ifdef _POSIX_MEMLOCK + if (stackgrowK > 0) { + unsigned char ret; + if ((ret=cl_stack_hogger(NULL, stackgrowK)) != HOGRET) { + cl_log(LOG_INFO, "Stack hogger failed 0x%x" + , ret); + } + } +#endif + cl_rtmalloc_setup(); + + if (!cl_realtimepermitted) { + cl_log(LOG_INFO + , "Request to set pid %ld to realtime ignored." + , (long)getpid()); + return; + } + +#ifdef DEFAULT_REALTIME_POLICY + if (spolicy < 0) { + spolicy = DEFAULT_REALTIME_POLICY; + } + + if (priority <= 0) { + priority = sched_get_priority_min(spolicy); + } + + if (priority > sched_get_priority_max(spolicy)) { + priority = sched_get_priority_max(spolicy); + } + + + if ((staticp=sched_getscheduler(0)) < 0) { + cl_perror("unable to get scheduler parameters."); + }else{ + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = priority; + + if (sched_setscheduler(0, spolicy, &sp) < 0) { + cl_perror("Unable to set scheduler parameters."); + } + } +#endif + +#if defined _POSIX_MEMLOCK +# ifdef RLIMIT_MEMLOCK +# define THRESHOLD(lim) (((lim))/2) + { + unsigned long growsize = ((stackgrowK+heapgrowK)*1024); + struct rlimit memlocklim; + + getrlimit(RLIMIT_MEMLOCK, &memlocklim); /* Allow for future added fields */ + memlocklim.rlim_max = RLIM_INFINITY; + memlocklim.rlim_cur = RLIM_INFINITY; + /* Try and remove memory locking limits -- if we can */ + if (setrlimit(RLIMIT_MEMLOCK, &memlocklim) < 0) { + /* Didn't work - get what we can */ + getrlimit(RLIMIT_MEMLOCK, &memlocklim); + memlocklim.rlim_cur = memlocklim.rlim_max; + setrlimit(RLIMIT_MEMLOCK, &memlocklim); + } + + /* Could we get 'enough' ? */ + /* (this is a guess - might not be right if we're not root) */ + if (getrlimit(RLIMIT_MEMLOCK, &memlocklim) >= 0 + && memlocklim.rlim_cur != RLIM_INFINITY + && (growsize >= THRESHOLD(memlocklim.rlim_cur))) { + cl_log(LOG_ERR + , "Cannot lock ourselves into memory: System limits" + " on locked-in memory are too small."); + return; + } + } +# endif /*RLIMIT_MEMLOCK*/ + if (mlockall(MCL_CURRENT|MCL_FUTURE) >= 0) { + if (ANYDEBUG) { + cl_log(LOG_DEBUG, "pid %d locked in memory.", (int) getpid()); + } + + } else if(errno == ENOSYS) { + const char *err = strerror(errno); + cl_log(LOG_WARNING, "Unable to lock pid %d in memory: %s", + (int) getpid(), err); + + } else { + cl_perror("Unable to lock pid %d in memory", (int) getpid()); + } +#endif +} + +void +cl_make_normaltime(void) +{ +#ifdef DEFAULT_REALTIME_POLICY + struct sched_param sp; + + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = sched_get_priority_min(SCHED_OTHER); + if (sched_setscheduler(0, SCHED_OTHER, &sp) < 0) { + cl_perror("unable to (re)set scheduler parameters."); + } +#endif +#ifdef _POSIX_MEMLOCK + /* Not strictly necessary. */ + munlockall(); +#endif +} + +void +cl_disable_realtime(void) +{ + cl_realtimepermitted = FALSE; +} + +void +cl_enable_realtime(void) +{ + cl_realtimepermitted = TRUE; +} + +/* Give up the CPU for a little bit */ +/* This is similar to sched_yield() but allows lower prio processes to run */ +int +cl_shortsleep(void) +{ + static const struct timespec req = {0,2000001L}; + + return nanosleep(&req, NULL); +} + + +static int post_rt_morecore_count = 0; +static unsigned long init_malloc_arena = 0L; + +#ifdef HAVE_MALLINFO +# define MALLOC_TOTALSIZE() (((unsigned long)mallinfo().arena)+((unsigned long)mallinfo().hblkhd)) +#else +# define MALLOC_TOTALSIZE() (0L) +#endif + + + +/* Return the number of times we went after more core */ +int +cl_nonrealtime_malloc_count(void) +{ + return post_rt_morecore_count; +} +unsigned long +cl_nonrealtime_malloc_size(void) +{ + return (MALLOC_TOTALSIZE() - init_malloc_arena); +} +/* Log the number of times we went after more core */ +void +cl_realtime_malloc_check(void) +{ + static int lastcount = 0; + static unsigned long oldarena = 0UL; + + if (oldarena == 0UL) { + oldarena = init_malloc_arena; + } + + if (post_rt_morecore_count > lastcount) { + + if (MALLOC_TOTALSIZE() > oldarena) { + + cl_log(LOG_WARNING, + "Performed %d more non-realtime malloc calls.", + post_rt_morecore_count - lastcount); + + cl_log(LOG_INFO, + "Total non-realtime malloc bytes: %ld", + MALLOC_TOTALSIZE() - init_malloc_arena); + oldarena = MALLOC_TOTALSIZE(); + + } + + lastcount = post_rt_morecore_count; + } +} + +#ifdef HAVE___DEFAULT_MORECORE + +static void (*our_save_morecore_hook)(void) = NULL; +static void cl_rtmalloc_morecore_fun(void); + +static void +cl_rtmalloc_morecore_fun(void) +{ + post_rt_morecore_count++; + if (our_save_morecore_hook) { + our_save_morecore_hook(); + } +} +#endif + +static void +cl_rtmalloc_setup(void) +{ + static gboolean inityet = FALSE; + if (!inityet) { + init_malloc_arena = MALLOC_TOTALSIZE(); +#ifdef HAVE___DEFAULT_MORECORE + our_save_morecore_hook = __after_morecore_hook; + __after_morecore_hook = cl_rtmalloc_morecore_fun; + inityet = TRUE; +#endif + } + } diff --git a/lib/clplumbing/replytrack.c b/lib/clplumbing/replytrack.c new file mode 100644 index 0000000..8c7c38e --- /dev/null +++ b/lib/clplumbing/replytrack.c @@ -0,0 +1,643 @@ + +/* + * Reply tracking library. + * + * Copyright (c) 2007 Alan Robertson + * Author: Alan Robertson + * + ****************************************************************** + * This library is useful for tracking replies to multicast messages + * sent to cluster members. It tracks incremental membership changes + * according to any desired criteria, and then keeps track of when + * the last expected reply is received according to the dynamically + * updated membership as of when the message was sent out. + ****************************************************************** + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * These are the only data items that go in our GHashTables + */ +struct rt_node_info { + char * nodename; + cl_uuid_t nodeid; +}; + +struct node_tables { + + GHashTable* uuidmap; /* Keyed by uuid */ + int uuidcount; + GHashTable* namemap; /* Keyed by nodename*/ + int namecount; +}; +struct _nodetrack { + struct node_tables nt; + int refcount; + nodetrack_callback_t callback; + gpointer user_data; + nodetrack_callback_t extra_callback; + gpointer ext_data; +}; + +/* + * Things we use to track outstanding replies + * This is the structure used by the replytrack_t typedef + */ +struct _replytrack { + replytrack_callback_t callback; + gpointer user_data; + unsigned timerid; + struct node_tables tables; + gboolean expectingmore; + nodetrack_t* membership; +}; + +struct _nodetrack_intersection { + nodetrack_t** tables; + int ntables; + nodetrack_callback_t callback; + gpointer user_data; + nodetrack_t* intersection; +}; + +static cl_uuid_t nulluuid; +static int nodetrack_t_count = 0; +static int replytrack_t_count = 0; +static int replytrack_intersection_t_count = 0; + +static struct rt_node_info * +rt_node_info_new(const char * nodename, cl_uuid_t nodeid) +{ + struct rt_node_info* ret; + + if (!nodename) { + return NULL; + } + ret = MALLOCT(struct rt_node_info); + + if (!ret) { + return ret; + } + ret->nodename = strdup(nodename); + if (!ret->nodename) { + free(ret); + ret = NULL; + return ret; + } + ret->nodeid = nodeid; + return ret; +} + +static void +rt_node_info_del(struct rt_node_info * ni) +{ + if (ni != NULL) { + if (ni->nodename != NULL) { + free(ni->nodename); + } + memset(ni, 0, sizeof(*ni)); + free(ni); + } +} + +/* + * namehash cannot be NULL, idhash cannot be NULL, and nodename cannot be NULL + * + * 'id' can be a NULL uuid, in which case it goes into only the name table + * 'nodename' can change over time - in which case we update our tables. + * It is possible for one nodename to have more than one uuid. + * We allow for that. + * + * Changing the uuid but keeping the nodename the same is considered to be + * adding a new node with the same nodename. + * Exception: A node with a null uuid is presumed to have acquired a proper + * uuid if it is later seen with a non-null UUID + */ + +static gboolean +del_node_from_hashtables(struct node_tables *t +, const char * nodename, cl_uuid_t id) +{ + struct rt_node_info * entry; + if (cl_uuid_is_null(&id)) { + if ((entry = g_hash_table_lookup(t->namemap,nodename))!=NULL){ + g_hash_table_remove(t->namemap, nodename); + rt_node_info_del(entry); + t->namecount--; + } + return TRUE; + } + if ((entry=g_hash_table_lookup(t->uuidmap, &id)) != NULL) { + g_hash_table_remove(t->uuidmap, &id); + rt_node_info_del(entry); + t->uuidcount--; + } + return TRUE; +} + + +static gboolean +add_node_to_hashtables(struct node_tables * t +, const char * nodename, cl_uuid_t id) +{ + struct rt_node_info* idinfo = NULL; + + if (cl_uuid_is_null(&id)) { + /* Supplied uuid is the null UUID - insert in name table */ + struct rt_node_info* ninfo; + if (g_hash_table_lookup(t->namemap, nodename) == NULL) { + if (NULL == (ninfo = rt_node_info_new(nodename, id))){ + goto outofmem; + } + g_hash_table_insert(t->namemap,ninfo->nodename,ninfo); + t->namecount++; + } + return TRUE; + } + + /* Supplied uuid is not the null UUID */ + + if (g_hash_table_lookup(t->uuidmap,&id) == NULL) { + /* See if a corresponding name is in name map */ + /* If so, delete it - assume uuid was missing before */ + + if (g_hash_table_lookup(t->namemap, nodename) != NULL) { + del_node_from_hashtables(t, nodename, nulluuid); + } + /* Not yet in our uuid hash table */ + idinfo = rt_node_info_new(nodename, id); + if (idinfo == NULL) { + goto outofmem; + } + g_hash_table_insert(t->uuidmap, &idinfo->nodeid, idinfo); + t->uuidcount++; + } + return TRUE; +outofmem: + cl_log(LOG_ERR, "%s: out of memory", __FUNCTION__); + return FALSE; +} + +static gboolean +create_new_hashtables(struct node_tables*t) +{ + t->namemap = g_hash_table_new(g_str_hash, g_str_equal); + if (t->namemap == NULL) { + return FALSE; + } + t->uuidmap = g_hash_table_new(cl_uuid_g_hash, cl_uuid_g_equal); + if (t->uuidmap == NULL) { + g_hash_table_destroy(t->namemap); + t->namemap = NULL; + return FALSE; + } + return TRUE; +} + +static gboolean +hashtable_destroy_rt_node_info(gpointer key, gpointer rti, gpointer unused) +{ + rt_node_info_del(rti); + rti = key = NULL; + return TRUE; +} + +static void +destroy_map_hashtable(GHashTable*t) +{ + g_hash_table_foreach_remove(t, hashtable_destroy_rt_node_info,NULL); + g_hash_table_destroy(t); + t = NULL; +} + +struct tablehelp { + struct node_tables* t; + gboolean ret; +}; + +static void +copy_hashtables_helper(gpointer key_unused, gpointer value +, gpointer user_data) +{ + struct tablehelp * th = user_data; + struct rt_node_info* ni = value; + if (!add_node_to_hashtables(th->t, ni->nodename, ni->nodeid)) { + th->ret = FALSE; + } +} + +static gboolean +copy_hashtables(struct node_tables* tin, struct node_tables* tout) +{ + struct tablehelp newtables; + if (!create_new_hashtables(tout)){ + return FALSE; + } + newtables.t = tout; + newtables.ret = TRUE; + + g_hash_table_foreach(tout->namemap,copy_hashtables_helper,&newtables); + if (!newtables.ret) { + return FALSE; + } + g_hash_table_foreach(tout->uuidmap,copy_hashtables_helper,&newtables); + return newtables.ret; +} + +static gboolean mbr_inityet = FALSE; +static void +init_global_membership(void) +{ + if (mbr_inityet) { + return; + } + mbr_inityet = TRUE; + memset(&nulluuid, 0, sizeof(nulluuid)); +} + +gboolean /* Call us when an expected replier joins / comes up */ +nodetrack_nodeup(nodetrack_t * mbr, const char * node, cl_uuid_t uuid) +{ + gboolean ret; + ret = add_node_to_hashtables(&mbr->nt, node, uuid); + if (ret && mbr->callback) { + mbr->callback(mbr, node, uuid, NODET_UP, mbr->user_data); + } + if (mbr->extra_callback) { + mbr->extra_callback(mbr, node, uuid, NODET_UP,mbr->ext_data); + } + return ret; +} + +gboolean /* Call us when an expected replier goes down / away */ +nodetrack_nodedown(nodetrack_t* mbr, const char* node, cl_uuid_t uuid) +{ + if (mbr->callback) { + mbr->callback(mbr, node, uuid, NODET_DOWN, mbr->user_data); + } + if (mbr->extra_callback) { + mbr->extra_callback(mbr, node,uuid,NODET_DOWN,mbr->ext_data); + } + return del_node_from_hashtables(&mbr->nt, node, uuid); +} + +/* This function calls the user's timeout callback */ +static gboolean +replytrack_timeout_helper(gpointer rldata) +{ + replytrack_t* rl = rldata; + rl->expectingmore = FALSE; + rl->timerid = 0; + if (rl->callback) { + rl->callback(rl, rl->user_data, REPLYT_TIMEOUT); + } + return FALSE; +} + +replytrack_t* /* replytrack_t constructor */ +replytrack_new(nodetrack_t * membership +, replytrack_callback_t callback +, unsigned long timeout_ms +, gpointer user_data) +{ + replytrack_t* ret = MALLOCT(replytrack_t); + if (!ret) { + return ret; + } + if (!copy_hashtables(&membership->nt, &ret->tables)) { + free(ret); + ret = NULL; + return ret; + } + replytrack_t_count++; + ret->membership = membership; + ret->membership->refcount++; + ret->callback = callback; + ret->user_data = user_data; + ret->expectingmore = TRUE; + ret->timerid = 0; + if (timeout_ms != 0 && callback != NULL) { + ret->timerid = Gmain_timeout_add(timeout_ms + , replytrack_timeout_helper, ret); + } + return ret; +} + +void /* replytrack_t destructor */ +replytrack_del(replytrack_t * rl) +{ + rl->membership->refcount--; + replytrack_t_count++; + if (rl->expectingmore && rl->timerid > 0) { + cl_log(LOG_INFO + , "%s: destroying replytrack while still expecting" + " %d replies" + , __FUNCTION__ + , (rl->tables.namecount + rl->tables.uuidcount)); + } + if (rl->timerid > 0) { + g_source_remove(rl->timerid); + rl->timerid = 0; + } + destroy_map_hashtable(rl->tables.namemap); + rl->tables.namemap=NULL; + destroy_map_hashtable(rl->tables.uuidmap); + rl->tables.uuidmap=NULL; + memset(&rl, 0, sizeof(rl)); + free(rl); + rl=NULL; +} + +gboolean /* Call replytrack_gotreply when you receive an expected reply */ +replytrack_gotreply(replytrack_t*rl, const char * node, cl_uuid_t uuid) +{ + gboolean lastone; + del_node_from_hashtables(&rl->tables, node, uuid); + lastone = (rl->tables.namecount + rl->tables.uuidcount) == 0; + if (lastone) { + rl->expectingmore = FALSE; + if (rl->timerid > 0) { + g_source_remove(rl->timerid); + rl->timerid = 0; + } + if (rl->callback){ + rl->callback(rl, rl->user_data, REPLYT_ALLRCVD); + } + } + return lastone; +} + +struct replytrack_iterator_data { + replytrack_t* rlist; + replytrack_iterator_t f; + int count; + gpointer user_data; +}; + + +static void /* g_hash_table user-level iteration helper */ +replytrack_iterator_helper(gpointer key_unused, gpointer entry +, gpointer user_data) +{ + struct replytrack_iterator_data* ri = user_data; + struct rt_node_info* ni = entry; + if (ri && ri->rlist) { + ++ri->count; + if (ri->f) { + ri->f(ri->rlist, ri->user_data + , ni->nodename, ni->nodeid); + } + } +} + + + +int /* iterate through the outstanding expected replies */ +replytrack_outstanding_iterate(replytrack_t* rl +, replytrack_iterator_t i, gpointer user_data) +{ + struct replytrack_iterator_data id; + id.rlist = rl; + id.f = i; + id.count = 0; + id.user_data = user_data; + g_hash_table_foreach(rl->tables.namemap, replytrack_iterator_helper + , &id); + g_hash_table_foreach(rl->tables.uuidmap, replytrack_iterator_helper + , &id); + if (id.count != (rl->tables.namecount + rl->tables.uuidcount)) { + cl_log(LOG_ERR + , "%s: iteration count %d disagrees with" + " (namecount %d+uuidcount %d)" + , __FUNCTION__, id.count + , rl->tables.namecount,rl->tables.uuidcount); + } + return id.count; +} +int /* return count of outstanding expected replies */ +replytrack_outstanding_count(replytrack_t* rl) +{ + return (rl->tables.namecount + rl->tables.uuidcount); +} + +nodetrack_t* +nodetrack_new(nodetrack_callback_t callback, gpointer user_data) +{ + nodetrack_t* ret = MALLOCT(nodetrack_t); + if (!mbr_inityet) { + init_global_membership(); + } + if (!ret) { + return ret; + } + nodetrack_t_count++; + ret->refcount = 0; + if (!create_new_hashtables(&ret->nt)) { + free(ret); + ret = NULL; + } + ret->user_data = user_data; + ret->callback = callback; + ret->extra_callback = NULL; + ret->ext_data = NULL; + return ret; +} +void +nodetrack_del(nodetrack_t * np) +{ + if (np->refcount) { + cl_log(LOG_ERR + , "%s: reply tracking reference count is %d" + , __FUNCTION__, np->refcount); + } + nodetrack_t_count--; + destroy_map_hashtable(np->nt.namemap); + np->nt.namemap=NULL; + destroy_map_hashtable(np->nt.uuidmap); + np->nt.uuidmap=NULL; + memset(np, 0, sizeof(*np)); + free(np); +} + +gboolean +nodetrack_ismember(nodetrack_t* mbr, const char * name, cl_uuid_t u) +{ + if (cl_uuid_is_null(&u)) { + return(g_hash_table_lookup(mbr->nt.namemap, name) != NULL); + } + return (g_hash_table_lookup(mbr->nt.uuidmap, &u) != NULL); +} + +struct nodetrack_iterator_data { + nodetrack_t* rlist; + nodetrack_iterator_t f; + int count; + gpointer user_data; +}; +static void /* g_hash_table user-level iteration helper */ +nodetrack_iterator_helper(gpointer key_unused, gpointer entry +, gpointer user_data) +{ + struct nodetrack_iterator_data* ri = user_data; + struct rt_node_info* ni = entry; + if (ri && ri->rlist) { + ++ri->count; + if (ri->f) { + ri->f(ri->rlist, ri->user_data + , ni->nodename, ni->nodeid); + } + } +} + +int /* iterate through the outstanding expected replies */ +nodetrack_iterate(nodetrack_t* rl +, nodetrack_iterator_t i, gpointer user_data) +{ + struct nodetrack_iterator_data id; + id.rlist = rl; + id.f = i; + id.count = 0; + id.user_data = user_data; + g_hash_table_foreach(rl->nt.namemap, nodetrack_iterator_helper + , &id); + g_hash_table_foreach(rl->nt.uuidmap, nodetrack_iterator_helper + , &id); + if (id.count != (rl->nt.namecount + rl->nt.uuidcount)) { + cl_log(LOG_ERR + , "%s: iteration count %d disagrees with" + " (namecount %d+uuidcount %d)" + , __FUNCTION__, id.count + , rl->nt.namecount,rl->nt.uuidcount); + } + return id.count; +} +static void +intersection_callback +( nodetrack_t * mbr +, const char * node +, cl_uuid_t u +, nodetrack_change_t reason +, gpointer user_data) +{ + nodetrack_intersection_t* it = user_data; + int j; + gboolean allfound = TRUE; + + if (reason == NODET_DOWN) { + if (nodetrack_ismember(it->intersection, node, u)) { + nodetrack_nodedown(it->intersection,node,u); + } + return; + } + for (j=0; j < it->ntables && allfound; ++j) { + if (nodetrack_ismember(it->tables[j], node, u)) { + allfound = FALSE; + } + } + if (allfound) { + nodetrack_nodeup(it->intersection, node, u); + } +} + +struct li_helper { + nodetrack_intersection_t* i; + gboolean result; +}; + +static void +intersection_init_iterator(nodetrack_t* nt +, gpointer ghelp +, const char* node +, cl_uuid_t uuid) +{ + struct li_helper* help = ghelp; + gboolean allfound = TRUE; + int j; + + for (j=1; allfound && j < help->i->ntables; ++j) { + if (!nodetrack_ismember(help->i->tables[j] + , node, uuid)) { + allfound = FALSE; + } + } + if (allfound) { + nodetrack_nodeup(help->i->intersection, node, uuid); + } +} + +nodetrack_intersection_t* +nodetrack_intersection_new(nodetrack_t** tables, int ntables +, nodetrack_callback_t callback, gpointer user_data) +{ + nodetrack_intersection_t* ret; + int j; + ret = MALLOCT(nodetrack_intersection_t); + if (!ret) { + return ret; + } + ret->intersection = nodetrack_new(callback, user_data); + if (!ret->intersection) { + free(ret); + ret = NULL; + return ret; + } + ret->tables = tables; + ret->ntables = ntables; + ret->callback = callback; + ret->user_data = user_data; + for (j=0; j < ntables; ++j) { + tables[j]->refcount ++; + tables[j]->ext_data = ret; + tables[j]->extra_callback = intersection_callback; + } + /* Initialize the intersection membership list */ + nodetrack_iterate(tables[0], intersection_init_iterator, ret); + replytrack_intersection_t_count++; + return ret; +} +void +nodetrack_intersection_del(nodetrack_intersection_t* p) +{ + int j; + + for (j=0; j < p->ntables; ++j) { + p->tables[j]->refcount ++; + } + nodetrack_del(p->intersection); + p->intersection = NULL; + memset(p, 0, sizeof(*p)); + free(p); + p = NULL; + replytrack_intersection_t_count--; +} + +nodetrack_t* +nodetrack_intersection_table(nodetrack_intersection_t*p) +{ + return p->intersection; +} diff --git a/lib/clplumbing/setproctitle.c b/lib/clplumbing/setproctitle.c new file mode 100644 index 0000000..ffc5481 --- /dev/null +++ b/lib/clplumbing/setproctitle.c @@ -0,0 +1,235 @@ +/* + * setproctitle.c + * + * The code in this file, setproctitle.c is heavily based on code from + * proftpd, please see the licening information below. + * + * This file added to the heartbeat tree by Horms + * + * Code to portably change the title of a programme as displayed + * by ps(1). + * + * heartbeat: Linux-HA heartbeat code + * + * Copyright (C) 1999,2000,2001 Alan Robertson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * ProFTPD - FTP server daemon + * Copyright (c) 1997, 1998 Public Flood Software + * Copyright (C) 1999, 2000 MacGyver aka Habeeb J. Dihu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu + * and other respective copyright holders give permission to link this program + * with OpenSSL, and distribute the resulting executable, without including + * the source code for OpenSSL in the source distribution. + */ + +#include + +#include +#include +#include +#include +#include + +#define PF_ARGV_NONE 0 +#define PF_ARGV_NEW 1 +#define PF_ARGV_WRITEABLE 2 +#define PF_ARGV_PSTAT 3 +#define PF_ARGV_PSSTRINGS 4 + +#if PF_ARGV_TYPE == PF_ARGV_PSTAT +# include +#endif + +#include + +#if PF_ARGV_TYPE != PF_ARGV_NONE +static char **Argv = NULL; +static char *LastArgv = NULL; +#endif /* PF_ARGV_TYPE != PF_ARGV_NONE */ + +extern char **environ; + +#ifdef HAVE___PROGNAME +extern char *__progname; +extern char *__progname_full; +#endif /* HAVE___PROGNAME */ + +int +init_set_proc_title(int argc, char *argv[], char *envp[]) +{ +#if PF_ARGV_TYPE == PF_ARGV_NONE + return 0; +#else + int i; + int envpsize; + char **p; + + /* Move the environment so setproctitle can use the space. + */ + for(i = envpsize = 0; envp[i] != NULL; i++) { + envpsize += strlen(envp[i]) + 1; + } + + p = (char **) malloc((i + 1) * sizeof(char *)); + if (p == NULL) { + return -1; + } + + environ = p; + + for(i = 0; envp[i] != NULL; i++) { + environ[i] = strdup(envp[i]); + if(environ[i] == NULL) { + goto error_environ; + } + } + environ[i] = NULL; + + Argv = argv; + + for(i = 0; i < argc; i++) { + if(!i || (LastArgv + 1 == argv[i])) + LastArgv = argv[i] + strlen(argv[i]); + } + + for(i = 0; envp[i] != NULL; i++) { + if((LastArgv + 1) == envp[i]) { + LastArgv = envp[i] + strlen(envp[i]); + } + } + +#ifdef HAVE___PROGNAME + /* Set the __progname and __progname_full variables so glibc and + * company don't go nuts. - MacGyver + */ + + __progname = strdup("heartbeat"); + if (__progname == NULL) { + goto error_environ; + } + __progname_full = strdup(argv[0]); + if (__progname_full == NULL) { + goto error_environ; + } +#endif /* HAVE___PROGNAME */ + + return 0; + +error_environ: + for(i = 0; environ[i] != NULL; i++) { + free(environ[i]); + } + free(environ); + return -1; +#endif /* PF_ARGV_TYPE == PF_ARGV_NONE */ +} + +void set_proc_title(const char *fmt,...) +{ +#if PF_ARGV_TYPE != PF_ARGV_NONE + va_list msg; + static char statbuf[BUFSIZ]; + +#ifndef HAVE_SETPROCTITLE +#if PF_ARGV_TYPE == PF_ARGV_PSTAT + union pstun pst; +#endif /* PF_ARGV_PSTAT */ + int i,maxlen = (LastArgv - Argv[0]) - 2; + char *p; +#endif /* HAVE_SETPROCTITLE */ + + va_start(msg,fmt); + + memset(statbuf, 0, sizeof(statbuf)); + + +#ifdef HAVE_SETPROCTITLE +# if __FreeBSD__ >= 4 && !defined(FREEBSD4_0) && !defined(FREEBSD4_1) + /* FreeBSD's setproctitle() automatically prepends the process name. */ + vsnprintf(statbuf, sizeof(statbuf), fmt, msg); + +# else /* FREEBSD4 */ + /* Manually append the process name for non-FreeBSD platforms. */ + vsnprintf(statbuf + strlen(statbuf), sizeof(statbuf) - strlen(statbuf), + fmt, msg); + +# endif /* FREEBSD4 */ + setproctitle("%s", statbuf); + +#else /* HAVE_SETPROCTITLE */ + /* Manually append the process name for non-setproctitle() platforms. */ + vsnprintf(statbuf + strlen(statbuf), sizeof(statbuf) - strlen(statbuf), + fmt, msg); + +#endif /* HAVE_SETPROCTITLE */ + + va_end(msg); + +#ifdef HAVE_SETPROCTITLE + return; +#else + i = strlen(statbuf); + +#if PF_ARGV_TYPE == PF_ARGV_NEW + /* We can just replace argv[] arguments. Nice and easy. + */ + Argv[0] = statbuf; + Argv[1] = NULL; +#endif /* PF_ARGV_NEW */ + +#if PF_ARGV_TYPE == PF_ARGV_WRITEABLE + /* We can overwrite individual argv[] arguments. Semi-nice. + */ + snprintf(Argv[0], maxlen, "%s", statbuf); + p = &Argv[0][i]; + + while(p < LastArgv) + *p++ = '\0'; + Argv[1] = NULL; +#endif /* PF_ARGV_WRITEABLE */ + +#if PF_ARGV_TYPE == PF_ARGV_PSTAT + pst.pst_command = statbuf; + pstat(PSTAT_SETCMD, pst, i, 0, 0); +#endif /* PF_ARGV_PSTAT */ + +#if PF_ARGV_TYPE == PF_ARGV_PSSTRINGS + PS_STRINGS->ps_nargvstr = 1; + PS_STRINGS->ps_argvstr = statbuf; +#endif /* PF_ARGV_PSSTRINGS */ + +#endif /* HAVE_SETPROCTITLE */ + +#endif /* PF_ARGV_TYPE != PF_ARGV_NONE */ +} diff --git a/lib/clplumbing/timers.c b/lib/clplumbing/timers.c new file mode 100644 index 0000000..c3e99da --- /dev/null +++ b/lib/clplumbing/timers.c @@ -0,0 +1,119 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int +setmsrepeattimer(long ms) +{ + long secs = ms / 1000L; + long usecs = (ms % 1000L)*1000L; + struct itimerval nexttime = + { {secs, usecs} /* Repeat Interval */ + , {secs, usecs} /* Timer Value */ + }; + +#if 0 + cl_log(LOG_DEBUG, "Setting repeating timer for %ld ms" + , ms); +#endif + + + /* Is this right??? */ + CL_IGNORE_SIG(SIGALRM); + return setitimer(ITIMER_REAL, &nexttime, NULL); +} + +int +setmsalarm(long ms) +{ + long secs = ms / 1000L; + long usecs = (ms % 1000L)*1000L; + struct itimerval nexttime = + { {0L, 0L} /* Repeat Interval */ + , {secs, usecs} /* Timer Value */ + }; + + return setitimer(ITIMER_REAL, &nexttime, NULL); +} + +int +cancelmstimer(void) +{ + struct itimerval nexttime = + { {0L, 0L} /* Repeat Interval */ + , {0L, 0L} /* Timer Value */ + }; + return setitimer(ITIMER_REAL, &nexttime, NULL); +} + + +static int alarmpopped = 0; + +static void +st_timer_handler(int nsig) +{ + ++alarmpopped; +} + +/* + * Pretty simple: + * 1) Set up SIGALRM signal handler + * 2) set alarmpopped to FALSE; + * 2) Record current time + * 3) Call setmsalarm(ms) + * 4) Call pause(2) + * 5) Call cancelmstimer() + * 6) Reset signal handler + * 7) See if SIGALRM happened + * if so: return zero + * if not: get current time, and compute milliseconds left 'til signal + * should arrive, and return that... + */ +long +mssleep(long ms) +{ + struct sigaction saveaction; + longclock_t start; + longclock_t finish; + unsigned long elapsedms; + + memset(&saveaction, 0, sizeof(saveaction)); + + cl_signal_set_simple_handler(SIGALRM, st_timer_handler, &saveaction); + alarmpopped = 0; + start = time_longclock(); + setmsalarm(ms); + pause(); + cancelmstimer(); + cl_signal_set_simple_handler(SIGALRM, saveaction.sa_handler, &saveaction); + if (alarmpopped) { + return 0; + } + + finish = time_longclock(); + elapsedms = longclockto_ms(sub_longclock(finish, start)); + return ms - elapsedms; +} diff --git a/lib/clplumbing/transient-test.sh b/lib/clplumbing/transient-test.sh new file mode 100755 index 0000000..7da88bf --- /dev/null +++ b/lib/clplumbing/transient-test.sh @@ -0,0 +1,120 @@ +#!/bin/sh +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# +####FIXME +# Known problems within this testing: +# 1. Doesn't reflect "someone else's message" problems into error count. +# 2. Path to "ipctransient{client,server}" not flexible enough. + +no_logs=0 +exit_on_error=1 +num_servers=10 +num_clients=10 +client_time=2 +commpath_args="" + +cmd=`basename $0` +USAGE="Usage: $cmd [-c clients] [-s servers] [-t timetowait] [-C commpath]" + +while getopts c:s:C:t: c +do + case $c in + c) + num_clients=$OPTARG + ;; + s) + num_servers=$OPTARG + ;; + t) + client_time=$OPTARG + ;; + C) + commpath_args="-$c $OPTARG" + ;; + \?) + echo $USAGE + exit 2 + ;; + esac +done +shift `expr $OPTIND - 1` + +total_failed=0 + +server_failed=0 +server_loop_cnt=0 +while [ $server_loop_cnt != $num_servers ]; do + echo "############ DEBUG: Starting server iter $server_loop_cnt" + if [ $no_logs = 1 ]; then + ./ipctransientserver $commpath_args > /dev/null 2>&1 & + else + ./ipctransientserver $commpath_args & + fi + server_pid=$! + + sleep 5 + + client_failed=0 + client_loop_cnt=0 + while [ $client_loop_cnt != $num_clients ]; do + sleep 5 + echo "############ DEBUG: Starting client iter $client_loop_cnt" + if [ $no_logs = 1 ]; then + ./ipctransientclient $commpath_args > /dev/null 2>&1 & + else + ./ipctransientclient $commpath_args & + fi + client_pid=$! + sleep $client_time + if [ $exit_on_error = 1 ];then + kill -0 $client_pid > /dev/null 2>&1 + else + kill -9 $client_pid > /dev/null 2>&1 + fi + rc=$? + if [ $rc = 0 ]; then + echo "############ ERROR: Iter $client_loop_cnt failed to receive all messages" + client_failed=`expr $client_failed + 1` + if [ $exit_on_error = 1 ];then + echo "terminating after first error..." + exit 0 + fi + else + echo "############ INFO: Iter $client_loop_cnt passed" + fi + + client_loop_cnt=`expr $client_loop_cnt + 1`; + done + server_loop_cnt=`expr $server_loop_cnt + 1`; + total_failed=`expr $total_failed + $client_failed` + kill -9 $server_pid > /dev/null 2>&1 + rc=$? + if [ $rc = 0 ]; then + echo "############ ERROR: Server was already dead" + server_failed=`expr $server_failed + 1` + fi +done + +total_failed=`expr $total_failed + $server_failed` + +if [ $total_failed = 0 ]; then + echo "INFO: All tests passed" +else + echo "ERROR: $total_failed tests failed" +fi + +exit $total_failed diff --git a/lib/clplumbing/uids.c b/lib/clplumbing/uids.c new file mode 100644 index 0000000..0727e1d --- /dev/null +++ b/lib/clplumbing/uids.c @@ -0,0 +1,140 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#define NOBODY "nobody" + +#if defined(HAVE_SETEUID) && defined(HAVE_SETEGID) && \ + defined(_POSIX_SAVED_IDS) +# define CAN_DROP_PRIVS 1 + +#endif + + + + +#ifndef CAN_DROP_PRIVS + int drop_privs(uid_t uid, gid_t gid) { return 0; } + int return_to_orig_privs(void) { return 0; } + int return_to_dropped_privs(void) { return 0; } + int cl_have_full_privs(void) { return 0; } +#else + +static int anysaveduid = 0; +static uid_t nobodyuid=-1; +static gid_t nobodygid=-1; +static uid_t poweruid=-1; +static gid_t powergid=-1; +static int privileged_state = 1; + +/* WARNING: uids are unsigned! */ +#define WANT_NOBODY(uid) ((uid) == 0) + +int /* Become nobody - and remember our original privileges */ +drop_privs(uid_t uid, gid_t gid) +{ + int rc; + gid_t curgid = getgid(); + + if (!anysaveduid) { + poweruid=getuid(); + powergid=curgid; + } + + if (WANT_NOBODY(uid)) { + struct passwd* p; + + p = getpwnam(NOBODY); + + if (p == NULL) { + return -1; + } + uid = p->pw_uid; + gid = p->pw_gid; + } + if (setegid(gid) < 0) { + return -1; + } + rc = seteuid(uid); + + if (rc >= 0) { + anysaveduid = 1; + nobodyuid=uid; + nobodygid=gid; + privileged_state = 0; + }else{ + /* Attempt to recover original privileges */ + int err = errno; + setegid(curgid); + errno = err; + } + cl_untaint_coredumps(); + return rc; +} + +int /* Return to our original privileges (if any) */ +return_to_orig_privs(void) +{ + int rc; + if (!anysaveduid) { + return 0; + } + if (seteuid(poweruid) < 0) { + return -1; + } + privileged_state = 1; + rc = setegid(powergid); + /* + * Sad but true, for security reasons we can't call + * cl_untaint_coredumps() here - because it might cause an + * leak of confidential information for some applications. + * So, the applications need to use either cl_untaint_coredumps() + * when they change privileges, or they need to call + * cl_set_all_coredump_signal_handlers() to handle core dump + * signals and set their privileges to maximum before core + * dumping. See the comments in coredumps.c for more details. + */ + return rc; +} + +int /* Return to "nobody" level of privs (if any) */ +return_to_dropped_privs(void) +{ + int rc; + + if (!anysaveduid) { + return 0; + } + setegid(nobodygid); + privileged_state = 0; + rc = seteuid(nobodyuid); + /* See note above about dumping core */ + return rc; +} + +/* Return TRUE if we have full privileges at the moment */ +int +cl_have_full_privs(void) +{ + return privileged_state != 0; +} +#endif diff --git a/lib/lrm/Makefile.am b/lib/lrm/Makefile.am new file mode 100644 index 0000000..815f92f --- /dev/null +++ b/lib/lrm/Makefile.am @@ -0,0 +1,36 @@ +# +# Author: Sun Jiang Dong +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +lrmdir = $(localstatedir)/lib/heartbeat/lrm +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(GLIBLIB) + +lib_LTLIBRARIES = liblrm.la +liblrm_la_SOURCES = lrm_msg.c clientlib.c racommon.c +liblrm_la_LDFLAGS = -version-info 2:0:0 $(COMMONLIBS) +liblrm_la_CFLAGS = $(INCLUDES) + +install-exec-local: + $(mkinstalldirs) $(DESTDIR)$(lrmdir) + -chgrp $(GLUE_DAEMON_GROUP) $(DESTDIR)/$(lrmdir) + chmod 770 $(DESTDIR)/$(lrmdir) diff --git a/lib/lrm/clientlib.c b/lib/lrm/clientlib.c new file mode 100644 index 0000000..78dcdc8 --- /dev/null +++ b/lib/lrm/clientlib.c @@ -0,0 +1,1612 @@ +/* + * Client Library for Local Resource Manager API. + * + * Author: Huang Zhen + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/* FIXME: Notice: this define should be replaced when merge to the whole pkg*/ +#define LRM_MAXPIDLEN 256 +#define LRM_ID "lrm" + +#define LOG_FAIL_create_lrm_msg(msg_type) \ + cl_log(LOG_ERR, "%s(%d): failed to create a %s message with " \ + "function create_lrm_msg." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_FAIL_create_lrm_rsc_msg(msg_type) \ + cl_log(LOG_ERR, "%s(%d): failed to create a %s message with " \ + "function create_lrm_rsc_msg." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_FAIL_receive_reply(msg_type) \ + cl_log(LOG_ERR, "%s(%d): failed to receive a reply message of %s." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_FAIL_SEND_MSG(msg_type, chan_name) \ + cl_log(LOG_ERR, "%s(%d): failed to send a %s message to lrmd " \ + "via %s channel." \ + , __FUNCTION__, __LINE__, msg_type, chan_name) + +#define LOG_GOT_FAIL_RET(priority, msg_type) \ + cl_log(priority, "%s(%d): got a return code HA_FAIL from " \ + "a reply message of %s with function get_ret_from_msg." \ + , __FUNCTION__, __LINE__, msg_type) + +#define LOG_BASIC_ERROR(apiname) \ + cl_log(LOG_ERR, "%s(%d): %s failed." \ + , __FUNCTION__, __LINE__, apiname) + +#define LOG_FAIL_GET_MSG_FIELD(priority, field_name, msg) \ + {cl_log(priority, "%s(%d): failed to get the value " \ + "of field %s from a ha_msg" \ + , __FUNCTION__, __LINE__, field_name); \ + cl_log(LOG_INFO, "%s: Message follows:", __FUNCTION__); \ + cl_log_message(LOG_INFO, (msg)); \ + } + +/* declare the functions used by the lrm_ops structure*/ +static int lrm_signon (ll_lrm_t* lrm, const char * app_name); +static int lrm_signoff (ll_lrm_t*); +static int lrm_delete (ll_lrm_t*); +static int lrm_set_lrm_callback (ll_lrm_t* lrm, + lrm_op_done_callback_t op_done_callback_func); +static GList* lrm_get_rsc_class_supported (ll_lrm_t* lrm); +static GList* lrm_get_rsc_type_supported (ll_lrm_t* lrm, const char* class); +static GList* lrm_get_rsc_provider_supported (ll_lrm_t* lrm + ,const char* class, const char* type); +static char* lrm_get_rsc_type_metadata(ll_lrm_t* lrm, const char* class + ,const char* type, const char* provider); +static GHashTable* lrm_get_all_type_metadata(ll_lrm_t*, const char* class); +static GList* lrm_get_all_rscs (ll_lrm_t* lrm); +static lrm_rsc_t* lrm_get_rsc (ll_lrm_t* lrm, const char* rsc_id); +static int lrm_add_rsc (ll_lrm_t*, const char* id, const char* class + ,const char* type, const char* provider + ,GHashTable* parameter); +static int lrm_delete_rsc (ll_lrm_t*, const char* id); +static int lrm_fail_rsc (ll_lrm_t* lrm, const char* rsc_id, const int fail_rc + ,const char* fail_reason); +static int lrm_set_lrmd_param (ll_lrm_t* lrm, const char* name, const char *value); +static char* lrm_get_lrmd_param (ll_lrm_t* lrm, const char* name); +static IPC_Channel* lrm_ipcchan (ll_lrm_t*); +static int lrm_msgready (ll_lrm_t*); +static int lrm_rcvmsg (ll_lrm_t*, int blocking); +static struct lrm_ops lrm_ops_instance = +{ + lrm_signon, + lrm_signoff, + lrm_delete, + lrm_set_lrm_callback, + lrm_set_lrmd_param, + lrm_get_lrmd_param, + lrm_get_rsc_class_supported, + lrm_get_rsc_type_supported, + lrm_get_rsc_provider_supported, + lrm_get_rsc_type_metadata, + lrm_get_all_type_metadata, + lrm_get_all_rscs, + lrm_get_rsc, + lrm_add_rsc, + lrm_delete_rsc, + lrm_fail_rsc, + lrm_ipcchan, + lrm_msgready, + lrm_rcvmsg +}; +/* declare the functions used by the lrm_rsc_ops structure*/ +static int rsc_perform_op (lrm_rsc_t*, lrm_op_t* op); +static int rsc_cancel_op (lrm_rsc_t*, int call_id); +static int rsc_flush_ops (lrm_rsc_t*); +static GList* rsc_get_cur_state (lrm_rsc_t*, state_flag_t* cur_state); +static lrm_op_t* rsc_get_last_result (lrm_rsc_t*, const char* op_type); +static gint compare_call_id(gconstpointer a, gconstpointer b); + +static struct rsc_ops rsc_ops_instance = +{ + rsc_perform_op, + rsc_cancel_op, + rsc_flush_ops, + rsc_get_cur_state, + rsc_get_last_result +}; + + +/* define the internal data used by the client library*/ +static int is_signed_on = FALSE; +static IPC_Channel* ch_cmd = NULL; +static IPC_Channel* ch_cbk = NULL; +static lrm_op_done_callback_t op_done_callback = NULL; + +/* define some utility functions*/ +static int get_ret_from_ch(IPC_Channel* ch); +static int get_ret_from_msg(struct ha_msg* msg); +static struct ha_msg* op_to_msg (lrm_op_t* op); +static lrm_op_t* msg_to_op(struct ha_msg* msg); +static void free_op (lrm_op_t* op); + +/* define of the api functions*/ +ll_lrm_t* +ll_lrm_new (const char * llctype) +{ + ll_lrm_t* lrm; + + /* check the parameter*/ + if (0 != STRNCMP_CONST(llctype, LRM_ID)) { + cl_log(LOG_ERR, "ll_lrm_new: wrong parameter"); + return NULL; + } + + /* alloc memory for lrm*/ + if (NULL == (lrm = (ll_lrm_t*) g_new(ll_lrm_t,1))) { + cl_log(LOG_ERR, "ll_lrm_new: can not allocate memory"); + return NULL; + } + /* assign the ops*/ + lrm->lrm_ops = &lrm_ops_instance; + + return lrm; +} + +static int +lrm_signon (ll_lrm_t* lrm, const char * app_name) +{ + + GHashTable* ch_cmd_attrs; + GHashTable* ch_cbk_attrs; + + struct ha_msg* msg; + + char path[] = IPC_PATH_ATTR; + char cmd_path[] = LRM_CMDPATH; + char callback_path[] = LRM_CALLBACKPATH; + + /* check parameters*/ + if (NULL == lrm || NULL == app_name) { + cl_log(LOG_ERR, "lrm_signon: wrong parameter"); + return HA_FAIL; + } + + /* if already signed on, sign off first*/ + if (is_signed_on) { + cl_log(LOG_WARNING, + "lrm_signon: the client is alreay signed on, re-sign"); + lrm_signoff(lrm); + } + + /* create the command ipc channel to lrmd*/ + ch_cmd_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(ch_cmd_attrs, path, cmd_path); + ch_cmd = ipc_channel_constructor(IPC_ANYTYPE, ch_cmd_attrs); + g_hash_table_destroy(ch_cmd_attrs); + + if (NULL == ch_cmd){ + lrm_signoff(lrm); + cl_log(LOG_WARNING, + "lrm_signon: can not connect to lrmd for cmd channel"); + return HA_FAIL; + } + + if (IPC_OK != ch_cmd->ops->initiate_connection(ch_cmd)) { + lrm_signoff(lrm); + cl_log(LOG_WARNING, + "lrm_signon: can not initiate connection"); + return HA_FAIL; + } + + /* construct the reg msg*/ + if (NULL == (msg = create_lrm_reg_msg(app_name))) { + lrm_signoff(lrm); + cl_log(LOG_ERR,"lrm_signon: failed to create a register message"); + return HA_FAIL; + } + + /* send the msg*/ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + lrm_signoff(lrm); + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(REGISTER, "ch_cmd"); + return HA_FAIL; + } + /* parse the return msg*/ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + ha_msg_del(msg); + lrm_signoff(lrm); + LOG_FAIL_receive_reply(REGISTER); + return HA_FAIL; + } + + /* create the callback ipc channel to lrmd*/ + ch_cbk_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(ch_cbk_attrs, path, callback_path); + ch_cbk = ipc_channel_constructor(IPC_ANYTYPE,ch_cbk_attrs); + g_hash_table_destroy(ch_cbk_attrs); + + if (NULL == ch_cbk) { + ha_msg_del(msg); + lrm_signoff(lrm); + cl_log(LOG_ERR, "lrm_signon: failed to construct a callback " + "channel to lrmd"); + return HA_FAIL; + } + + if (IPC_OK != ch_cbk->ops->initiate_connection(ch_cbk)) { + ha_msg_del(msg); + lrm_signoff(lrm); + cl_log(LOG_ERR, + "lrm_signon: failed to initiate the callback channel."); + return HA_FAIL; + } + /* send the msg*/ + if (HA_OK != msg2ipcchan(msg,ch_cbk)) { + lrm_signoff(lrm); + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(REGISTER, "ch_cbk"); + return HA_FAIL; + } + ha_msg_del(msg); + /* parse the return msg*/ + if (HA_OK != get_ret_from_ch(ch_cbk)) { + lrm_signoff(lrm); + LOG_FAIL_receive_reply(REGISTER); + return HA_FAIL; + } + /* ok, we sign on sucessfully now*/ + is_signed_on = TRUE; + return HA_OK; +} + +static int +lrm_signoff (ll_lrm_t* lrm) +{ + /* close channels */ + if (NULL != ch_cmd) { + if (IPC_ISWCONN(ch_cmd)) { + ch_cmd->ops->destroy(ch_cmd); + } + ch_cmd = NULL; + } + if (NULL != ch_cbk) { + if (IPC_ISWCONN(ch_cbk)) { + ch_cbk->ops->destroy(ch_cbk); + } + ch_cbk = NULL; + } + is_signed_on = FALSE; + + return HA_OK; +} + +static int +lrm_delete (ll_lrm_t* lrm) +{ + /* check the parameter */ + if (NULL == lrm) { + cl_log(LOG_ERR,"lrm_delete: the parameter is a null pointer."); + return HA_FAIL; + } + g_free(lrm); + + return HA_OK; +} + +static int +lrm_set_lrm_callback (ll_lrm_t* lrm, + lrm_op_done_callback_t op_done_callback_func) + +{ + op_done_callback = op_done_callback_func; + + return HA_OK; +} + +static GList* +lrm_get_rsc_class_supported (ll_lrm_t* lrm) +{ + struct ha_msg* msg; + struct ha_msg* ret; + GList* class_list = NULL; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, + "lrm_get_rsc_class_supported: ch_cmd is a null pointer."); + return NULL; + } + /* create the get ra type message */ + msg = create_lrm_msg(GETRSCCLASSES); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETRSCCLASSES); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCCLASSES, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCCLASSES); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_WARNING, GETRSCCLASSES); + ha_msg_del(ret); + return NULL; + } + /* get the ra type list from message */ + class_list = ha_msg_value_str_list(ret,F_LRM_RCLASS); + + ha_msg_del(ret); + + return class_list; +} +static GList* +lrm_get_rsc_type_supported (ll_lrm_t* lrm, const char* rclass) +{ + struct ha_msg* msg; + struct ha_msg* ret; + GList* type_list = NULL; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, "%s(%d): ch_cmd is null." + , __FUNCTION__, __LINE__); + + return NULL; + } + /* create the get ra type message */ + msg = create_lrm_msg(GETRSCTYPES); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETRSCTYPES); + return NULL; + } + if ( HA_OK != ha_msg_add(msg, F_LRM_RCLASS, rclass)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCTYPES, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCTYPES); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETRSCTYPES); + ha_msg_del(ret); + return NULL; + } + /* get the ra type list from message */ + type_list = ha_msg_value_str_list(ret,F_LRM_RTYPES); + + ha_msg_del(ret); + + return type_list; +} +static GList* +lrm_get_rsc_provider_supported (ll_lrm_t* lrm, const char* class, const char* type) +{ + struct ha_msg* msg; + struct ha_msg* ret; + GList* provider_list = NULL; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, + "lrm_get_rsc_provider_supported: ch_mod is null."); + return NULL; + } + /* create the get ra providers message */ + msg = create_lrm_msg(GETPROVIDERS); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETPROVIDERS); + return NULL; + } + if (HA_OK != ha_msg_add(msg, F_LRM_RCLASS, class) + || HA_OK != ha_msg_add(msg, F_LRM_RTYPE, type)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETPROVIDERS, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETPROVIDERS); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETPROVIDERS); + ha_msg_del(ret); + return NULL; + } + /* get the ra provider list from message */ + provider_list = ha_msg_value_str_list(ret,F_LRM_RPROVIDERS); + + ha_msg_del(ret); + + return provider_list; +} + +/* + * lrm_get_all_type_metadatas(): + * The key of the hash table is in the format "type:provider" + * The value of the hash table is the metadata. + */ +static GHashTable* +lrm_get_all_type_metadata (ll_lrm_t* lrm, const char* rclass) +{ + GHashTable* metas = g_hash_table_new_full(g_str_hash, g_str_equal + , g_free, g_free); + GList* types = lrm_get_rsc_type_supported (lrm, rclass); + GList* providers = NULL; + GList* cur_type = NULL; + GList* cur_provider = NULL; + + cur_type = g_list_first(types); + while (cur_type != NULL) + { + const char* type; + char key[MAXLENGTH]; + type = (const char*) cur_type->data; + providers = lrm_get_rsc_provider_supported(lrm, rclass, type); + cur_provider = g_list_first(providers); + while (cur_provider != NULL) { + const char* meta; + const char* provider; + provider = (const char*) cur_provider->data; + meta = lrm_get_rsc_type_metadata(lrm,rclass,type,provider); + if (NULL == meta) { + cur_provider = g_list_next(cur_provider); + continue; + } + snprintf(key,MAXLENGTH, "%s:%s",type,provider); + key[MAXLENGTH-1]='\0'; + g_hash_table_insert(metas,g_strdup(key),g_strdup(meta)); + cur_provider = g_list_next(cur_provider); + } + lrm_free_str_list(providers); + cur_type=g_list_next(cur_type); + } + lrm_free_str_list(types); + return metas; +} + +static char* +lrm_get_rsc_type_metadata (ll_lrm_t* lrm, const char* rclass, const char* rtype, + const char* provider) +{ + struct ha_msg* msg; + struct ha_msg* ret; + const char* tmp = NULL; + char* metadata = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) + { + cl_log(LOG_ERR, + "lrm_get_rsc_type_metadata: ch_mod is null."); + return NULL; + } + /* create the get ra type message */ + msg = create_lrm_msg(GETRSCMETA); + if (NULL == msg ) { + LOG_FAIL_create_lrm_msg(GETRSCMETA); + return NULL; + } + + if (HA_OK != ha_msg_add(msg, F_LRM_RCLASS, rclass) + || HA_OK != ha_msg_add(msg, F_LRM_RTYPE, rtype)){ + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + if( provider ) { + if (HA_OK != ha_msg_add(msg, F_LRM_RPROVIDER, provider)) { + LOG_BASIC_ERROR("ha_msg_add"); + ha_msg_del(msg); + return NULL; + } + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCMETA, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return message */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCMETA); + return NULL; + } + /* get the return code of the message */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETRSCMETA); + ha_msg_del(ret); + return NULL; + } + + /* get the metadata from message */ + tmp = cl_get_string(ret, F_LRM_METADATA); + if (NULL!=tmp) { + metadata = g_strdup(tmp); + } + ha_msg_del(ret); + + return metadata; +} + +static GList* +lrm_get_all_rscs (ll_lrm_t* lrm) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + GList* rid_list = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_get_all_rscs: ch_mod is null."); + return NULL; + } + /* create the msg of get all resource */ + msg = create_lrm_msg(GETALLRCSES); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETALLRCSES); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETALLRCSES, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return msg */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETALLRCSES); + return NULL; + } + /* get the return code of msg */ + if (HA_OK != get_ret_from_msg(ret)) { + LOG_GOT_FAIL_RET(LOG_ERR, GETALLRCSES); + ha_msg_del(ret); + return NULL; + } + /* get the rsc_id list from msg */ + rid_list = ha_msg_value_str_list(ret,F_LRM_RID); + + ha_msg_del(ret); + /* return the id list */ + return rid_list; + +} + +static lrm_rsc_t* +lrm_get_rsc (ll_lrm_t* lrm, const char* rsc_id) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + lrm_rsc_t* rsc = NULL; + + /* check whether the rsc_id is available */ + if (strlen(rsc_id) >= RID_LEN) { + cl_log(LOG_ERR, "lrm_get_rsc: rsc_id is too long."); + return NULL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_get_rsc: ch_mod is null."); + return NULL; + } + /* create the msg of get resource */ + msg = create_lrm_rsc_msg(rsc_id, GETRSC); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(GETRSC); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSC, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return msg from lrmd */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSC); + return NULL; + } + /* get the return code of return message */ + if (HA_OK != get_ret_from_msg(ret)) { + ha_msg_del(ret); + return NULL; + } + /* create a new resource structure */ + rsc = g_new(lrm_rsc_t, 1); + + /* fill the field of resource with the data from msg */ + rsc->id = g_strdup(ha_msg_value(ret, F_LRM_RID)); + rsc->type = g_strdup(ha_msg_value(ret, F_LRM_RTYPE)); + rsc->class = g_strdup(ha_msg_value(ret, F_LRM_RCLASS)); + rsc->provider = g_strdup(ha_msg_value(ret, F_LRM_RPROVIDER)); + rsc->params = ha_msg_value_str_table(ret,F_LRM_PARAM); + + rsc->ops = &rsc_ops_instance; + ha_msg_del(ret); + /* return the new resource */ + return rsc; +} + +static int +lrm_fail_rsc (ll_lrm_t* lrm, const char* rsc_id, const int fail_rc +, const char* fail_reason) +{ + struct ha_msg* msg; + + /* check whether the rsc_id is available */ + if (NULL == rsc_id || RID_LEN <= strlen(rsc_id)) { + cl_log(LOG_ERR, "%s: wrong parameter rsc_id.", __FUNCTION__); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "%s: ch_mod is null.", __FUNCTION__); + return HA_FAIL; + } + + /* create the message */ + msg = create_lrm_rsc_msg(rsc_id,FAILRSC); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(FAILRSC); + return HA_FAIL; + } + if ((fail_reason && HA_OK != ha_msg_add(msg,F_LRM_FAIL_REASON,fail_reason)) + || HA_OK != ha_msg_add_int(msg, F_LRM_ASYNCMON_RC, fail_rc) + ) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return HA_FAIL; + } + /* send to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(FAILRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the result */ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + LOG_GOT_FAIL_RET(LOG_ERR, FAILRSC); + return HA_FAIL; + } + + return HA_OK; +} + +static int +lrm_set_lrmd_param(ll_lrm_t* lrm, const char* name, const char *value) +{ + struct ha_msg* msg; + + if (!name || !value) { + cl_log(LOG_ERR, "%s: no parameter name or value", __FUNCTION__); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "%s: ch_mod is null.", __FUNCTION__); + return HA_FAIL; + } + + /* create the message */ + msg = create_lrm_msg(SETLRMDPARAM); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(SETLRMDPARAM); + return HA_FAIL; + } + if (HA_OK != ha_msg_add(msg,F_LRM_LRMD_PARAM_NAME,name) + || HA_OK != ha_msg_add(msg,F_LRM_LRMD_PARAM_VAL,value)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return HA_FAIL; + } + /* send to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(FAILRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the result */ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + LOG_GOT_FAIL_RET(LOG_ERR, FAILRSC); + return HA_FAIL; + } + + return HA_OK; +} + +static char* +lrm_get_lrmd_param (ll_lrm_t* lrm, const char *name) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + const char* value = NULL; + char* v2; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_get_rsc: ch_mod is null."); + return NULL; + } + /* create the msg of get resource */ + msg = create_lrm_msg(GETLRMDPARAM); + if ( NULL == msg) { + LOG_FAIL_create_lrm_msg(GETLRMDPARAM); + return NULL; + } + if (HA_OK != ha_msg_add(msg,F_LRM_LRMD_PARAM_NAME,name)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETLRMDPARAM, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + /* get the return msg from lrmd */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETLRMDPARAM); + return NULL; + } + /* get the return code of return message */ + if (HA_OK != get_ret_from_msg(ret)) { + ha_msg_del(ret); + return NULL; + } + value = ha_msg_value(ret,F_LRM_LRMD_PARAM_VAL); + if (!value) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_LRMD_PARAM_VAL, ret); + ha_msg_del(ret); + return NULL; + } + v2 = g_strdup(value); + ha_msg_del(ret); + return v2; +} + +static int +lrm_add_rsc (ll_lrm_t* lrm, const char* rsc_id, const char* class +, const char* type, const char* provider, GHashTable* parameter) +{ + struct ha_msg* msg; + + /* check whether the rsc_id is available */ + if (NULL == rsc_id || RID_LEN <= strlen(rsc_id)) { + cl_log(LOG_ERR, "lrm_add_rsc: wrong parameter rsc_id."); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_add_rsc: ch_mod is null."); + return HA_FAIL; + } + + /* create the message of add resource */ + msg = create_lrm_addrsc_msg(rsc_id, class, type, provider, parameter); + if ( NULL == msg) { + cl_log(LOG_ERR, "%s(%d): failed to create a ADDSRC message " + "with function create_lrm_addrsc_msg" + , __FUNCTION__, __LINE__); + return HA_FAIL; + } + /* send to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(ADDRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the result */ + if (HA_OK != get_ret_from_ch(ch_cmd)) { + LOG_GOT_FAIL_RET(LOG_ERR, ADDRSC); + return HA_FAIL; + } + + return HA_OK; +} + +static int +lrm_delete_rsc (ll_lrm_t* lrm, const char* rsc_id) +{ + struct ha_msg* msg = NULL; + int rc; + + /* check whether the rsc_id is available */ + if (NULL == rsc_id || RID_LEN <= strlen(rsc_id)) { + cl_log(LOG_ERR, "lrm_delete_rsc: wrong parameter rsc_id."); + return HA_FAIL; + } + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "lrm_delete_rsc: ch_mod is null."); + return HA_FAIL; + } + + /* create the msg of del resource */ + msg = create_lrm_rsc_msg(rsc_id, DELRSC); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(DELRSC); + return HA_FAIL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(DELRSC, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + /* check the response of the msg */ + rc = get_ret_from_ch(ch_cmd); + if (rc != HA_OK && rc != HA_RSCBUSY) { + LOG_GOT_FAIL_RET(LOG_ERR, DELRSC); + return HA_FAIL; + } + + return rc; +} + +static IPC_Channel* +lrm_ipcchan (ll_lrm_t* lrm) +{ + if (NULL == ch_cbk) { + cl_log(LOG_ERR, + "lrm_inputfd: callback channel is null."); + return NULL; + } + + return ch_cbk; +} + +static gboolean +lrm_msgready (ll_lrm_t* lrm) +{ + if (NULL == ch_cbk) { + cl_log(LOG_ERR, + "lrm_msgready: callback channel is null."); + return FALSE; + } + return ch_cbk->ops->is_message_pending(ch_cbk); +} + +static int +lrm_rcvmsg (ll_lrm_t* lrm, int blocking) +{ + struct ha_msg* msg = NULL; + lrm_op_t* op = NULL; + int msg_count = 0; + + /* if it is not blocking mode and no message in the channel, return */ + if ((!lrm_msgready(lrm)) && (!blocking)) { + cl_log(LOG_DEBUG, + "lrm_rcvmsg: no message and non-block."); + return msg_count; + } + /* wait until message ready */ + if (!lrm_msgready(lrm)) { + ch_cbk->ops->waitin(ch_cbk); + } + while (lrm_msgready(lrm)) { + if (ch_cbk->ch_status == IPC_DISCONNECT) { + return msg_count; + } + /* get the message */ + msg = msgfromIPC(ch_cbk, MSG_ALLOWINTR); + if (msg == NULL) { + cl_log(LOG_WARNING, + "%s(%d): receive a null message with msgfromIPC." + , __FUNCTION__, __LINE__); + return msg_count; + } + msg_count++; + + op = msg_to_op(msg); + if (NULL!=op && NULL!=op_done_callback) { + (*op_done_callback)(op); + } + free_op(op); + ha_msg_del(msg); + } + + return msg_count; +} + +/* following are the functions for rsc_ops */ +static int +rsc_perform_op (lrm_rsc_t* rsc, lrm_op_t* op) +{ + int rc = 0; + struct ha_msg* msg = NULL; + char* rsc_id; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd + || NULL == rsc + || NULL == rsc->id + || NULL == op + || NULL == op->op_type) { + cl_log(LOG_ERR, + "rsc_perform_op: wrong parameters."); + return HA_FAIL; + } + /* create the msg of perform op */ + rsc_id = op->rsc_id; + op->rsc_id = rsc->id; + msg = op_to_msg(op); + op->rsc_id = rsc_id; + if ( NULL == msg) { + cl_log(LOG_ERR, "rsc_perform_op: failed to create a message " + "with function op_to_msg"); + return HA_FAIL; + } + /* send it to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(PERFORMOP, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + + /* check return code, the return code is the call_id of the op */ + rc = get_ret_from_ch(ch_cmd); + return rc; +} + +static int +rsc_cancel_op (lrm_rsc_t* rsc, int call_id) +{ + int rc; + struct ha_msg* msg = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_cancel_op: ch_mod is null."); + return HA_FAIL; + } + /* check parameter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_cancel_op: parameter rsc is null."); + return HA_FAIL; + } + /* create the msg of flush ops */ + msg = create_lrm_rsc_msg(rsc->id,CANCELOP); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(CANCELOP); + return HA_FAIL; + } + if (HA_OK != ha_msg_add_int(msg, F_LRM_CALLID, call_id)) { + LOG_BASIC_ERROR("ha_msg_add_int"); + ha_msg_del(msg); + return HA_FAIL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(CANCELOP, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + + rc = get_ret_from_ch(ch_cmd); + + return rc; +} + +static int +rsc_flush_ops (lrm_rsc_t* rsc) +{ + int rc; + struct ha_msg* msg = NULL; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_flush_ops: ch_mod is null."); + return HA_FAIL; + } + /* check parameter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_flush_ops: parameter rsc is null."); + return HA_FAIL; + } + /* create the msg of flush ops */ + msg = create_lrm_rsc_msg(rsc->id,FLUSHOPS); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(CANCELOP); + return HA_FAIL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(FLUSHOPS, "ch_cmd"); + return HA_FAIL; + } + ha_msg_del(msg); + + rc = get_ret_from_ch(ch_cmd); + + return rc>0?rc:HA_FAIL; +} +static gint +compare_call_id(gconstpointer a, gconstpointer b) +{ + const lrm_op_t* opa = (const lrm_op_t*)a; + const lrm_op_t* opb = (const lrm_op_t*)b; + return opa->call_id - opb->call_id; +} +static GList* +rsc_get_cur_state (lrm_rsc_t* rsc, state_flag_t* cur_state) +{ + GList* op_list = NULL, * tmplist = NULL; + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + struct ha_msg* op_msg = NULL; + lrm_op_t* op = NULL; + int state; + int op_count, i; + + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_get_cur_state: ch_mod is null."); + return NULL; + } + /* check paramter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_get_cur_state: parameter rsc is null."); + return NULL; + } + /* create the msg of get current state of resource */ + msg = create_lrm_rsc_msg(rsc->id,GETRSCSTATE); + if ( NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(GETRSCSTATE); + return NULL; + } + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETRSCSTATE, "ch_cmd"); + return NULL; + } + ha_msg_del(msg); + + /* get the return msg */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETRSCSTATE); + return NULL; + } + + /* get the state of the resource from the message */ + if (HA_OK != ha_msg_value_int(ret, F_LRM_STATE, &state)) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_STATE, ret); + ha_msg_del(ret); + return NULL; + } + *cur_state = (state_flag_t)state; + /* the first msg includes the count of pending ops. */ + if (HA_OK != ha_msg_value_int(ret, F_LRM_OPCNT, &op_count)) { + LOG_FAIL_GET_MSG_FIELD(LOG_WARNING, F_LRM_OPCNT, ret); + ha_msg_del(ret); + return NULL; + } + ha_msg_del(ret); + for (i = 0; i < op_count; i++) { + /* one msg for one op */ + op_msg = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + + if (NULL == op_msg) { + cl_log(LOG_WARNING, "%s(%d): failed to receive a " + "(pending operation) message from lrmd." + , __FUNCTION__, __LINE__); + continue; + } + op = msg_to_op(op_msg); + /* add msg to the return list */ + + if (NULL != op) { + op_list = g_list_append(op_list, op); + } + else { + cl_log(LOG_WARNING, "%s(%d): failed to make a operation " + "from a message with function msg_to_op" + , __FUNCTION__, __LINE__); + } + ha_msg_del(op_msg); + } + op_list = g_list_sort(op_list, compare_call_id); + + /* Delete the duplicate op for call_id */ +#if 0 + cl_log(LOG_WARNING, "Before uniquing"); + tmplist = g_list_first(op_list); + while (tmplist != NULL) { + cl_log(LOG_WARNING, "call_id=%d", ((lrm_op_t*)(tmplist->data))->call_id); + tmplist = g_list_next(tmplist); + } +#endif + + tmplist = g_list_first(op_list); + while (tmplist != NULL) { + if (NULL != g_list_previous(tmplist)) { + if (((lrm_op_t*)(g_list_previous(tmplist)->data))->call_id + == ((lrm_op_t*)(tmplist->data))->call_id) { + op_list = g_list_remove_link (op_list, tmplist); + free_op((lrm_op_t *)tmplist->data); + g_list_free_1(tmplist); + tmplist = g_list_first(op_list); + } + } + tmplist = g_list_next(tmplist); + } + +#if 0 + cl_log(LOG_WARNING, "After uniquing"); + while (tmplist != NULL) { + cl_log(LOG_WARNING, "call_id=%d", ((lrm_op_t*)(tmplist->data))->call_id); + tmplist = g_list_next(tmplist); + } +#endif + + return op_list; +} + +static lrm_op_t* +rsc_get_last_result (lrm_rsc_t* rsc, const char* op_type) +{ + struct ha_msg* msg = NULL; + struct ha_msg* ret = NULL; + lrm_op_t* op = NULL; + int opcount = 0; + /* check whether the channel to lrmd is available */ + if (NULL == ch_cmd) { + cl_log(LOG_ERR, "rsc_get_last_result: ch_mod is null."); + return NULL; + } + /* check parameter */ + if (NULL == rsc) { + cl_log(LOG_ERR, "rsc_get_last_result: parameter rsc is null."); + return NULL; + } + /* create the msg of get last op */ + msg = create_lrm_rsc_msg(rsc->id,GETLASTOP); + if (NULL == msg) { + LOG_FAIL_create_lrm_rsc_msg(GETLASTOP); + return NULL; + } + if (HA_OK != ha_msg_add(msg, F_LRM_RID, rsc->id)) { + LOG_BASIC_ERROR("ha_msg_add"); + ha_msg_del(msg); + return NULL; + } + if (HA_OK != ha_msg_add(msg, F_LRM_OP, op_type)) { + LOG_BASIC_ERROR("ha_msg_add"); + ha_msg_del(msg); + return NULL; + } + + /* send the msg to lrmd */ + if (HA_OK != msg2ipcchan(msg,ch_cmd)) { + ha_msg_del(msg); + LOG_FAIL_SEND_MSG(GETLASTOP, "ch_cmd"); + return NULL; + } + + /* get the return msg */ + ret = msgfromIPC(ch_cmd, MSG_ALLOWINTR); + if (NULL == ret) { + LOG_FAIL_receive_reply(GETLASTOP); + ha_msg_del(msg); + return NULL; + } + if (HA_OK != ha_msg_value_int(ret,F_LRM_OPCNT, &opcount)) { + op = NULL; + } + else if ( 1 == opcount ) { + op = msg_to_op(ret); + } + ha_msg_del(msg); + ha_msg_del(ret); + return op; +} +/* + * following are the implements of the utility functions + */ +lrm_op_t* +lrm_op_new(void) +{ + lrm_op_t* op; + + op = g_new0(lrm_op_t, 1); + op->op_status = LRM_OP_PENDING; + return op; +} + +static lrm_op_t* +msg_to_op(struct ha_msg* msg) +{ + lrm_op_t* op; + const char* op_type; + const char* app_name; + const char* rsc_id; + const char* fail_reason; + const char* output; + const void* user_data; + + op = lrm_op_new(); + + /* op->timeout, op->interval, op->target_rc, op->call_id*/ + if (HA_OK != ha_msg_value_int(msg,F_LRM_TIMEOUT, &op->timeout) + || HA_OK != ha_msg_value_int(msg,F_LRM_INTERVAL, &op->interval) + || HA_OK != ha_msg_value_int(msg,F_LRM_TARGETRC, &op->target_rc) + || HA_OK != ha_msg_value_int(msg,F_LRM_DELAY, &op->start_delay) + || HA_OK != ha_msg_value_int(msg,F_LRM_CALLID, &op->call_id)) { + LOG_BASIC_ERROR("ha_msg_value_int"); + free_op(op); + return NULL; + } + + /* op->op_status */ + if (HA_OK != + ha_msg_value_int(msg, F_LRM_OPSTATUS, (int*)&op->op_status)) { + LOG_FAIL_GET_MSG_FIELD(LOG_WARNING, F_LRM_OPSTATUS, msg); + op->op_status = LRM_OP_PENDING; + } + + /* if it finished successfully */ + if (LRM_OP_DONE == op->op_status ) { + /* op->rc */ + if (HA_OK != ha_msg_value_int(msg, F_LRM_RC, &op->rc)) { + free_op(op); + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RC, msg); + return NULL; + } + /* op->output */ + output = cl_get_string(msg, F_LRM_DATA); + if (NULL != output){ + op->output = g_strdup(output); + } + else { + op->output = NULL; + } + } else if(op->op_status == LRM_OP_PENDING) { + op->rc = EXECRA_STATUS_UNKNOWN; + + } else { + op->rc = EXECRA_EXEC_UNKNOWN_ERROR; + } + + + /* op->app_name */ + app_name = ha_msg_value(msg, F_LRM_APP); + if (NULL == app_name) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_APP, msg); + free_op(op); + return NULL; + } + op->app_name = g_strdup(app_name); + + + /* op->op_type */ + op_type = ha_msg_value(msg, F_LRM_OP); + if (NULL == op_type) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_OP, msg); + free_op(op); + return NULL; + } + op->op_type = g_strdup(op_type); + + /* op->rsc_id */ + rsc_id = ha_msg_value(msg, F_LRM_RID); + if (NULL == rsc_id) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RID, msg); + free_op(op); + return NULL; + } + op->rsc_id = g_strdup(rsc_id); + + /* op->fail_reason present only on async failures */ + fail_reason = ha_msg_value(msg, F_LRM_FAIL_REASON); + if (fail_reason) { + op->fail_reason = g_strdup(fail_reason); + } + + /* op->user_data */ + user_data = cl_get_string(msg, F_LRM_USERDATA); + + if (NULL != user_data) { + op->user_data = g_strdup(user_data); + } + + /* time_stamps */ + if (ha_msg_value_ul(msg, F_LRM_T_RUN, &op->t_run) != HA_OK + || ha_msg_value_ul(msg, F_LRM_T_RCCHANGE, &op->t_rcchange) != HA_OK + || ha_msg_value_ul(msg, F_LRM_EXEC_TIME, &op->exec_time) != HA_OK + || ha_msg_value_ul(msg, F_LRM_QUEUE_TIME, &op->queue_time) != HA_OK) { + /* cl_log(LOG_WARNING + , "%s:%d: failed to get the timing information" + , __FUNCTION__, __LINE__); + */ + } + + /* op->params */ + op->params = ha_msg_value_str_table(msg, F_LRM_PARAM); + + ha_msg_value_int(msg, F_LRM_RSCDELETED, &op->rsc_deleted); + + return op; +} + +static struct ha_msg* +op_to_msg (lrm_op_t* op) +{ + struct ha_msg* msg = ha_msg_new(15); + if (!msg) { + LOG_BASIC_ERROR("ha_msg_new"); + return NULL; + } + + if (HA_OK != ha_msg_add(msg, F_LRM_TYPE, PERFORMOP) + || HA_OK != ha_msg_add(msg, F_LRM_RID, op->rsc_id) + || HA_OK != ha_msg_add(msg, F_LRM_OP, op->op_type) + || HA_OK != ha_msg_add_int(msg, F_LRM_TIMEOUT, op->timeout) + || HA_OK != ha_msg_add_int(msg, F_LRM_INTERVAL, op->interval) + || HA_OK != ha_msg_add_int(msg, F_LRM_DELAY, op->start_delay) + || HA_OK != ha_msg_add_int(msg, F_LRM_COPYPARAMS, op->copyparams) + || HA_OK != ha_msg_add_ul(msg, F_LRM_T_RUN,op->t_run) + || HA_OK != ha_msg_add_ul(msg, F_LRM_T_RCCHANGE, op->t_rcchange) + || HA_OK != ha_msg_add_ul(msg, F_LRM_EXEC_TIME, op->exec_time) + || HA_OK != ha_msg_add_ul(msg, F_LRM_QUEUE_TIME, op->queue_time) + || HA_OK != ha_msg_add_int(msg, F_LRM_TARGETRC, op->target_rc) + || ( op->app_name && (HA_OK != ha_msg_add(msg, F_LRM_APP, op->app_name))) + || ( op->user_data && (HA_OK != ha_msg_add(msg,F_LRM_USERDATA,op->user_data))) + || ( op->params && (HA_OK != ha_msg_add_str_table(msg,F_LRM_PARAM,op->params)))) { + LOG_BASIC_ERROR("op_to_msg conversion failed"); + ha_msg_del(msg); + return NULL; + } + + return msg; +} + +static int +get_ret_from_ch(IPC_Channel* ch) +{ + int ret; + struct ha_msg* msg; + + msg = msgfromIPC(ch, MSG_ALLOWINTR); + + if (NULL == msg) { + cl_log(LOG_ERR + , "%s(%d): failed to receive message with function msgfromIPC" + , __FUNCTION__, __LINE__); + return HA_FAIL; + } + if (HA_OK != ha_msg_value_int(msg, F_LRM_RET, &ret)) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RET, msg); + ha_msg_del(msg); + return HA_FAIL; + } + ha_msg_del(msg); + return ret; +} + +static int +get_ret_from_msg(struct ha_msg* msg) +{ + int ret; + + if (NULL == msg) { + cl_log(LOG_ERR, "%s(%d): the parameter is a NULL pointer." + , __FUNCTION__, __LINE__); + return HA_FAIL; + } + if (HA_OK != ha_msg_value_int(msg, F_LRM_RET, &ret)) { + LOG_FAIL_GET_MSG_FIELD(LOG_ERR, F_LRM_RET, msg); + return HA_FAIL; + } + return ret; +} +static void +free_op (lrm_op_t* op) +{ + if (NULL == op) { + return; + } + if (NULL != op->op_type) { + g_free(op->op_type); + } + if (NULL != op->output) { + g_free(op->output); + } + if (NULL != op->rsc_id) { + g_free(op->rsc_id); + } + if (NULL != op->app_name) { + g_free(op->app_name); + } + if (NULL != op->user_data) { + g_free(op->user_data); + } + if (NULL != op->params) { + free_str_table(op->params); + } + g_free(op); +} + +void lrm_free_op(lrm_op_t* op) { + free_op(op); +} +void lrm_free_rsc(lrm_rsc_t* rsc) { + if (NULL == rsc) { + return; + } + if (NULL != rsc->id) { + g_free(rsc->id); + } + if (NULL != rsc->type) { + g_free(rsc->type); + } + if (NULL != rsc->class) { + g_free(rsc->class); + } + if (NULL != rsc->provider) { + g_free(rsc->provider); + } + if (NULL != rsc->params) { + free_str_table(rsc->params); + } + g_free(rsc); +} +void lrm_free_str_list(GList* list) { + GList* item; + if (NULL == list) { + return; + } + item = g_list_first(list); + while (NULL != item) { + if (NULL != item->data) { + g_free(item->data); + } + list = g_list_delete_link(list, item); + item = g_list_first(list); + } +} +void lrm_free_op_list(GList* list) { + GList* item; + if (NULL == list) { + return; + } + item = g_list_first(list); + while (NULL != item) { + if (NULL != item->data) { + free_op((lrm_op_t*)item->data); + } + list = g_list_delete_link(list, item); + item = g_list_first(list); + } +} +void lrm_free_str_table(GHashTable* table) { + if (NULL != table) { + free_str_table(table); + } +} + +const char * +execra_code2string(uniform_ret_execra_t code) +{ + switch(code) { + case EXECRA_EXEC_UNKNOWN_ERROR: + return "unknown exec error"; + case EXECRA_NO_RA: + return "no RA"; + case EXECRA_OK: + return "ok"; + case EXECRA_UNKNOWN_ERROR: + return "unknown error"; + case EXECRA_INVALID_PARAM: + return "invalid parameter"; + case EXECRA_UNIMPLEMENT_FEATURE: + return "unimplemented feature"; + case EXECRA_INSUFFICIENT_PRIV: + return "insufficient privileges"; + case EXECRA_NOT_INSTALLED: + return "not installed"; + case EXECRA_NOT_CONFIGURED: + return "not configured"; + case EXECRA_NOT_RUNNING: + return "not running"; + /* For status command only */ + case EXECRA_RUNNING_MASTER: + return "master"; + case EXECRA_FAILED_MASTER: + return "master (failed)"; + case EXECRA_RA_DEAMON_DEAD1: + return "status: daemon dead"; + case EXECRA_RA_DEAMON_DEAD2: + return "status: daemon dead"; + case EXECRA_RA_DEAMON_STOPPED: + return "status: daemon stopped"; + case EXECRA_STATUS_UNKNOWN: + return "status: unknown"; + default: + break; + } + + return ""; +} diff --git a/lib/lrm/lrm_msg.c b/lib/lrm/lrm_msg.c new file mode 100644 index 0000000..fdd3b3f --- /dev/null +++ b/lib/lrm/lrm_msg.c @@ -0,0 +1,212 @@ +/* + * Message Functions For Local Resource Manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * By Huang Zhen 2004/2/13 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#define LOG_BASIC_ERROR(apiname) \ + cl_log(LOG_ERR, "%s(%d): %s failed.", __FUNCTION__, __LINE__, apiname) + +const lrm_op_t lrm_zero_op; /* Default initialized to zeros */ + +static void +copy_pair(gpointer key, gpointer value, gpointer user_data) +{ + GHashTable* taget_table = (GHashTable*)user_data; + g_hash_table_insert(taget_table, g_strdup(key), g_strdup(value)); +} + +GHashTable* +copy_str_table(GHashTable* src_table) +{ + GHashTable* target_table = NULL; + + if ( NULL == src_table) { + return NULL; + } + target_table = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_foreach(src_table, copy_pair, target_table); + return target_table; +} + +static void +merge_pair(gpointer key, gpointer value, gpointer user_data) +{ + GHashTable *merged = (GHashTable*)user_data; + + if (g_hash_table_lookup(merged, key)) { + return; + } + + g_hash_table_insert(merged, g_strdup(key), g_strdup(value)); +} + +GHashTable* +merge_str_tables(GHashTable* old, GHashTable* new) +{ + GHashTable* merged = NULL; + if ( NULL == old ) { + return copy_str_table(new); + } + if ( NULL == new ) { + return copy_str_table(old); + } + merged = copy_str_table(new); + g_hash_table_foreach(old, merge_pair, merged); + return merged; +} + +static gboolean +free_pair(gpointer key, gpointer value, gpointer user_data) +{ + g_free(key); + g_free(value); + return TRUE; +} + +void +free_str_table(GHashTable* hash_table) +{ + g_hash_table_foreach_remove(hash_table, free_pair, NULL); + g_hash_table_destroy(hash_table); +} + + + +struct ha_msg* +create_lrm_msg (const char* msg) +{ + struct ha_msg* ret; + if ((NULL == msg) || (0 == strlen(msg))) { + return NULL; + } + + ret = ha_msg_new(1); + if (HA_OK != ha_msg_add(ret, F_LRM_TYPE, msg)) { + ha_msg_del(ret); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + return ret; +} + +struct ha_msg* +create_lrm_reg_msg(const char* app_name) +{ + struct ha_msg* ret; + if ((NULL == app_name) || (0 == strlen(app_name))) { + return NULL; + } + + ret = ha_msg_new(5); + + if(HA_OK != ha_msg_add(ret, F_LRM_TYPE, REGISTER) + || HA_OK != ha_msg_add(ret, F_LRM_APP, app_name) + || HA_OK != ha_msg_add_int(ret, F_LRM_PID, getpid()) + || HA_OK != ha_msg_add_int(ret, F_LRM_GID, getegid()) + || HA_OK != ha_msg_add_int(ret, F_LRM_UID, getuid())) { + ha_msg_del(ret); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + return ret; +} + +struct ha_msg* +create_lrm_addrsc_msg(const char* rid, const char* class, const char* type, + const char* provider, GHashTable* params) +{ + struct ha_msg* msg; + if (NULL==rid||NULL==class||NULL==type) { + return NULL; + } + + msg = ha_msg_new(5); + if(HA_OK != ha_msg_add(msg, F_LRM_TYPE, ADDRSC) + || HA_OK != ha_msg_add(msg, F_LRM_RID, rid) + || HA_OK != ha_msg_add(msg, F_LRM_RCLASS, class) + || HA_OK != ha_msg_add(msg, F_LRM_RTYPE, type)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + + if( provider ) { + if (HA_OK != ha_msg_add(msg, F_LRM_RPROVIDER, provider)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + } + + if ( params ) { + if (HA_OK != ha_msg_add_str_table(msg,F_LRM_PARAM,params)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + } + return msg; +} + + +struct ha_msg* +create_lrm_rsc_msg(const char* rid, const char* msg) +{ + struct ha_msg* ret; + if ((NULL == rid) ||(NULL == msg) || (0 == strlen(msg))) { + return NULL; + } + + ret = ha_msg_new(2); + if(HA_OK != ha_msg_add(ret, F_LRM_TYPE, msg) + || HA_OK != ha_msg_add(ret, F_LRM_RID, rid)) { + ha_msg_del(ret); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + return ret; +} + + + +struct ha_msg* +create_lrm_ret(int ret, int fields) +{ + struct ha_msg* msg = ha_msg_new(fields); + if(HA_OK != ha_msg_add(msg, F_LRM_TYPE, RETURN) + || HA_OK != ha_msg_add_int(msg, F_LRM_RET, ret)) { + ha_msg_del(msg); + LOG_BASIC_ERROR("ha_msg_add"); + return NULL; + } + return msg; +} + diff --git a/lib/lrm/racommon.c b/lib/lrm/racommon.c new file mode 100644 index 0000000..2670f05 --- /dev/null +++ b/lib/lrm/racommon.c @@ -0,0 +1,178 @@ +/* + * Common functions for LRM interface to resource agents + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: racommon.c + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include /* Add it for compiling on OSX */ +#include +#include +#include +#include +#include + +void +get_ra_pathname(const char* class_path, const char* type, const char* provider, + char pathname[]) +{ + char* type_dup; + char* base_name; + + type_dup = g_strndup(type, RA_MAX_NAME_LENGTH); + if (type_dup == NULL) { + cl_log(LOG_ERR, "No enough memory to allocate."); + pathname[0] = '\0'; + return; + } + + base_name = basename(type_dup); + + if ( strncmp(type, base_name, RA_MAX_NAME_LENGTH) == 0 ) { + /*the type does not include path*/ + if (provider) { + snprintf(pathname, RA_MAX_NAME_LENGTH, "%s/%s/%s", + class_path, provider, type); + }else{ + snprintf(pathname, RA_MAX_NAME_LENGTH, "%s/%s", + class_path,type); + } + }else{ + /*the type includes path, just copy it to pathname*/ + if ( *type == '/' ) { + g_strlcpy(pathname, type, RA_MAX_NAME_LENGTH); + } else { + *pathname = '\0'; + cl_log(LOG_ERR, "%s: relative paths not allowed: %s", + __FUNCTION__, type); + } + } + + g_free(type_dup); +} + +/* + * Description: Filter a file. + * Return Value: + * TRUE: the file is qualified. + * FALSE: the file is unqualified. + * Notes: A qualifed file is a regular file with execute bits + * which does not start with '.' + */ +gboolean +filtered(char * file_name) +{ + struct stat buf; + char *s; + + if ( stat(file_name, &buf) != 0 ) { + return FALSE; + } + if ( ((s = strrchr(file_name,'/')) && *(s+1) == '.') + || *file_name == '.' ) { + return FALSE; + } + + if ( S_ISREG(buf.st_mode) + && ( ( buf.st_mode & S_IXUSR ) || ( buf.st_mode & S_IXGRP ) + || ( buf.st_mode & S_IXOTH ) ) ) { + return TRUE; + } + return FALSE; +} + +int +get_runnable_list(const char* class_path, GList ** rsc_info) +{ + struct dirent **namelist; + int file_num; + + if ( rsc_info == NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list"); + return -2; + } + + if ( *rsc_info != NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list."\ + "will cause memory leak."); + *rsc_info = NULL; + } + + file_num = scandir(class_path, &namelist, NULL, alphasort); + if (file_num < 0) { + cl_log(LOG_ERR, "scandir failed in RA plugin"); + return -2; + } else{ + while (file_num--) { + char tmp_buffer[FILENAME_MAX+1]; + + tmp_buffer[0] = '\0'; + tmp_buffer[FILENAME_MAX] = '\0'; + snprintf(tmp_buffer, FILENAME_MAX, "%s/%s", + class_path, namelist[file_num]->d_name ); + if ( filtered(tmp_buffer) == TRUE ) { + *rsc_info = g_list_append(*rsc_info, + g_strdup(namelist[file_num]->d_name)); + } + free(namelist[file_num]); + } + free(namelist); + } + return g_list_length(*rsc_info); +} + +int +get_failed_exec_rc(void) +{ + int rc; + + switch (errno) { /* see execve(2) */ + case ENOENT: /* No such file or directory */ + case EISDIR: /* Is a directory */ + rc = EXECRA_NOT_INSTALLED; + break; + case EACCES: /* permission denied (various errors) */ + rc = EXECRA_INSUFFICIENT_PRIV; + break; + default: + rc = EXECRA_EXEC_UNKNOWN_ERROR; + break; + } + return rc; +} + +void +closefiles(void) +{ + int fd; + + /* close all descriptors except stdin/out/err and channels to logd */ + for (fd = getdtablesize() - 1; fd > STDERR_FILENO; fd--) { + /*if (!cl_log_is_logd_fd(fd))*/ + close(fd); + } +} diff --git a/lib/pils/Makefile.am b/lib/pils/Makefile.am new file mode 100644 index 0000000..d47c6c7 --- /dev/null +++ b/lib/pils/Makefile.am @@ -0,0 +1,57 @@ +# +# pils: Linux-HA heartbeat code +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + + +AM_CFLAGS = @CFLAGS@ + +## include files +#pkginclude_HEADERS = $(top_srcdir)/include/pils/plugin.h \ +# $(top_srcdir)/include/pils/interface.h + +## binaries +#sbin_PROGRAMS = main + + +#main_SOURCES = main.c + +#main_LDADD = libpils.la @LIBLTDL@ \ +# $(GLIBLIB) \ +# $(top_builddir)/replace/libreplace.la +#main_LDFLAGS = @LIBADD_DL@ @LIBLTDL@ -export-dynamic @DLOPEN_FORCE_FLAGS@ + + +## libraries + +lib_LTLIBRARIES = libpils.la + +plugindir = $(libdir)/@HB_PKG@/plugins/test +plugin_LTLIBRARIES = test.la + +libpils_la_SOURCES = pils.c +libpils_la_LDFLAGS = -version-info 2:0:0 +libpils_la_LIBADD = $(top_builddir)/replace/libreplace.la \ + @LIBLTDL@ $(GLIBLIB) +test_la_SOURCES = test.c +test_la_LDFLAGS = -export-dynamic -module -avoid-version diff --git a/lib/pils/main.c b/lib/pils/main.c new file mode 100644 index 0000000..32faceb --- /dev/null +++ b/lib/pils/main.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2001 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include + +#define MOD "/home/alanr/modules" + +GHashTable* test1functions = NULL; + +long one = 1; +long two = 2; +long three = 3; +long four = 4; + +static int TestCallBack +( GenericPILCallbackType t +, PILPluginUniv* univ +, const char * iftype +, const char * ifname +, void* userptr +); + +static PILGenericIfMgmtRqst RegRqsts [] = + { {"test", &test1functions, &one, TestCallBack, &two}, + {NULL, NULL, NULL, NULL, NULL} +}; + +int +main(int argc, char ** argv) +{ + PILPluginUniv * u; + PIL_rc rc; + int j; + + + u = NewPILPluginUniv(MOD); + /* PILSetDebugLevel(u, NULL, NULL, 0); */ + PILLogMemStats(); + + + if ((rc = PILLoadPlugin(u, "InterfaceMgr", "generic", &RegRqsts)) + != PIL_OK) { + fprintf(stderr, "generic plugin load Error = [%s]\n" + , lt_dlerror()); + /*exit(1);*/ + } + /* PILSetDebugLevel(u, NULL, NULL, 0); */ + + for (j=0; j < 10; ++j) { + PILLogMemStats(); + fprintf(stderr, "****Loading plugin test/test\n"); + if ((rc = PILLoadPlugin(u, "test", "test", NULL)) != PIL_OK) { + printf("ERROR: test plugin load error = [%d/%s]\n" + , rc, lt_dlerror()); + } + PILLogMemStats(); + fprintf(stderr, "****UN-loading plugin test/test\n"); + if ((rc = PILIncrIFRefCount(u, "test", "test", -1))!= PIL_OK){ + printf("ERROR: test plugin UNload error = [%d/%s]\n" + , rc, lt_dlerror()); + } + } + PILLogMemStats(); + DelPILPluginUniv(u); u = NULL; + PILLogMemStats(); + + return 0; +} + + +static int +TestCallBack +( GenericPILCallbackType t +, PILPluginUniv* univ +, const char * iftype +, const char * ifname +, void* userptr) +{ + char cbbuf[32]; + + switch(t) { + case PIL_REGISTER: + snprintf(cbbuf, sizeof(cbbuf), "PIL_REGISTER"); + break; + + case PIL_UNREGISTER: + snprintf(cbbuf, sizeof(cbbuf), "PIL_UNREGISTER"); + break; + + default: + snprintf(cbbuf, sizeof(cbbuf), "type [%d?]", t); + break; + } + + fprintf(stderr, "Callback: (%s, univ: 0x%lx, module: %s/%s, user ptr: 0x%lx (%ld))\n" + , cbbuf + , (unsigned long) univ + , iftype, ifname + , (unsigned long)userptr + , (*((long *)userptr))); + return PIL_OK; +} + diff --git a/lib/pils/pils.c b/lib/pils/pils.c new file mode 100644 index 0000000..4243b22 --- /dev/null +++ b/lib/pils/pils.c @@ -0,0 +1,2152 @@ +/* + * Copyright (C) 2001 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Dumbness... */ +#define time FooTimeParameter +#define index FooIndexParameter +# include +#undef time +#undef index + + +#define ENABLE_PIL_DEFS_PRIVATE +#define ENABLE_PLUGIN_MANAGER_PRIVATE + +#ifndef STRLEN_CONST +# define STRLEN_CONST(con) (sizeof(con)-1) +#endif + +#include + +#define NEW(type) (g_new(type,1)) +#define ZAP(obj) memset(obj, 0, sizeof(*obj)) +#define DELETE(obj) {g_free(obj); obj = NULL;} + +#ifdef LTDL_SHLIB_EXT +# define PLUGINSUFFIX LTDL_SHLIB_EXT +#else +# define PLUGINSUFFIX ".so" +#endif + +static int PluginDebugLevel = 0; + +#define DEBUGPLUGIN (PluginDebugLevel > 0) + + + +static PIL_rc InterfaceManager_plugin_init(PILPluginUniv* univ); + +static char** PILPluginTypeListPlugins(PILPluginType* pitype, int* picount); +static PILInterface* FindIF(PILPluginUniv* universe, const char *iftype +, const char * ifname); +static PIL_rc PluginExists(const char * PluginPath); +static char * PILPluginPath(PILPluginUniv* universe, const char * plugintype +, const char * pluginname); + + +void DelPILPluginUniv(PILPluginUniv*); +/* + * These RmA* functions primarily called from hash_table_foreach, + * functions, so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + * + * They all follow the same calling sequence though. It is: + * String name"*" type object + * "*" type object with the name given by 1st argument + * NULL + * + * For example: + * RmAPILPluginType takes + * string name + * PILPluginType* object with the given name. + */ +static gboolean RmAPILPluginType +( gpointer pitname /* Name of this plugin type */ +, gpointer pitype /* PILPluginType* */ +, gpointer notused +); +static void RemoveAPILPluginType(PILPluginType*); + +static PILPluginType* NewPILPluginType +( PILPluginUniv* pluginuniv +, const char * plugintype +); +static void DelPILPluginType(PILPluginType*); +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + */ +static gboolean RmAPILPlugin +( gpointer piname /* Name of this plugin */ +, gpointer plugin /* PILPlugin* */ +, gpointer notused +); +static void RemoveAPILPlugin(PILPlugin*); + + +static PILPlugin* NewPILPlugin(PILPluginType* pitype + , const char * plugin_name + , lt_dlhandle dlhand + , PILPluginInitFun PluginSym); +static void DelPILPlugin(PILPlugin*); + +struct MemStat { + unsigned long news; + unsigned long frees; +}; + +static struct PluginStats { + struct MemStat plugin; + struct MemStat pitype; + struct MemStat piuniv; + struct MemStat interface; + struct MemStat interfacetype; + struct MemStat interfaceuniv; +}PILstats; + +#define STATNEW(t) {PILstats.t.news ++; } +#define STATFREE(t) {PILstats.t.frees ++; } + + + +static PILInterfaceUniv* NewPILInterfaceUniv(PILPluginUniv*); +static void DelPILInterfaceUniv(PILInterfaceUniv*); +/* + * These RmA* functions primarily called from hash_table_foreach, but + * not necessarily, so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + */ +static gboolean RmAPILInterfaceType +( gpointer iftypename /* Name of this interface type */ +, gpointer iftype /* PILInterfaceType* */ +, gpointer notused +); +static void RemoveAPILInterfaceType(PILInterfaceType*, PILInterfaceType*); + +static PILInterfaceType* NewPILInterfaceType +( PILInterfaceUniv* +, const char * typename +, void* ifexports, void* user_data +); +static void DelPILInterfaceType(PILInterfaceType*); +/* + * These RmA* functions are designed to be called from + * hash_table_foreach, so they have gpointer arguments. When calling + * them by hand, take special care to pass the right argument types. + * They can be called from other places safely also. + */ +static gboolean RmAPILInterface +( gpointer ifname /* Name of this interface */ +, gpointer plugin /* PILInterface* */ +, gpointer notused +); +static PIL_rc RemoveAPILInterface(PILInterface*); +static void DelPILPluginType(PILPluginType*); + +static PILInterface* NewPILInterface +( PILInterfaceType* interfacetype +, const char* interfacename +, void * exports +, PILInterfaceFun closefun +, void* ud_interface +, PILPlugin* loading_plugin /* The plugin that loaded us */ +); +static void DelPILInterface(PILInterface*); +static PIL_rc close_ifmgr_interface(PILInterface*, void*); + + + + +/* + * For consistency, we show up as a plugin in our our system. + * + * Here are our exports as a plugin. + * + */ +static const char * PIL_PILPluginVersion(void); +static void PIL_PILPluginClose (PILPlugin*); +void PILpisysSetDebugLevel (int level); +int PILpisysGetDebugLevel(void); +static const char * PIL_PILPluginLicense (void); +static const char * PIL_PILPluginLicenseUrl (void); + +static const PILPluginOps PluginExports = +{ PIL_PILPluginVersion +, PILpisysGetDebugLevel +, PILpisysSetDebugLevel +, PIL_PILPluginLicense +, PIL_PILPluginLicenseUrl +, PIL_PILPluginClose +}; + +/* Prototypes for the functions that we export to every plugin */ +static PIL_rc PILregister_plugin(PILPlugin* piinfo, const PILPluginOps* mops); +static PIL_rc PILunregister_plugin(PILPlugin* piinfo); +static PIL_rc +PILRegisterInterface +( PILPlugin* piinfo +, const char * interfacetype /* Type of interface */ +, const char * interfacename /* Name of interface */ +, void* Ops /* Ops exported by this interface */ +, PILInterfaceFun closefunc /* Ops exported by this interface */ +, PILInterface** interfaceid /* Interface id (OP) */ +, void** Imports /* Functions imported by */ + /* this interface (OP) */ +, void* ud_interface /* interface user data */ +); +static PIL_rc PILunregister_interface(PILInterface* interfaceid); +static void PILLog(PILLogLevel priority, const char * fmt, ...) + G_GNUC_PRINTF(2,3); + + +/* + * This is the set of functions that we export to every plugin + * + * That also makes it the set of functions that every plugin imports. + * + */ + +static PILPluginImports PILPluginImportSet = +{ PILregister_plugin /* register_plugin */ +, PILunregister_plugin /* unregister_plugin */ +, PILRegisterInterface /* register_interface */ +, RemoveAPILInterface /* unregister_interface */ +, PILLoadPlugin /* load_plugin */ +, PILLog /* Logging function */ +, g_malloc /* Malloc function */ +, g_realloc /* realloc function */ +, g_free /* Free function */ +, g_strdup /* Strdup function */ +}; + +static PIL_rc ifmgr_register_interface(PILInterface* newif + , void** imports); +static PIL_rc ifmgr_unregister_interface(PILInterface* interface); + +/* + * For consistency, the master interface manager is a interface in the + * system. Below is our set of exported Interface functions. + * + * Food for thought: This is the interface manager whose name is + * interface. This makes it the Interface Interface interface ;-) + * (or the Interface/Interface interface if you prefer) + */ + +static PILInterfaceOps IfExports = +{ ifmgr_register_interface +, ifmgr_unregister_interface +}; + + + +/* + * Below is the set of functions we export to every interface manager. + */ + +static int IfRefCount(PILInterface * ifh); +static int IfIncrRefCount(PILInterface*eifinfo,int plusminus); +static int PluginIncrRefCount(PILPlugin*eifinfo,int plusminus); +#if 0 +static int PluginRefCount(PILPlugin * ifh); +#endif +static void IfForceUnregister(PILInterface *eifinfo); +static void IfForEachClientRemove(PILInterface* manangerif + , gboolean(*f)(PILInterface* clientif, void * other) + , void* other); + +static PILInterfaceImports IFManagerImports = +{ IfRefCount +, IfIncrRefCount +, IfForceUnregister +, IfForEachClientRemove +}; +static void PILValidatePlugin(gpointer key, gpointer plugin, gpointer pitype); +static void PILValidatePluginType(gpointer key, gpointer pitype, gpointer piuniv); +static void PILValidatePluginUniv(gpointer key, gpointer pitype, gpointer); +static void PILValidateInterface(gpointer key, gpointer interface, gpointer iftype); +static void PILValidateInterfaceType(gpointer key, gpointer iftype, gpointer ifuniv); +static void PILValidateInterfaceUniv(gpointer key, gpointer puniv, gpointer); + +/***************************************************************************** + * + * This code is for managing plugins, and interacting with them... + * + ****************************************************************************/ + +static PILPlugin* +NewPILPlugin( PILPluginType* pitype + , const char * plugin_name + , lt_dlhandle dlhand + , PILPluginInitFun PluginSym) +{ + PILPlugin* ret = NEW(PILPlugin); + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILPlugin(0x%lx)", (unsigned long)ret); + } + + STATNEW(plugin); + ret->MagicNum = PIL_MAGIC_PLUGIN; + ret->plugin_name = g_strdup(plugin_name); + ret->plugintype = pitype; + ret->refcnt = 0; + ret->dlhandle = dlhand; + ret->dlinitfun = PluginSym; + PILValidatePlugin(ret->plugin_name, ret, pitype); + return ret; +} +static void +DelPILPlugin(PILPlugin*pi) +{ + + if (pi->refcnt > 0) { + PILLog(PIL_INFO, "DelPILPlugin: Non-zero refcnt"); + } + + if (pi->dlhandle) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Closing dlhandle for (%s/%s)" + , pi->plugintype->plugintype, pi->plugin_name); + } + lt_dlclose(pi->dlhandle); + }else if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NO dlhandle for (%s/%s)!" + , pi->plugintype->plugintype, pi->plugin_name); + } + DELETE(pi->plugin_name); + ZAP(pi); + DELETE(pi); + STATFREE(plugin); +} + + +static PILPluginType dummymlpitype = +{ PIL_MAGIC_PLUGINTYPE +, NULL /*plugintype*/ +, NULL /*piuniv*/ +, NULL /*Plugins*/ +, PILPluginTypeListPlugins /* listplugins */ +}; + +static PILPluginType* +NewPILPluginType(PILPluginUniv* pluginuniv + , const char * plugintype +) +{ + PILPluginType* ret = NEW(PILPluginType); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILPlugintype(0x%lx)", (unsigned long)ret); + } + STATNEW(pitype); + + *ret = dummymlpitype; + + ret->plugintype = g_strdup(plugintype); + ret->piuniv = pluginuniv; + ret->Plugins = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(pluginuniv->PluginTypes + , g_strdup(ret->plugintype), ret); + PILValidatePluginType(ret->plugintype, ret, pluginuniv); + return ret; +} +static void +DelPILPluginType(PILPluginType*pitype) +{ + PILValidatePluginType(NULL, pitype, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILPluginType(%s)", pitype->plugintype); + } + + STATFREE(pitype); + g_hash_table_foreach_remove(pitype->Plugins, RmAPILPlugin, NULL); + g_hash_table_destroy(pitype->Plugins); + DELETE(pitype->plugintype); + ZAP(pitype); + DELETE(pitype); +} +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key * + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean +RmAPILPlugin /* IsA GHFunc: required for g_hash_table_foreach_remove() */ +( gpointer piname /* Name of this plugin */ +, gpointer plugin /* PILPlugin* */ +, gpointer notused +) +{ + PILPlugin* Plugin = plugin; + PILPluginType* Pitype = Plugin->plugintype; + + PILValidatePlugin(piname, plugin, NULL); + PILValidatePluginType(NULL, Pitype, NULL); + g_assert(IS_PILPLUGIN(Plugin)); + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILPlugin(%s/%s)", Pitype->plugintype + , Plugin->plugin_name); + } + /* Normally called from g_hash_table_foreachremove or equivalent */ + + DelPILPlugin(plugin); + DELETE(piname); + return TRUE; +} + +static void +RemoveAPILPlugin(PILPlugin*Plugin) +{ + PILPluginType* Pitype = Plugin->plugintype; + gpointer key; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RemoveAPILPlugin(%s/%s)" + , Pitype->plugintype + , Plugin->plugin_name); + } + if (g_hash_table_lookup_extended(Pitype->Plugins + , Plugin->plugin_name, &key, (void*)&Plugin)) { + + g_hash_table_remove(Pitype->Plugins, key); + RmAPILPlugin(key, Plugin, NULL); + key = NULL; + Plugin = NULL; + + }else{ + g_assert_not_reached(); + } + if (g_hash_table_size(Pitype->Plugins) == 0) { + RemoveAPILPluginType(Pitype); + /* Pitype is now invalid */ + Pitype = NULL; + } +} + +PILPluginUniv* +NewPILPluginUniv(const char * basepluginpath) +{ + PILPluginUniv* ret = NEW(PILPluginUniv); + + /* The delimiter separating search path components */ + const char* path_delim = G_SEARCHPATH_SEPARATOR_S; + char * fullpath; + + STATNEW(piuniv); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILPluginUniv(0x%lx)" + , (unsigned long)ret); + } + if (!g_path_is_absolute(basepluginpath)) { + DELETE(ret); + return(ret); + } + ret->MagicNum = PIL_MAGIC_PLUGINUNIV; + fullpath = g_strdup_printf("%s%s%s", basepluginpath + , path_delim, PILS_BASE_PLUGINDIR); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "PILS: Plugin path = %s", fullpath); + } + + /* Separate the root directory PATH into components */ + ret->rootdirlist = g_strsplit(fullpath, path_delim, 100); + g_free(fullpath); + + ret->PluginTypes = g_hash_table_new(g_str_hash, g_str_equal); + ret->imports = &PILPluginImportSet; + ret->ifuniv = NewPILInterfaceUniv(ret); + PILValidatePluginUniv(NULL, ret, NULL); + return ret; +} + +/* Change memory allocation functions immediately after creating universe */ +void +PilPluginUnivSetMemalloc(PILPluginUniv* u +, gpointer (*allocfun)(glib_size_t size) +, gpointer (*reallocfun)(gpointer ptr, glib_size_t size) +, void (*freefun)(void* space) +, char* (*strdupfun)(const char *s)) +{ + u->imports->alloc = allocfun; + u->imports->mrealloc = reallocfun; + u->imports->mfree = freefun; + u->imports->mstrdup = strdupfun; +} + + +/* Change logging functions - preferably right after creating universe */ +void +PilPluginUnivSetLog(PILPluginUniv* u +, void (*logfun) (PILLogLevel priority, const char * fmt, ...)) +{ + u->imports->log = logfun; +} + +void +DelPILPluginUniv(PILPluginUniv* piuniv) +{ + + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILPluginUniv(0x%lx)" + , (unsigned long)piuniv); + } + STATFREE(piuniv); + PILValidatePluginUniv(NULL, piuniv, NULL); + DelPILInterfaceUniv(piuniv->ifuniv); + piuniv->ifuniv = NULL; + g_hash_table_foreach_remove(piuniv->PluginTypes + , RmAPILPluginType, NULL); + g_hash_table_destroy(piuniv->PluginTypes); + g_strfreev(piuniv->rootdirlist); + ZAP(piuniv); + DELETE(piuniv); +} + +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key * + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean /* IsA GHFunc: required for g_hash_table_foreach_remove() */ +RmAPILPluginType +( gpointer pitname /* Name of this plugin type "real" key */ +, gpointer pitype /* PILPluginType* */ +, gpointer notused +) +{ + PILPluginType* Plugintype = pitype; + + g_assert(IS_PILPLUGINTYPE(Plugintype)); + PILValidatePluginType(pitname, pitype, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILPluginType(%s)" + , Plugintype->plugintype); + } + /* + * This function is usually but not always called by + * g_hash_table_foreach_remove() + */ + + DelPILPluginType(pitype); + DELETE(pitname); + return TRUE; +} +static void +RemoveAPILPluginType(PILPluginType*Plugintype) +{ + PILPluginUniv* Pluginuniv = Plugintype->piuniv; + gpointer key; + if (g_hash_table_lookup_extended(Pluginuniv->PluginTypes + , Plugintype->plugintype, &key, (void*)&Plugintype)) { + + g_hash_table_remove(Pluginuniv->PluginTypes, key); + RmAPILPluginType(key, Plugintype, NULL); + }else{ + g_assert_not_reached(); + } +} + +/* + * InterfaceManager_plugin_init: Initialize the handling of + * "Interface Manager" interfaces. + * + * There are a few potential bootstrapping problems here ;-) + * + */ +static PIL_rc +InterfaceManager_plugin_init(PILPluginUniv* univ) +{ + PILPluginImports* imports = univ->imports; + PILPluginType* pitype; + PILInterface* ifinfo; + PILInterfaceType* iftype; + void* dontcare; + PILPlugin* ifmgr_plugin; + PIL_rc rc; + + + iftype = NewPILInterfaceType(univ->ifuniv, PI_IFMANAGER, &IfExports + , NULL); + + g_hash_table_insert(univ->ifuniv->iftypes + , g_strdup(PI_IFMANAGER), iftype); + + pitype = NewPILPluginType(univ, PI_IFMANAGER); + + g_hash_table_insert(univ->PluginTypes + , g_strdup(PI_IFMANAGER), pitype); + + ifmgr_plugin= NewPILPlugin(pitype, PI_IFMANAGER, NULL, NULL); + + g_hash_table_insert(pitype->Plugins + , g_strdup(PI_IFMANAGER), ifmgr_plugin); + + /* We can call register_plugin, since it doesn't depend on us... */ + rc = imports->register_plugin(ifmgr_plugin, &PluginExports); + if (rc != PIL_OK) { + PILLog(PIL_CRIT, "register_plugin() failed in init: %s" + , PIL_strerror(rc)); + return(rc); + } + /* + * Now, we're registering interfaces, and are into some deep + * Catch-22 if do it the "easy" way, since our code is + * needed in order to support interface loading for the type of + * interface we are (a Interface interface). + * + * So, instead of calling imports->register_interface(), we have to + * do the work ourselves here... + * + * Since no one should yet be registered to handle Interface + * interfaces, we need to bypass the hash table handler lookup + * that register_interface would do and call the function that + * register_interface would call... + * + */ + + /* The first argument is the PILInterfaceType* */ + ifinfo = NewPILInterface(iftype, PI_IFMANAGER, &IfExports + , close_ifmgr_interface, NULL, NULL); + ifinfo->ifmanager = iftype->ifmgr_ref = ifinfo; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "InterfaceManager_plugin_init(0x%lx/%s)" + , (unsigned long)ifinfo, ifinfo->interfacename); + } + PILValidatePluginUniv(NULL, univ, NULL); + ifmgr_register_interface(ifinfo, &dontcare); + PILValidatePluginUniv(NULL, univ, NULL); + + return(PIL_OK); +}/*InterfaceManager_plugin_init*/ + + +/* Return current IfIf "plugin" version (not very interesting for us) */ +static const char * +PIL_PILPluginVersion(void) +{ + return("1.0"); +} + +/* Return current IfIf debug level */ +int +PILpisysGetDebugLevel(void) +{ + return(PluginDebugLevel); +} + +/* Set current IfIf debug level */ +void +PILpisysSetDebugLevel (int level) +{ + PluginDebugLevel = level; +} +struct set_debug_helper { + const char * pitype; + const char * piname; + int level; +}; + +static void +PILSetDebugLeveltoPlugin(gpointer key, gpointer plugin, gpointer Helper) +{ + PILPlugin* p = plugin; + struct set_debug_helper* helper = Helper; + + p->pluginops->setdebuglevel(helper->level); +} + +static void +PILSetDebugLevelbyType(const void * key, gpointer plugintype, gpointer Helper) +{ + struct set_debug_helper* helper = Helper; + + + PILPluginType* t = plugintype; + + if (helper->piname == NULL) { + g_hash_table_foreach(t->Plugins, PILSetDebugLeveltoPlugin + , helper); + }else{ + PILPlugin* p = g_hash_table_lookup(t->Plugins + , helper->piname); + if (p != NULL) { + p->pluginops->setdebuglevel(helper->level); + } + } +} + +void +PILSetDebugLevel(PILPluginUniv* u, const char * pitype, const char * piname +, int level) +{ + struct set_debug_helper helper = {pitype, piname, level}; + + if (u == NULL) { + return; + } + + if (pitype == NULL) { + g_hash_table_foreach(u->PluginTypes + /* + * Reason for this next cast: + * SetDebugLevelbyType takes const gpointer + * arguments, unlike a GHFunc which doesn't. + */ + , (GHFunc)PILSetDebugLevelbyType + , &helper); + }else{ + PILPluginType* t = g_hash_table_lookup(u->PluginTypes + , pitype); + if (t != NULL) { + PILSetDebugLevelbyType(pitype, t, &helper); + } + } +} + + +int +PILGetDebugLevel(PILPluginUniv* u, const char * pitype, const char * piname) +{ + PILPluginType* t; + PILPlugin* p; + if ( u == NULL + || pitype == NULL + || (t = g_hash_table_lookup(u->PluginTypes, pitype)) == NULL + || (p = g_hash_table_lookup(t->Plugins, piname)) == NULL) { + return -1; + } + return p->pluginops->getdebuglevel(); +} + +/* Close/shutdown our PILPlugin (the interface manager interface plugin) */ +/* All our interfaces will have already been shut down and unregistered */ +static void +PIL_PILPluginClose (PILPlugin* plugin) +{ +} +static const char * +PIL_PILPluginLicense (void) +{ + return LICENSE_LGPL; +} +static const char * +PIL_PILPluginLicenseUrl (void) +{ + return URL_LGPL; +} + +/***************************************************************************** + * + * This code is for managing interfaces, and interacting with them... + * + ****************************************************************************/ + + +static PILInterface* +NewPILInterface(PILInterfaceType* interfacetype + , const char* interfacename + , void * exports + , PILInterfaceFun closefun + , void* ud_interface + , PILPlugin* loading_plugin) +{ + PILInterface* ret = NULL; + PILInterface* look = NULL; + + + if ((look = g_hash_table_lookup(interfacetype->interfaces + , interfacename)) != NULL) { + PILLog(PIL_DEBUG, "Deleting PILInterface!"); + DelPILInterface(look); + } + ret = NEW(PILInterface); + STATNEW(interface); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILInterface(0x%lx)", (unsigned long)ret); + } + + if (ret) { + ret->MagicNum = PIL_MAGIC_INTERFACE; + ret->interfacetype = interfacetype; + ret->exports = exports; + ret->ud_interface = ud_interface; + ret->interfacename = g_strdup(interfacename); + ret->ifmanager = interfacetype->ifmgr_ref; + ret->loadingpi = loading_plugin; + g_hash_table_insert(interfacetype->interfaces + , g_strdup(ret->interfacename), ret); + + ret->if_close = closefun; + ret->refcnt = 1; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILInterface(0x%lx:%s/%s)*** user_data: 0x%p *******" + , (unsigned long)ret + , interfacetype->typename + , ret->interfacename + , ud_interface); + } + } + return ret; +} +static void +DelPILInterface(PILInterface* intf) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterface(0x%lx/%s)" + , (unsigned long)intf, intf->interfacename); + } + STATFREE(interface); + DELETE(intf->interfacename); + ZAP(intf); + DELETE(intf); +} + +static PILInterfaceType* +NewPILInterfaceType(PILInterfaceUniv*univ, const char * typename +, void* ifeports, void* user_data) +{ + PILInterfaceType* ifmgr_types; + PILInterface* ifmgr_ref; + PILInterfaceType* ret = NEW(PILInterfaceType); + + + STATNEW(interfacetype); + ret->MagicNum = PIL_MAGIC_INTERFACETYPE; + ret->typename = g_strdup(typename); + ret->interfaces = g_hash_table_new(g_str_hash, g_str_equal); + ret->ud_if_type = user_data; + ret->universe = univ; + ret->ifmgr_ref = NULL; + + /* Now find the pointer to our if type in the Interface Universe */ + if ((ifmgr_types = g_hash_table_lookup(univ->iftypes, PI_IFMANAGER)) + != NULL) { + if ((ifmgr_ref=g_hash_table_lookup(ifmgr_types->interfaces + , typename)) != NULL) { + ret->ifmgr_ref = ifmgr_ref; + }else { + g_assert(strcmp(typename, PI_IFMANAGER) == 0); + } + }else { + g_assert(strcmp(typename, PI_IFMANAGER) == 0); + } + + /* Insert ourselves into our parent's table */ + g_hash_table_insert(univ->iftypes, g_strdup(typename), ret); + return ret; +} +static void +DelPILInterfaceType(PILInterfaceType*ift) +{ + PILInterfaceUniv* u = ift->universe; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterfaceType(%s)" + , ift->typename); + } + STATFREE(interfacetype); + + PILValidateInterfaceUniv(NULL, u, NULL); + + /* + * RmAPILInterface refuses to remove the interface for the + * Interface manager, because it must be removed last. + * + * Otherwise we won't be able to unregister interfaces + * for other types of objects, and we'll be very confused. + */ + + g_hash_table_foreach_remove(ift->interfaces, RmAPILInterface, NULL); + + PILValidateInterfaceUniv(NULL, u, NULL); + + if (g_hash_table_size(ift->interfaces) > 0) { + gpointer key, iftype; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "DelPILInterfaceType(%s): table size (%d)" + , ift->typename, g_hash_table_size(ift->interfaces)); + } + if (g_hash_table_lookup_extended(ift->interfaces + , PI_IFMANAGER, &key, &iftype)) { + DelPILInterface((PILInterface*)iftype); + DELETE(key); + } + } + DELETE(ift->typename); + g_hash_table_destroy(ift->interfaces); + ZAP(ift); + DELETE(ift); +} + +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key * + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean /* IsAGHFunc: required for g_hash_table_foreach_remove() */ +RmAPILInterface +( gpointer ifname /* Name of this interface */ +, gpointer intf /* PILInterface* */ +, gpointer notused +) +{ + PILInterface* If = intf; + PILInterfaceType* Iftype = If->interfacetype; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterface(0x%lx/%s)" + , (unsigned long)If, If->interfacename); + } + g_assert(IS_PILINTERFACE(If)); + + /* + * Don't remove the master interface manager this way, or + * Somebody will have a cow... + */ + if (If == If->ifmanager) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterface: skipping (%s)" + , If->interfacename); + } + return FALSE; + } + PILValidateInterface(ifname, If, Iftype); + PILValidateInterfaceType(NULL, Iftype, NULL); + + /* + * This function is usually but not always called by + * g_hash_table_foreach_remove() + */ + + PILunregister_interface(If); + PILValidateInterface(ifname, If, Iftype); + PILValidateInterfaceType(NULL, Iftype, NULL); + DELETE(ifname); + DelPILInterface(If); + return TRUE; +} +static PIL_rc +RemoveAPILInterface(PILInterface* pif) +{ + PILInterfaceType* Iftype = pif->interfacetype; + gpointer key; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RemoveAPILInterface(0x%lx/%s)" + , (unsigned long)pif, pif->interfacename); + } + if (g_hash_table_lookup_extended(Iftype->interfaces + , pif->interfacename, &key, (void*)&pif)) { + g_assert(IS_PILINTERFACE(pif)); + g_hash_table_remove(Iftype->interfaces, key); + RmAPILInterface(key, pif, NULL); + }else{ + g_assert_not_reached(); + } + + if (g_hash_table_size(Iftype->interfaces) == 0) { + /* The generic plugin handler doesn't want us to + * delete it's types... + */ + if (Iftype->ifmgr_ref->refcnt <= 1) { + RemoveAPILInterfaceType(Iftype, NULL); + } + } + return PIL_OK; +} + + +/* Register a Interface Interface (Interface manager) */ +static PIL_rc +ifmgr_register_interface(PILInterface* intf +, void** imports) +{ + PILInterfaceType* ift = intf->interfacetype; + PILInterfaceUniv* ifuniv = ift->universe; + PILInterfaceOps* ifops; /* Ops vector for InterfaceManager */ + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "Registering Implementation manager for" + " Interface type '%s'" + , intf->interfacename); + } + + ifops = intf->exports; + if (ifops->RegisterInterface == NULL + || ifops->UnRegisterInterface == NULL) { + PILLog(PIL_DEBUG, "ifmgr_register_interface(%s)" + ": NULL exported function pointer" + , intf->interfacename); + return PIL_INVAL; + } + + *imports = &IFManagerImports; + + if(g_hash_table_lookup(ifuniv->iftypes, intf->interfacename) == NULL){ + /* It registers itself into ifuniv automatically */ + NewPILInterfaceType(ifuniv,intf->interfacename, &IfExports + , NULL); + } + return PIL_OK; +} + +static gboolean +RemoveAllClients(PILInterface*interface, void * managerif) +{ + /* + * Careful! We can't remove ourselves this way... + * This gets taken care of as a special case in DelPILInterfaceUniv... + */ + if (managerif == interface) { + return FALSE; + } + PILunregister_interface(interface); + return TRUE; +} + +/* Unconditionally unregister a interface manager (InterfaceMgr Interface) */ +static PIL_rc +ifmgr_unregister_interface(PILInterface* interface) +{ + /* + * We need to unregister every interface we manage + */ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "ifmgr_unregister_interface(%s)" + , interface->interfacename); + } + + IfForEachClientRemove(interface, RemoveAllClients, interface); + return PIL_OK; +} + +/* Called to close the Interface manager for type Interface */ +static PIL_rc +close_ifmgr_interface(PILInterface* us, void* ud_interface) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "close_ifmgr_interface(%s)" + , us->interfacename); + } + /* Nothing much to do */ + return PIL_OK; +} + +/* Return the reference count for this interface */ +static int +IfRefCount(PILInterface * eifinfo) +{ + return eifinfo->refcnt; +} + +/* Modify the reference count for this interface */ +static int +IfIncrRefCount(PILInterface*eifinfo, int plusminus) +{ + if(DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfIncrRefCount(%d + %d )" + , eifinfo->refcnt, plusminus); + } + eifinfo->refcnt += plusminus; + if (eifinfo->refcnt <= 0) { + eifinfo->refcnt = 0; + /* Unregister this interface. */ + RemoveAPILInterface(eifinfo); + return 0; + } + return eifinfo->refcnt; +} + +#if 0 +static int +PluginRefCount(PILPlugin * pi) +{ + return pi->refcnt; +} +#endif + +static int +PluginIncrRefCount(PILPlugin*pi, int plusminus) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PluginIncrRefCount(%d + %d )" + , pi->refcnt, plusminus); + } + pi->refcnt += plusminus; + if (pi->refcnt <= 0) { + pi->refcnt = 0; + RemoveAPILPlugin(pi); + return 0; + } + return pi->refcnt; +} + +static PILInterface* +FindIF(PILPluginUniv* universe, const char *iftype, const char * ifname) +{ + PILInterfaceUniv* puniv; + PILInterfaceType* ptype; + + if (universe == NULL || (puniv = universe->ifuniv) == NULL + || (ptype=g_hash_table_lookup(puniv->iftypes, iftype))==NULL){ + return NULL; + } + return g_hash_table_lookup(ptype->interfaces, ifname); +} + +PIL_rc +PILIncrIFRefCount(PILPluginUniv* mu +, const char * interfacetype +, const char * interfacename +, int plusminus) +{ + PILInterface* intf = FindIF(mu, interfacetype, interfacename); + + if (intf) { + g_assert(IS_PILINTERFACE(intf)); + IfIncrRefCount(intf, plusminus); + return PIL_OK; + } + return PIL_NOPLUGIN; +} + +int +PILGetIFRefCount(PILPluginUniv* mu +, const char * interfacetype +, const char * interfacename) +{ + PILInterface* intf = FindIF(mu, interfacetype, interfacename); + + return IfRefCount(intf); +} + +static void +IfForceUnregister(PILInterface *id) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfForceUnRegister(%s)" + , id->interfacename); + } + RemoveAPILInterface(id); +} + +struct f_e_c_helper { + gboolean(*fun)(PILInterface* clientif, void * passalong); + void* passalong; +}; + +static gboolean IfForEachClientHelper(gpointer key +, gpointer iftype, gpointer helper_v); + +static gboolean +IfForEachClientHelper(gpointer unused, gpointer iftype, gpointer v) +{ + struct f_e_c_helper* s = (struct f_e_c_helper*)v; + + g_assert(IS_PILINTERFACE((PILInterface*)iftype)); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfForEachClientHelper(%s)" + , ((PILInterface*)iftype)->interfacename); + } + + return s->fun((PILInterface*)iftype, s->passalong); +} + + +static void +IfForEachClientRemove +( PILInterface* mgrif +, gboolean(*f)(PILInterface* clientif, void * passalong) +, void* passalong /* usually PILInterface* */ +) +{ + PILInterfaceType* mgrt; + PILInterfaceUniv* u; + const char * ifname; + PILInterfaceType* clientt; + + struct f_e_c_helper h = {f, passalong}; + + + if (mgrif == NULL || (mgrt = mgrif->interfacetype) == NULL + || (u = mgrt->universe) == NULL + || (ifname = mgrif->interfacename) == NULL) { + PILLog(PIL_WARN, "bad parameters to IfForEachClientRemove"); + return; + } + + if ((clientt = g_hash_table_lookup(u->iftypes, ifname)) == NULL) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "Interface manager [%s/%s] has no clients" + , PI_IFMANAGER, ifname); + } + return; + }; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "IfForEachClientRemove(%s:%s)" + , mgrt->typename, clientt->typename); + } + if (clientt->ifmgr_ref != mgrif) { + PILLog(PIL_WARN, "Bad ifmgr_ref ptr in PILInterfaceType"); + return; + } + + g_hash_table_foreach_remove(clientt->interfaces, IfForEachClientHelper + , &h); +} + +static PIL_rc +PILregister_plugin(PILPlugin* piinfo, const PILPluginOps* commonops) +{ + piinfo->pluginops = commonops; + + return PIL_OK; +} + +static PIL_rc +PILunregister_plugin(PILPlugin* piinfo) +{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILunregister_plugin(%s)" + , piinfo->plugin_name); + } + RemoveAPILPlugin(piinfo); + return PIL_OK; +} + +/* General logging function (not really UPPILS-specific) */ +static void +PILLog(PILLogLevel priority, const char * format, ...) +{ + va_list args; + GLogLevelFlags flags; + + switch(priority) { + case PIL_FATAL: flags = G_LOG_LEVEL_ERROR; + break; + case PIL_CRIT: flags = G_LOG_LEVEL_CRITICAL; + break; + + default: /* FALL THROUGH... */ + case PIL_WARN: flags = G_LOG_LEVEL_WARNING; + break; + + case PIL_INFO: flags = G_LOG_LEVEL_INFO; + break; + case PIL_DEBUG: flags = G_LOG_LEVEL_DEBUG; + break; + }; + va_start (args, format); + g_logv (G_LOG_DOMAIN, flags, format, args); + va_end (args); +} + +static const char * PIL_strerrmsgs [] = +{ "Success" +, "Invalid Parameters" +, "Bad plugin/interface type" +, "Duplicate entry (plugin/interface name/type)" +, "Oops happens" +, "No such plugin/interface/interface type" +}; + +const char * +PIL_strerror(PIL_rc rc) +{ + int irc = (int) rc; + static char buf[128]; + + if (irc < 0 || irc >= DIMOF(PIL_strerrmsgs)) { + snprintf(buf, sizeof(buf), "return code %d (?)", irc); + return buf; + } + return PIL_strerrmsgs[irc]; +} + +/* + * Returns the PATHname of the file containing the requested plugin + * This file handles PATH-like semantics from the rootdirlist. + * It is also might be the right place to put alias handing in the future... + */ +static char * +PILPluginPath(PILPluginUniv* universe, const char * plugintype +, const char * pluginname) +{ + char * PluginPath = NULL; + char ** spath_component; + + for (spath_component = universe->rootdirlist; *spath_component + ; ++ spath_component) { + + if (PluginPath) { + g_free(PluginPath); PluginPath=NULL; + } + + PluginPath = g_strdup_printf("%s%s%s%s%s%s" + , *spath_component + , G_DIR_SEPARATOR_S + , plugintype + , G_DIR_SEPARATOR_S + , pluginname + , PLUGINSUFFIX); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "PILS: Looking for %s/%s => [%s]" + , plugintype, pluginname, PluginPath); + } + + if (PluginExists(PluginPath) == PIL_OK) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "Plugin path for %s/%s => [%s]" + , plugintype, pluginname, PluginPath); + } + return PluginPath; + } + /* FIXME: Put alias file processing here... */ + } + + /* Can't find 'em all... */ + return PluginPath; +} + +static PIL_rc +PluginExists(const char * PluginPath) +{ + /* Make sure we can read and execute the plugin file */ + /* This test is nice, because dlopen reasons aren't return codes */ + + if (access(PluginPath, R_OK) != 0) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin file %s does not exist" + , PluginPath); + } + return PIL_NOPLUGIN; + } + return PIL_OK; +} + +/* Return PIL_OK if the given plugin exists */ +PIL_rc +PILPluginExists(PILPluginUniv* piuniv +, const char * plugintype +, const char * pluginname) +{ + PIL_rc rc; + char * path = PILPluginPath(piuniv, plugintype, pluginname); + + if (path == NULL) { + return PIL_INVAL; + } + rc = PluginExists(path); + DELETE(path); + return rc; +} + +/* + * PILLoadPlugin() - loads a plugin into memory and calls the + * initial() entry point in the plugin. + * + * + * Method: + * + * Construct file name of plugin. + * See if plugin exists. If not, fail with PIL_NOPLUGIN. + * + * Search Universe for plugin type + * If found, search plugin type for pluginname + * if found, fail with PIL_EXIST. + * Otherwise, + * Create new Plugin type structure + * Use lt_dlopen() on plugin to get lt_dlhandle for it. + * + * Construct the symbol name of the initialization function. + * + * Use lt_dlsym() to find the pointer to the init function. + * + * Call the initialization function. + */ +PIL_rc +PILLoadPlugin(PILPluginUniv* universe, const char * plugintype +, const char * pluginname +, void* plugin_user_data) +{ + PIL_rc rc; + char * PluginPath; + char * PluginSym; + PILPluginType* pitype; + PILPlugin* piinfo; + lt_dlhandle dlhand; + PILPluginInitFun initfun; + + PluginPath = PILPluginPath(universe, plugintype, pluginname); + + if ((rc=PluginExists(PluginPath)) != PIL_OK) { + DELETE(PluginPath); + return rc; + } + + if((pitype=g_hash_table_lookup(universe->PluginTypes, plugintype)) + != NULL) { + if ((piinfo = g_hash_table_lookup + ( pitype->Plugins, pluginname)) != NULL) { + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin %s already loaded" + , PluginPath); + } + DELETE(PluginPath); + return PIL_EXIST; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PluginType %s already present" + , plugintype); + } + }else{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Creating PluginType for %s" + , plugintype); + } + /* Create a new PILPluginType object */ + pitype = NewPILPluginType(universe, plugintype); + } + + g_assert(pitype != NULL); + + /* + * At this point, we have a PILPluginType object and our + * plugin name is not listed in it. + */ + + dlhand = lt_dlopen(PluginPath); + + if (!dlhand) { + PILLog(PIL_WARN + , "lt_dlopen() failure on plugin %s/%s [%s]." + " Reason: [%s]" + , plugintype, pluginname + , PluginPath + , lt_dlerror()); + DELETE(PluginPath); + return PIL_NOPLUGIN; + } + DELETE(PluginPath); + /* Construct the magic init function symbol name */ + PluginSym = g_strdup_printf(PIL_FUNC_FMT + , plugintype, pluginname); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin %s/%s init function: %s" + , plugintype, pluginname + , PluginSym); + } + + initfun = lt_dlsym(dlhand, PluginSym); + + if (initfun == NULL) { + PILLog(PIL_WARN + , "Plugin %s/%s init function (%s) not found" + , plugintype, pluginname, PluginSym); + DELETE(PluginSym); + lt_dlclose(dlhand); dlhand=NULL; + DelPILPluginType(pitype); + return PIL_NOPLUGIN; + } + DELETE(PluginSym); + /* + * Construct the new PILPlugin object + */ + piinfo = NewPILPlugin(pitype, pluginname, dlhand, initfun); + g_assert(piinfo != NULL); + g_hash_table_insert(pitype->Plugins, g_strdup(piinfo->plugin_name), piinfo); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Plugin %s/%s loaded and constructed." + , plugintype, pluginname); + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Calling init function in plugin %s/%s." + , plugintype, pluginname); + } + /* Save away the user_data for later */ + piinfo->ud_plugin = plugin_user_data; + /* initfun is allowed to change ud_plugin if they want */ + initfun(piinfo, universe->imports, plugin_user_data); + + return PIL_OK; +}/*PILLoadPlugin*/ + + + +#define REPORTERR(msg) PILLog(PIL_CRIT, "%s", msg) + +/* + * Register an interface. + * + * This function is exported to plugins for their use. + */ +static PIL_rc +PILRegisterInterface(PILPlugin* piinfo +, const char * interfacetype /* Type of interface */ +, const char * interfacename /* Name of interface */ +, void* Ops /* Info (functions) exported + by this interface */ +, PILInterfaceFun close_func /* Close function for interface */ +, PILInterface** interfaceid /* Interface id (OP) */ +, void** Imports /* Functions imported by + this interface (OP) */ +, void* ud_interface /* Optional user_data */ +) +{ + PILPluginUniv* piuniv; /* Universe this plugin is in */ + PILPluginType* pitype; /* Type of this plugin */ + PILInterfaceUniv* ifuniv; /* Universe this interface is in */ + PILInterfaceType*iftype; /* Type of this interface */ + PILInterface* ifinfo; /* Info about this Interface */ + + PILInterfaceType*ifmgrtype; /* PILInterfaceType for PI_IFMANAGER */ + PILInterface* ifmgrinfo; /* Interf info for "interfacetype" */ + const PILInterfaceOps* ifops; /* Ops vector for InterfaceManager */ + /* of type "interfacetype" */ + PIL_rc rc; + + if ( piinfo == NULL + || (pitype = piinfo->plugintype) == NULL + || (piuniv = pitype->piuniv) == NULL + || (ifuniv = piuniv->ifuniv) == NULL + || ifuniv->iftypes == NULL + ) { + REPORTERR("bad parameters to PILRegisterInterface"); + return PIL_INVAL; + } + + /* Now we have lots of info, but not quite enough... */ + + if ((iftype = g_hash_table_lookup(ifuniv->iftypes, interfacetype)) + == NULL) { + + /* Try to autoload the needed interface handler */ + rc = PILLoadPlugin(piuniv, PI_IFMANAGER, interfacetype, NULL); + + /* See if the interface handler loaded like we expect */ + if ((iftype = g_hash_table_lookup(ifuniv->iftypes + , interfacetype)) == NULL) { + return PIL_BADTYPE; + } + } + if ((ifinfo = g_hash_table_lookup(iftype->interfaces, interfacename)) + != NULL) { + g_warning("Attempt to register duplicate interface: %s/%s" + , interfacetype, interfacename); + return PIL_EXIST; + } + /* + * OK... Now we know it is valid, and isn't registered... + * Let's locate the InterfaceManager registrar for this type + */ + if ((ifmgrtype = g_hash_table_lookup(ifuniv->iftypes, PI_IFMANAGER)) + == NULL) { + REPORTERR("No " PI_IFMANAGER " type!"); + return PIL_OOPS; + } + if ((ifmgrinfo = g_hash_table_lookup(ifmgrtype->interfaces + , interfacetype)) == NULL) { + PILLog(PIL_CRIT + , "No interface manager for given type (%s) !" + , interfacetype); + return PIL_BADTYPE; + } + + ifops = ifmgrinfo->exports; + + /* Now we have all the information anyone could possibly want ;-) */ + + ifinfo = NewPILInterface(iftype, interfacename, Ops + , close_func, ud_interface, piinfo); + + g_assert(ifmgrinfo == ifinfo->ifmanager); + *interfaceid = ifinfo; + + /* Call the registration function for our interface type */ + rc = ifops->RegisterInterface(ifinfo, Imports); + + + /* Increment reference count of interface manager */ + IfIncrRefCount(ifmgrinfo, 1); + + /* Increment the ref count of the plugin that loaded us */ + PluginIncrRefCount(piinfo, 1); + + if (rc != PIL_OK) { + RemoveAPILInterface(ifinfo); + } + return rc; +} + +/* + * Method: + * + * Verify interface is valid. + * + * Call interface close function. + * + * Call interface manager unregister function + * + * Call RmAPILInterface to remove from InterfaceType table, and + * free interface object. + * + */ + +static PIL_rc +PILunregister_interface(PILInterface* id) +{ + PILInterfaceType* t; + PILInterfaceUniv* u; + PIL_rc rc; + PILInterface* ifmgr_info; /* Pointer to our interface handler */ + const PILInterfaceOps* exports; /* InterfaceManager operations for + * the type of interface we are + */ + + if ( id == NULL + || (t = id->interfacetype) == NULL + || (u = t->universe) == NULL + || id->interfacename == NULL) { + PILLog(PIL_WARN, "PILunregister_interface: bad interfaceid"); + return PIL_INVAL; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILunregister_interface(%s/%s)" + , t->typename, id->interfacename); + } + PILValidateInterface(NULL, id, t); + PILValidateInterfaceType(NULL, t, u); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "Calling InterfaceClose on %s/%s" + , t->typename, id->interfacename); + } + + /* Call the close function supplied by the interface */ + + if ((id->if_close != NULL) + && ((rc=id->if_close(id, id->ud_interface)) != PIL_OK)) { + PILLog(PIL_WARN, "InterfaceClose on %s/%s returned %s" + , t->typename, id->interfacename + , PIL_strerror(rc)); + } else { + rc = PIL_OK; + } + + /* Find the InterfaceManager that manages us */ + ifmgr_info = t->ifmgr_ref; + + g_assert(ifmgr_info != NULL); + + /* Find the exported functions from that IFIF */ + exports = ifmgr_info->exports; + + g_assert(exports != NULL && exports->UnRegisterInterface != NULL); + + /* Call the interface manager unregister function */ + exports->UnRegisterInterface(id); + + /* Decrement reference count of interface manager */ + IfIncrRefCount(ifmgr_info, -1); + /* This may make ifmgr_info invalid */ + ifmgr_info = NULL; + + /* Decrement the reference count of the plugin that loaded us */ + PluginIncrRefCount(id->loadingpi, -1); + + return rc; +} + +static PILInterfaceUniv* +NewPILInterfaceUniv(PILPluginUniv* piuniv) +{ + PILInterfaceUniv* ret = NEW(PILInterfaceUniv); + static int ltinityet = 0; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "NewPILInterfaceUniv(0x%lx)" + , (unsigned long)ret); + } + if (!ltinityet) { + ltinityet=1; + lt_dlinit(); + } + STATNEW(interfaceuniv); + ret->MagicNum = PIL_MAGIC_INTERFACEUNIV; + /* Make the two universes point at each other */ + ret->piuniv = piuniv; + piuniv->ifuniv = ret; + + ret->iftypes = g_hash_table_new(g_str_hash, g_str_equal); + + InterfaceManager_plugin_init(piuniv); + return ret; +} + +static void +DelPILInterfaceUniv(PILInterfaceUniv* ifuniv) +{ + PILInterfaceType* ifmgrtype; + g_assert(ifuniv!= NULL && ifuniv->iftypes != NULL); + PILValidateInterfaceUniv(NULL, ifuniv, NULL); + + STATFREE(interfaceuniv); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterfaceUniv(0x%lx)" + , (unsigned long) ifuniv); + } + g_hash_table_foreach_remove(ifuniv->iftypes, RmAPILInterfaceType, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "DelPILInterfaceUniv: final cleanup"); + } + ifmgrtype = g_hash_table_lookup(ifuniv->iftypes, PI_IFMANAGER); + RemoveAPILInterfaceType(ifmgrtype, ifmgrtype); + /* + * FIXME! need to delete the interface for PI_IFMANAGER last + * Right now, it seems to happen last, but I think that's + * coincidence... + */ + g_hash_table_destroy(ifuniv->iftypes); + ZAP(ifuniv); + DELETE(ifuniv); +} + +/* + * These RmA* functions primarily called from hash_table_foreach, + * so they have gpointer arguments. This *not necessarily* clause + * is why they do the g_hash_table_lookup_extended call instead of + * just deleting the key. When called from outside, the key + * may not be pointing at the key to actually free, but a copy + * of the key. + */ +static gboolean /* IsA GHFunc: required for g_hash_table_foreach_remove() */ +RmAPILInterfaceType +( gpointer typename /* Name of this interface type */ +, gpointer iftype /* PILInterfaceType* */ +, gpointer notused +) +{ + PILInterfaceType* Iftype = iftype; + PILInterfaceUniv* Ifuniv = Iftype->universe; + + /* + * We are not always called by g_hash_table_foreach_remove() + */ + + g_assert(IS_PILINTERFACETYPE(Iftype)); + PILValidateInterfaceUniv(NULL, Ifuniv, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterfaceType(%s)" + , (char*)typename); + } + if (iftype != notused + && strcmp(Iftype->typename, PI_IFMANAGER) == 0) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RmAPILInterfaceType: skipping (%s)" + , (char*)typename); + } + return FALSE; + } + + DelPILInterfaceType(iftype); + DELETE(typename); + + return TRUE; +} + +static void +RemoveAPILInterfaceType(PILInterfaceType*Iftype, PILInterfaceType* t2) +{ + PILInterfaceUniv* Ifuniv = Iftype->universe; + gpointer key; + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "RemoveAPILInterfaceType(%s)" + , Iftype->typename); + } + if (t2 != Iftype + && strcmp(Iftype->typename, PI_IFMANAGER) == 0) { + PILLog(PIL_DEBUG, "RemoveAPILInterfaceType: skipping (%s)" + , Iftype->typename); + return; + } + if (g_hash_table_lookup_extended(Ifuniv->iftypes + , Iftype->typename, &key, (gpointer)&Iftype)) { + + g_hash_table_remove(Ifuniv->iftypes, key); + RmAPILInterfaceType(key, Iftype, t2); + }else{ + g_assert_not_reached(); + } +} + +/* + * We need to write more functions: These include... + * + * Plugin functions: + * + * PILPluginPath() - returns path name for a given plugin + * + * PILPluginTypeList() - returns list of plugins of a given type + * + */ +static void free_dirlist(struct dirent** dlist, int n); + +static int qsort_string_cmp(const void *a, const void *b); + + +static void +free_dirlist(struct dirent** dlist, int n) +{ + int j; + for (j=0; j < n; ++j) { + if (dlist[j]) { + free(dlist[j]); + dlist[j] = NULL; + } + } + free(dlist); +} + +static int +qsort_string_cmp(const void *a, const void *b) +{ + return(strcmp(*(const char * const *)a, *(const char * const *)b)); +} + +#define FREE_DIRLIST(dlist, n) {free_dirlist(dlist, n); dlist = NULL;} + +static int +so_select (const struct dirent *dire) +{ + + const char obj_end [] = PLUGINSUFFIX; + const char *end = &dire->d_name[strlen(dire->d_name) + - (STRLEN_CONST(obj_end))]; + + + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "In so_select: %s.", dire->d_name); + } + if (end < dire->d_name) { + return 0; + } + if (strcmp(end, obj_end) == 0) { + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "FILE %s looks like a plugin name." + , dire->d_name); + } + return 1; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "FILE %s Doesn't look like a plugin name [%s] " + "%zd %zd %s." + , dire->d_name, end + , sizeof(obj_end), strlen(dire->d_name) + , &dire->d_name[strlen(dire->d_name) + - (STRLEN_CONST(obj_end))]); + } + + return 0; +} + +/* Return (sorted) list of available plugin names */ +static char** +PILPluginTypeListPlugins(PILPluginType* pitype +, int * picount /* Can be NULL ... */) +{ + const char * piclass = pitype->plugintype; + unsigned plugincount = 0; + char ** result = NULL; + int initoff = 0; + char ** pelem; + + /* Return all the plugins in all the directories in the PATH */ + + for (pelem=pitype->piuniv->rootdirlist; *pelem; ++pelem) { + int j; + GString* path; + int dircount; + struct dirent** files; + + + path = g_string_new(*pelem); + g_assert(piclass != NULL); + if (piclass) { + if (g_string_append_c(path, G_DIR_SEPARATOR) == NULL + || g_string_append(path, piclass) == NULL) { + g_string_free(path, 1); path = NULL; + return(NULL); + } + } + + files = NULL; + errno = 0; + dircount = scandir(path->str, &files + , SCANSEL_CAST so_select, NULL); + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILS: Examining directory [%s]" + ": [%d] files matching [%s] suffix found." + , path->str, dircount, PLUGINSUFFIX); + } + g_string_free(path, 1); path=NULL; + + if (dircount <= 0) { + if (files != NULL) { + FREE_DIRLIST(files, dircount); + files = NULL; + } + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG + , "PILS: skipping empty directory" + " in PILPluginTypeListPlugins()"); + } + continue; + } + + initoff = plugincount; + plugincount += dircount; + if (result == NULL) { + result = (char **) g_malloc((plugincount+1)*sizeof(char *)); + }else{ + result = (char **) g_realloc(result + , (plugincount+1)*sizeof(char *)); + } + + for (j=0; j < dircount; ++j) { + char* s; + unsigned slen = strlen(files[j]->d_name) + - STRLEN_CONST(PLUGINSUFFIX); + + s = g_malloc(slen+1); + strncpy(s, files[j]->d_name, slen); + s[slen] = EOS; + result[initoff+j] = s; + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILS: plugin [%s] found" + , s); + } + } + FREE_DIRLIST(files, dircount); + files = NULL; + } + + if (picount != NULL) { + *picount = plugincount; + } + if (result) { + result[plugincount] = NULL; + /* Return them in sorted order... */ + qsort(result, plugincount, sizeof(char *), qsort_string_cmp); + }else{ + if (DEBUGPLUGIN) { + PILLog(PIL_DEBUG, "PILS: NULL return" + " from PILPluginTypeListPlugins()"); + } + } + + + return result; +} +/* Return (sorted) list of available plugin names */ +char** +PILListPlugins(PILPluginUniv* u, const char * pitype +, int * picount /* Can be NULL ... */) +{ + PILPluginType* t; + if ((t = g_hash_table_lookup(u->PluginTypes, pitype)) == NULL) { + if (picount) { + *picount = 0; + } + t = NewPILPluginType(u, pitype); + if (!t) { + return NULL; + } + } + return PILPluginTypeListPlugins(t, picount); +} + +void +PILFreePluginList(char ** pluginlist) +{ + char ** ml = pluginlist; + + if (!ml) { + return; + } + + while (*ml != NULL) { + DELETE(*ml); + } + DELETE(pluginlist); +} + + +static void +PILValidatePlugin(gpointer key, gpointer plugin, gpointer pitype) +{ + const char * Key = key; + const PILPlugin * Plugin = plugin; + + g_assert(IS_PILPLUGIN(Plugin)); + + g_assert(Key == NULL || strcmp(Key, Plugin->plugin_name) == 0); + + g_assert (Plugin->refcnt >= 0 ); + + /* g_assert (Plugin->pluginops != NULL ); */ + g_assert (strcmp(Key, PI_IFMANAGER) == 0 || Plugin->dlinitfun != NULL ); + g_assert (strcmp(Plugin->plugin_name, PI_IFMANAGER) == 0 + || Plugin->dlhandle != NULL); + g_assert(Plugin->plugintype != NULL); + g_assert(IS_PILPLUGINTYPE(Plugin->plugintype)); + g_assert(pitype == NULL || pitype == Plugin->plugintype); +} + +static void +PILValidatePluginType(gpointer key, gpointer pitype, gpointer piuniv) +{ + char * Key = key; + PILPluginType * Pitype = pitype; + PILPluginUniv * Muniv = piuniv; + + g_assert(IS_PILPLUGINTYPE(Pitype)); + g_assert(Muniv == NULL || IS_PILPLUGINUNIV(Muniv)); + g_assert(Key == NULL || strcmp(Key, Pitype->plugintype) == 0); + g_assert(IS_PILPLUGINUNIV(Pitype->piuniv)); + g_assert(piuniv == NULL || piuniv == Pitype->piuniv); + g_assert(Pitype->Plugins != NULL); + g_hash_table_foreach(Pitype->Plugins, PILValidatePlugin, Pitype); +} +static void +PILValidatePluginUniv(gpointer key, gpointer piuniv, gpointer dummy) +{ + PILPluginUniv * Muniv = piuniv; + + g_assert(IS_PILPLUGINUNIV(Muniv)); + g_assert(Muniv->rootdirlist != NULL); + g_assert(Muniv->imports != NULL); + g_hash_table_foreach(Muniv->PluginTypes, PILValidatePluginType, piuniv); + PILValidateInterfaceUniv(NULL, Muniv->ifuniv, piuniv); +} +static void +PILValidateInterface(gpointer key, gpointer interface, gpointer iftype) +{ + char * Key = key; + PILInterface* Interface = interface; + g_assert(IS_PILINTERFACE(Interface)); + g_assert(Key == NULL || strcmp(Key, Interface->interfacename) == 0); + g_assert(IS_PILINTERFACETYPE(Interface->interfacetype)); + g_assert(iftype == NULL || iftype == Interface->interfacetype); + g_assert(Interface->ifmanager!= NULL); + g_assert(IS_PILINTERFACE(Interface->ifmanager)); + g_assert(strcmp(Interface->interfacetype->typename + , Interface->ifmanager->interfacename)== 0); + g_assert(Interface->exports != NULL); +} +static void +PILValidateInterfaceType(gpointer key, gpointer iftype, gpointer ifuniv) +{ + char * Key = key; + PILInterfaceType* Iftype = iftype; + g_assert(IS_PILINTERFACETYPE(Iftype)); + g_assert(Key == NULL || strcmp(Key, Iftype->typename) == 0); + g_assert(ifuniv == NULL || Iftype->universe == ifuniv); + g_assert(Iftype->interfaces != NULL); + g_assert(Iftype->ifmgr_ref != NULL); + g_assert(IS_PILINTERFACE(Iftype->ifmgr_ref)); + g_assert(Key == NULL || strcmp(Key, Iftype->ifmgr_ref->interfacename) == 0); + + g_hash_table_foreach(Iftype->interfaces, PILValidateInterface, iftype); +} +static void +PILValidateInterfaceUniv(gpointer key, gpointer ifuniv, gpointer piuniv) +{ + PILInterfaceUniv* Ifuniv = ifuniv; + PILPluginUniv* Pluginuniv = piuniv; + g_assert(IS_PILINTERFACEUNIV(Ifuniv)); + g_assert(Pluginuniv == NULL || IS_PILPLUGINUNIV(Pluginuniv)); + g_assert(piuniv == NULL || piuniv == Ifuniv->piuniv); + g_hash_table_foreach(Ifuniv->iftypes, PILValidateInterfaceType, ifuniv); +} + +#define PRSTAT(type) { \ + PILLog(PIL_INFO, "Plugin system objects (" #type "): " \ + "\tnew %ld free \%ld current %ld" \ + , PILstats.type.news \ + , PILstats.type.frees \ + , PILstats.type.news - PILstats.type.frees); \ +} +void +PILLogMemStats(void) +{ + PRSTAT(plugin); + PRSTAT(pitype); + PRSTAT(piuniv); + PRSTAT(interface); + PRSTAT(interfacetype); + PRSTAT(interfaceuniv); +} + +/* + * Function for logging with the given logging function + * The reason why it's here is so we can get printf arg checking + * You can't get that when you call a function pointer directly. + */ +void +PILCallLog(PILLogFun logfun, PILLogLevel priority, const char * fmt, ...) +{ + va_list args; + char * str; + int err = errno; + + va_start (args, fmt); + str = g_strdup_vprintf(fmt, args); + va_end (args); + logfun(priority, "%s", str); + g_free(str); + errno = err; +} diff --git a/lib/pils/test.c b/lib/pils/test.c new file mode 100644 index 0000000..c2cdb26 --- /dev/null +++ b/lib/pils/test.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2001 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +/* + * Sample Interface manager. + */ +#define PIL_PLUGINTYPE test +#define PIL_PLUGINTYPENAME "test" +#define PIL_PLUGIN test +#define PIL_PLUGINNAME "test" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +/* We are a interface manager... */ +#define ENABLE_PLUGIN_MANAGER_PRIVATE + +#include + +PIL_PLUGIN_BOILERPLATE("1.0", DebugFlag, Ourclose) + +/* + * Places to store information gotten during registration. + */ +static const PILPluginImports* OurPIImports; /* Imported plugin funs */ +static PILPlugin* OurPlugin; /* Our plugin info */ +static PILInterfaceImports* OurIfImports; /* Interface imported funs */ +static PILInterface* OurIf; /* Pointer to interface info */ + +static void +Ourclose (PILPlugin* us) +{ +} + +/* + * Our Interface Manager interfaces - exported to the universe! + * + * (or at least the interface management universe ;-). + * + */ +static PILInterfaceOps OurIfOps = { + /* FIXME -- put some in here !! */ +}; + +PIL_rc PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void*); + +static PIL_rc +IfClose(PILInterface*intf, void* ud_interface) +{ + OurPIImports->log(PIL_INFO, "In Ifclose (test plugin)"); + return PIL_OK; +} + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void *user_ptr) +{ + PIL_rc ret; + /* + * Force compiler to check our parameters... + */ + PILPluginInitFun fun = &PIL_PLUGIN_INIT; (void)fun; + + + OurPIImports = imports; + OurPlugin = us; + + imports->log(PIL_INFO, "Plugin %s: user_ptr = %lx" + , PIL_PLUGINNAME, (unsigned long)user_ptr); + + imports->log(PIL_INFO, "Registering ourselves as a plugin"); + + /* Register as a plugin */ + imports->register_plugin(us, &OurPIExports); + + imports->log(PIL_INFO, "Registering our interfaces"); + + /* Register our interfaces */ + ret = imports->register_interface + ( us + , PIL_PLUGINTYPENAME + , PIL_PLUGINNAME + , &OurIfOps /* Exported interface operations */ + , IfClose /* Interface Close function */ + , &OurIf + , (void*)&OurIfImports + , NULL); + imports->log(PIL_INFO, "test init function: returning %d" + , ret); + + return ret; +} diff --git a/lib/plugins/InterfaceMgr/HBauth.c b/lib/plugins/InterfaceMgr/HBauth.c new file mode 100644 index 0000000..eae22cf --- /dev/null +++ b/lib/plugins/InterfaceMgr/HBauth.c @@ -0,0 +1,171 @@ +/* + * Heartbeat authentication interface manager + * + * Copyright 2001 Alan Robertson + * Licensed under the GNU Lesser General Public License + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + */ +#define PIL_PLUGINTYPE InterfaceMgr +#define PIL_PLUGIN HBauth + +#define PIN(f) #f +#define PIN2(f) PIN(f) +#define PIN3 PIN2(PIL_PLUGIN) +#define PIT PIN2(PIL_PLUGINTYPE) + +/* We are a interface manager... */ +#define ENABLE_PLUGIN_MANAGER_PRIVATE + +#include +#include +#include + +PIL_PLUGIN_BOILERPLATE2("1.0", AuthDebugFlag) + + +/* + * Places to store information gotten during registration. + */ +static const PILPluginImports* AuthPIImports; /* Imported plugin fcns */ +static PILPlugin* AuthPlugin; /* Our plugin info */ +static PILInterfaceImports* AuthIfImports; /* Interface imported fcns */ +static PILInterface* AuthIf; /* Our Auth Interface info */ + +/* Our exported auth interface management functions */ +static PIL_rc RegisterAuthIF(PILInterface* ifenv, void** imports); + +static PIL_rc UnregisterAuthIF(PILInterface*iifinfo); + +/* + * Our Interface Manager interfaces - exported to the universe! + * + * (or at least to the interface management universe ;-). + * + * These are the interfaces which are used to manage our + * client authentication interfaces + * + */ +static PILInterfaceOps AuthIfOps = +{ RegisterAuthIF +, UnregisterAuthIF +}; + + +PIL_rc PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void*); + +/* + * Our user_ptr is presumed to point at a GHashTable for us + * to put plugin into when they show up, and drop from when + * they disappear. + * + * We need to think more carefully about the way for us to get + * the user_ptr from the global environment. + * + * We need to think more carefully about how interface registration + * etc. interact with plugin loading, reference counts, etc. and how + * the application that uses us (i.e., heartbeat) interacts with us. + * + * Issues include: + * - freeing all memory, + * - making sure things are all cleaned up correctly + * - Thread-safety? + * + * I think the global system should handle thread-safety. + */ + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void *user_ptr) +{ + PIL_rc ret; + /* + * Force compiler to check our parameters... + */ + PILPluginInitFun fun = &PIL_PLUGIN_INIT; (void)fun; + + + if (user_ptr == NULL) { + imports->log(PIL_CRIT + , "Interface Manager %s requires non-NULL " + " user pointer (to GHashTable) at initialization" + , PIN3); + return PIL_INVAL; + } + + AuthPIImports = imports; + AuthPlugin = us; + + /* Register as a plugin */ + imports->register_plugin(us, &OurPIExports); + + + /* Register our interfaces */ + ret = imports->register_interface(us + , PIT + , PIN3 + , &AuthIfOps + , NULL + , &AuthIf /* Our interface object pointer */ + , (void**)&AuthIfImports /* Interface-imported functions */ + , user_ptr); + return ret; +} + +/* + * We get called for every authentication interface that gets registered. + * + * It's our job to make the authentication interface that's + * registering with us available to the system. + * + * We do that by adding it to a g_hash_table of authentication + * plugin. The rest of the system takes it from there... + * The key is the authentication method, and the data + * is a pointer to the functions the method exports. + * It's a piece of cake ;-) + */ +static PIL_rc +RegisterAuthIF(PILInterface* intf, void** imports) +{ + GHashTable* authtbl = intf->ifmanager->ud_interface; + + g_assert(authtbl != NULL); + + /* Reference count should now be one */ + g_assert(intf->refcnt == 1); + g_hash_table_insert(authtbl, intf->interfacename, intf->exports); + + return PIL_OK; +} + +/* Unregister a client authentication interface - + * We get called from the interface mgmt sys when someone requests that + * a interface be unregistered. + */ +static PIL_rc +UnregisterAuthIF(PILInterface*intf) +{ + GHashTable* authtbl = intf->ifmanager->ud_interface; + g_assert(authtbl != NULL); + + intf->refcnt--; + g_assert(intf->refcnt >= 0); + if (intf->refcnt <= 0) { + g_hash_table_remove(authtbl, intf->interfacetype); + } + return PIL_OK; +} + diff --git a/lib/plugins/InterfaceMgr/Makefile.am b/lib/plugins/InterfaceMgr/Makefile.am new file mode 100644 index 0000000..86b88d1 --- /dev/null +++ b/lib/plugins/InterfaceMgr/Makefile.am @@ -0,0 +1,33 @@ +# +# InterfaceMgr: Interface manager plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl \ + -I$(top_builddir)/lib/upmls -I$(top_srcdir)/lib/upmls + +## libraries + +plugindir = $(libdir)/@HB_PKG@/plugins/InterfaceMgr +plugin_LTLIBRARIES = generic.la + +generic_la_SOURCES = generic.c +generic_la_LDFLAGS = -export-dynamic -module -avoid-version diff --git a/lib/plugins/InterfaceMgr/generic.c b/lib/plugins/InterfaceMgr/generic.c new file mode 100644 index 0000000..6ddad3b --- /dev/null +++ b/lib/plugins/InterfaceMgr/generic.c @@ -0,0 +1,452 @@ +/* + * + * Generic interface (implementation) manager + * + * Copyright 2001 Alan Robertson + * Licensed under the GNU Lesser General Public License + * + * This manager will manage any number of types of interfaces. + * + * This means that when any implementations of our client interfaces register + * or unregister, it is us that makes their interfaces show up in the outside + * world. + * + * And, of course, we have to do this in a very generic way, since we have + * no idea about the client programs or interface types, or anything else. + * + * We do that by getting a parameter passed to us which tell us the names + * of the interface types we want to manage, and the address of a GHashTable + * for each type that we put the implementation in when they register + * themselves. + * + * So, each type of interface that we manage gets its own private + * GHashTable of the implementations of that type that are currently + * registered. + * + * For example, if we manage communication modules, their exported + * interfaces will be registered in a hash table. If we manage + * authentication modules, they'll have their (separate) hash table that + * their exported interfaces are registered in. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define PIL_PLUGINTYPE InterfaceMgr +#define PIL_PLUGINTYPE_S "InterfaceMgr" +#define PIL_PLUGIN generic +#define PIL_PLUGIN_S "generic" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +/* We are an interface manager... */ +#define ENABLE_PLUGIN_MANAGER_PRIVATE +#define ENABLE_PIL_DEFS_PRIVATE + +#include +#include + +#include + +PIL_PLUGIN_BOILERPLATE("1.0", GenDebugFlag, CloseGeneralPluginManager) + +/* + * Key is interface type, value is a PILGenericIfMgmtRqst. + * The key is g_strdup()ed, but the struct is not copied. + */ + +static gboolean FreeAKey(gpointer key, gpointer value, gpointer data); + +/* + * Places to store information gotten during registration. + */ +static const PILPluginImports* GenPIImports; /* Imported plugin fcns */ +static PILPlugin* GenPlugin; /* Our plugin info */ +static PILInterfaceImports* GenIfImports; /* Interface imported fcns */ + +/* Our exported generic interface management functions */ +static PIL_rc RegisterGenIF(PILInterface* ifenv, void** imports); + +static PIL_rc UnregisterGenIF(PILInterface*iifinfo); + +static PIL_rc CloseGenInterfaceManager(PILInterface*, void* info); + +/* + * Our Interface Manager interfaces - exported to the universe! + * + * (or at least to the interface management universe ;-). + * + * These are the interfaces which are used to manage our + * client implementations + */ +static PILInterfaceOps GenIfOps = +{ RegisterGenIF +, UnregisterGenIF +}; + + +PIL_rc PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void*); + +/* + * Our user_ptr is presumed to point to NULL-terminated array of + * PILGenericIfMgmtRqst structs. + * + * These requests have pointers to GHashTables for us + * to put plugins into when they show up, and drop from when + * they disappear. + * + * Issues include: + * - freeing all memory, + * - making sure things are all cleaned up correctly + * - Thread-safety? + * + * IMHO the global system should handle thread-safety. + */ +static PIL_rc AddAnInterfaceType(PILPlugin*us, GHashTable* MasterTable, PILGenericIfMgmtRqst* req); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, PILPluginImports* imports, void *user_ptr) +{ + PIL_rc ret; + PILGenericIfMgmtRqst* user_req; + PILGenericIfMgmtRqst* curreq; + GHashTable* MasterTable = NULL; + /* + * Force the compiler to check our parameters... + */ + PILPluginInitFun fun = &PIL_PLUGIN_INIT; (void)fun; + + + GenPIImports = imports; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "IF manager %s: initializing.", PIL_PLUGIN_S); + } + + if (user_ptr == NULL) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "%s Interface Manager requires non-NULL " + " PILGenericIfMgmtRqst user pointer at initialization." + , PIL_PLUGIN_S); + return PIL_INVAL; + } + + GenPlugin = us; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "IF manager %s: registering as a plugin." + , PIL_PLUGIN_S); + } + + user_req = user_ptr; + MasterTable = g_hash_table_new(g_str_hash, g_str_equal); + us->ud_plugin = MasterTable; /* Override passed value */ + + /* Register ourselves as a plugin */ + + if ((ret = imports->register_plugin(us, &OurPIExports)) != PIL_OK) { + PILCallLog(imports->log, PIL_CRIT + , "IF manager %s unable to register as plugin (%s)" + , PIL_PLUGIN_S, PIL_strerror(ret)); + + return ret; + } + + /* + * Register to manage implementations + * for all the interface types we've been asked to manage. + */ + + for(curreq = user_req; curreq->iftype != NULL; ++curreq) { + PIL_rc newret; + + newret = AddAnInterfaceType(us, MasterTable, curreq); + + if (newret != PIL_OK) { + ret = newret; + } + } + + /* + * Our plugin and all our registered plugin types + * have ud_plugin pointing at MasterTable. + */ + + return ret; +} + +static PIL_rc +AddAnInterfaceType(PILPlugin*us, GHashTable* MasterTable, PILGenericIfMgmtRqst* req) +{ + PIL_rc rc; + PILInterface* GenIf; /* Our Generic Interface info*/ + + g_assert(MasterTable != NULL); + g_hash_table_insert(MasterTable, g_strdup(req->iftype), req); + + if (req->ifmap == NULL) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "IF manager %s: iftype %s has NULL" + " ifmap pointer address." + , PIL_PLUGIN_S, req->iftype); + return PIL_INVAL; + } + if ((*req->ifmap) != NULL) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "IF manager %s: iftype %s GHashTable pointer" + " was not initialized to NULL" + , PIL_PLUGIN_S, req->iftype); + return PIL_INVAL; + } + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "IF manager %s: registering ourselves" + " to manage interface type %s" + , PIL_PLUGIN_S, req->iftype); + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: ifmap: 0x%lx callback: 0x%lx" + " imports: 0x%lx" + , PIL_PLUGIN_S + , (unsigned long)req->ifmap + , (unsigned long)req->callback + , (unsigned long)req->importfuns); + } + + /* Create the hash table to communicate with this client */ + *(req->ifmap) = g_hash_table_new(g_str_hash, g_str_equal); + + rc = GenPIImports->register_interface(us + , PIL_PLUGINTYPE_S + , req->iftype /* the iftype we're managing here */ + , &GenIfOps + , CloseGenInterfaceManager + , &GenIf + , (void*)&GenIfImports + , MasterTable); /* Point ud_interface to MasterTable */ + + /* We don't ever want to be unloaded... */ + GenIfImports->ModRefCount(GenIf, +100); + + if (rc != PIL_OK) { + PILCallLog(GenPIImports->log, PIL_CRIT + , "Generic interface manager %s: unable to register" + " to manage interface type %s: %s" + , PIL_PLUGIN_S, req->iftype + , PIL_strerror(rc)); + } + return rc; +} + +static void +CloseGeneralPluginManager(PILPlugin* us) +{ + + GHashTable* MasterTable = us->ud_plugin; + int count; + + g_assert(MasterTable != NULL); + + /* + * All our clients have already been shut down automatically + * This is the final shutdown for us... + */ + + + /* There *shouldn't* be any keys in there ;-) */ + + if ((count=g_hash_table_size(MasterTable)) > 0) { + + /* But just in case there are... */ + g_hash_table_foreach_remove(MasterTable, FreeAKey, NULL); + } + g_hash_table_destroy(MasterTable); + us->ud_plugin = NULL; + return; +} + +/* + * We get called for every time an implementation registers itself as + * implementing one of the kinds of interfaces we manage. + * + * It's our job to make the implementation that's + * registering with us available to the system. + * + * We do that by adding it to a GHashTable for its interface type + * Our users in the rest of the system takes it from there... + * + * The key to the GHashTable is the implementation name, and the data is + * a pointer to the information the implementation exports. + * + * It's a piece of cake ;-) + */ +static PIL_rc +RegisterGenIF(PILInterface* intf, void** imports) +{ + PILGenericIfMgmtRqst* ifinfo; + GHashTable* MasterTable = intf->ifmanager->ud_interface; + + g_assert(MasterTable != NULL); + + /* Reference count should now be one */ + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: interface %s/%s registering." + , PIL_PLUGIN_S, intf->interfacetype->typename + , intf->interfacename); + } + g_assert(intf->refcnt == 1); + /* + * We need to add it to the table that goes with this particular + * type of interface. + */ + if ((ifinfo = g_hash_table_lookup(MasterTable + , intf->interfacetype->typename)) != NULL) { + GHashTable* ifmap = *(ifinfo->ifmap); + + g_hash_table_insert(ifmap, intf->interfacename,intf->exports); + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: Inserted interface [%s] in hash" + " table @ 0x%08lx" + , PIL_PLUGIN_S, intf->interfacename + , (unsigned long)ifmap); + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: Exports are here: 0x%08x" + , PIL_PLUGIN_S + , GPOINTER_TO_UINT(intf->exports)); + } + + if (ifinfo->callback != NULL) { + PILInterfaceType* t = intf->interfacetype; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: callback 0x%lx" + , PIL_PLUGIN_S + , (unsigned long)ifinfo->callback); + } + ifinfo->callback(PIL_REGISTER + , t->universe->piuniv, intf->interfacename + , t->typename, ifinfo->userptr); + } + + *imports = ifinfo->importfuns; + + return PIL_OK; + + }else{ + PILCallLog(GenPIImports->log, PIL_WARN + , "RegisterGenIF: interface type %s not found" + , intf->interfacename); + } + return PIL_INVAL; +} + +/* Unregister an implementation - + * We get called from the interface management system when someone + * has requested that an implementation of a client interface be + * unregistered. + */ +static PIL_rc +UnregisterGenIF(PILInterface*intf) +{ + GHashTable* MasterTable = intf->ifmanager->ud_interface; + PILGenericIfMgmtRqst* ifinfo; + + g_assert(MasterTable != NULL); + g_assert(intf->refcnt >= 0); + /* + * Go through the "master table" and find client table, + * notify client we're about to remove this entry, then + * then remove this entry from it. + */ + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: unregistering interface %s/%s." + , PIL_PLUGIN_S, intf->interfacetype->typename + , intf->interfacename); + } + if ((ifinfo = g_hash_table_lookup(MasterTable + , intf->interfacetype->typename)) != NULL) { + + GHashTable* ifmap = *(ifinfo->ifmap); + + if (ifinfo->callback != NULL) { + PILInterfaceType* t = intf->interfacetype; + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_DEBUG + , "%s IF manager: callback 0x%lx" + , PIL_PLUGIN_S + , (unsigned long)ifinfo->callback); + } + ifinfo->callback(PIL_UNREGISTER + , t->universe->piuniv, intf->interfacename + , t->typename, ifinfo->userptr); + } + + /* Remove the client entry from master table */ + g_hash_table_remove(ifmap, intf->interfacename); + + }else{ + PILCallLog(GenPIImports->log, PIL_WARN + , "UnregisterGenIF: interface type %s not found" + , intf->interfacename); + return PIL_INVAL; + } + return PIL_OK; +} + +/* + * Close down the generic interface manager. + */ +static PIL_rc +CloseGenInterfaceManager(PILInterface*intf, void* info) +{ + void* key; + void* data; + GHashTable* MasterTable = intf->ud_interface; + + if (GenDebugFlag) { + PILCallLog(GenPIImports->log, PIL_INFO + , "In CloseGenInterFaceManager on %s/%s (MasterTable: 0x%08lx)" + , intf->interfacetype->typename, intf->interfacename + , (unsigned long)MasterTable); + } + + + g_assert(MasterTable != NULL); + if (g_hash_table_lookup_extended(MasterTable + , intf->interfacename, &key, &data)) { + PILGenericIfMgmtRqst* ifinfo = data; + g_hash_table_destroy(*(ifinfo->ifmap)); + *(ifinfo->ifmap) = NULL; + g_hash_table_remove(MasterTable, key); + g_free(key); + }else{ + g_assert_not_reached(); + } + return PIL_OK; +} + +static gboolean +FreeAKey(gpointer key, gpointer value, gpointer data) +{ + g_free(key); + return TRUE; +} diff --git a/lib/plugins/Makefile.am b/lib/plugins/Makefile.am new file mode 100644 index 0000000..21827cd --- /dev/null +++ b/lib/plugins/Makefile.am @@ -0,0 +1,20 @@ +# +# Copyright (C) 2008 Andrew Beekhof +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in +SUBDIRS = InterfaceMgr stonith lrm compress diff --git a/lib/plugins/compress/Makefile.am b/lib/plugins/compress/Makefile.am new file mode 100644 index 0000000..3a3193a --- /dev/null +++ b/lib/plugins/compress/Makefile.am @@ -0,0 +1,52 @@ +# +# InterfaceMgr: Interface manager plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +if BUILD_ZLIB_COMPRESS_MODULE +zlibmodule = zlib.la +endif + +if BUILD_BZ2_COMPRESS_MODULE +bz2module = bz2.la +endif + +SUBDIRS = + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl \ + -I$(top_builddir)/lib/upmls -I$(top_srcdir)/lib/upmls + +AM_CFLAGS = @CFLAGS@ + +## libraries + +halibdir = $(libdir)/@HB_PKG@ +plugindir = $(halibdir)/plugins/compress +plugin_LTLIBRARIES = $(zlibmodule) $(bz2module) + +zlib_la_SOURCES = zlib.c +zlib_la_LDFLAGS = -export-dynamic -module -avoid-version -lz +zlib_la_LIBADD = $(top_builddir)/replace/libreplace.la + +bz2_la_SOURCES = bz2.c +bz2_la_LDFLAGS = -export-dynamic -module -avoid-version -lbz2 +bz2_la_LIBADD = $(top_builddir)/replace/libreplace.la + diff --git a/lib/plugins/compress/bz2.c b/lib/plugins/compress/bz2.c new file mode 100644 index 0000000..2eab116 --- /dev/null +++ b/lib/plugins/compress/bz2.c @@ -0,0 +1,142 @@ + /* bz2.c: compression module using bz2 for heartbeat. + * + * Copyright (C) 2005 Guochun Shi + * + * SECURITY NOTE: It would be very easy for someone to masquerade as the + * device that you're pinging. If they don't know the password, all they can + * do is echo back the packets that you're sending out, or send out old ones. + * This does mean that if you're using such an approach, that someone could + * make you think you have quorum when you don't during a cluster partition. + * The danger in that seems small, but you never know ;-) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + + +#define PIL_PLUGINTYPE HB_COMPRESS_TYPE +#define PIL_PLUGINTYPE_S HB_COMPRESS_TYPE_S +#define PIL_PLUGIN bz2 +#define PIL_PLUGIN_S "bz2" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include +#include +#include +#include +#include +#include +#include + + +static struct hb_compress_fns bz2Ops; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static struct hb_media_imports* OurImports; +static void* interfprivate; + +#define LOG PluginImports->log +#define MALLOC PluginImports->alloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &bz2Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , interfprivate); +} + +static int +bz2_compress(char* dest, size_t* destlen, + const char* _src, size_t srclen) +{ + int ret; + char* src; + unsigned int tmpdestlen; + + memcpy(&src, &_src, sizeof(char*)); + + tmpdestlen = *destlen; + ret = BZ2_bzBuffToBuffCompress(dest, &tmpdestlen, src, srclen, 1, 0, 30); + if (ret != BZ_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + return HA_FAIL; + } + + *destlen = tmpdestlen; + + return HA_OK; +} + +static int +bz2_decompress(char* dest, size_t* destlen, + const char* _src, size_t srclen) +{ + + int ret; + char* src; + unsigned int tmpdestlen; + + memcpy(&src, &_src, sizeof(char*)); + + tmpdestlen = *destlen; + ret = BZ2_bzBuffToBuffDecompress(dest, &tmpdestlen, src, srclen, 1, 0); + if (ret != BZ_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + return HA_FAIL; + } + + *destlen = tmpdestlen; + + return HA_OK; +} + +static const char* +bz2_getname(void) +{ + return "bz2"; +} + +static struct hb_compress_fns bz2Ops ={ + bz2_compress, + bz2_decompress, + bz2_getname, +}; diff --git a/lib/plugins/compress/zlib.c b/lib/plugins/compress/zlib.c new file mode 100644 index 0000000..5958966 --- /dev/null +++ b/lib/plugins/compress/zlib.c @@ -0,0 +1,135 @@ + /* zlib.c: compression module using zlib for heartbeat. + * + * Copyright (C) 2005 Guochun Shi + * + * SECURITY NOTE: It would be very easy for someone to masquerade as the + * device that you're pinging. If they don't know the password, all they can + * do is echo back the packets that you're sending out, or send out old ones. + * This does mean that if you're using such an approach, that someone could + * make you think you have quorum when you don't during a cluster partition. + * The danger in that seems small, but you never know ;-) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + + +#define PIL_PLUGINTYPE HB_COMPRESS_TYPE +#define PIL_PLUGINTYPE_S HB_COMPRESS_TYPE_S +#define PIL_PLUGIN zlib +#define PIL_PLUGIN_S "zlib" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include +#include +#include +#include +#include +#include + + +static struct hb_compress_fns zlibOps; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static struct hb_media_imports* OurImports; +static void* interfprivate; + +#define LOG PluginImports->log +#define MALLOC PluginImports->alloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &zlibOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , interfprivate); +} + +static int +zlib_compress(char* dest, size_t* _destlen, + const char* src, size_t _srclen) +{ + int ret; + uLongf destlen = *_destlen; + uLongf srclen = _srclen; + + ret = compress((Bytef *)dest, &destlen, (const Bytef *)src, srclen); + if (ret != Z_OK){ + cl_log(LOG_ERR, "%s: compression failed", + __FUNCTION__); + return HA_FAIL; + } + + *_destlen = destlen; + return HA_OK; + +} + +static int +zlib_decompress(char* dest, size_t* _destlen, + const char* src, size_t _srclen) +{ + + int ret; + uLongf destlen = *_destlen; + uLongf srclen = _srclen; + + ret = uncompress((Bytef *)dest, &destlen, (const Bytef *)src, srclen); + if (ret != Z_OK){ + cl_log(LOG_ERR, "%s: decompression failed", + __FUNCTION__); + return HA_FAIL; + } + + *_destlen = destlen; + + return HA_OK; +} + +static const char* +zlib_getname(void) +{ + return "zlib"; +} + +static struct hb_compress_fns zlibOps ={ + zlib_compress, + zlib_decompress, + zlib_getname, +}; diff --git a/lib/plugins/lrm/Makefile.am b/lib/plugins/lrm/Makefile.am new file mode 100644 index 0000000..fd24579 --- /dev/null +++ b/lib/plugins/lrm/Makefile.am @@ -0,0 +1,58 @@ +# +# Author: Sun Jiang Dong +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in +if UPSTART +SUBDIRS = dbus +endif + +LRM_DIR = lrm +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl +if UPSTART +INCLUDES += $(DBUS_CFLAGS) +endif + +halibdir = $(libdir)/@HB_PKG@ +havarlibdir = $(localstatedir)/lib/@HB_PKG@ +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(top_builddir)/lib/lrm/liblrm.la \ + $(GLIBLIB) + +plugindir = $(halibdir)/plugins/RAExec + +plugin_LTLIBRARIES = lsb.la ocf.la heartbeat.la +if UPSTART +plugin_LTLIBRARIES += upstart.la +endif + +lsb_la_SOURCES = raexeclsb.c +lsb_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version + +ocf_la_SOURCES = raexecocf.c +ocf_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version + +heartbeat_la_SOURCES = raexechb.c +heartbeat_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version + +if UPSTART +upstart_la_SOURCES = raexecupstart.c upstart-dbus.c +upstart_la_LDFLAGS = -L$(top_builddir)/lib/pils -lpils -export-dynamic -module -avoid-version \ + $(DBUS_LIBS) +endif diff --git a/lib/plugins/lrm/dbus/Makefile.am b/lib/plugins/lrm/dbus/Makefile.am new file mode 100644 index 0000000..ec93436 --- /dev/null +++ b/lib/plugins/lrm/dbus/Makefile.am @@ -0,0 +1,16 @@ +if UPSTART +BINDINGS=Upstart_Instance.h \ + Upstart_Job.h \ + Upstart.h + +all-local: + for header in $(BINDINGS); do \ + input=com.ubuntu.`echo $$header | sed 's/\.h//' | tr _ .`.xml; \ + $(DBUS_BINDING_TOOL) --mode=glib-client $$input > $$header; \ + done + +clean-local: + rm -f $(BINDINGS) + +EXTRA_DIST = *.xml +endif diff --git a/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml new file mode 100644 index 0000000..d4f7ab2 --- /dev/null +++ b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Instance.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml new file mode 100644 index 0000000..27f47a1 --- /dev/null +++ b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.Job.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml new file mode 100644 index 0000000..a4331cd --- /dev/null +++ b/lib/plugins/lrm/dbus/com.ubuntu.Upstart.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/plugins/lrm/raexechb.c b/lib/plugins/lrm/raexechb.c new file mode 100644 index 0000000..f9f1eb9 --- /dev/null +++ b/lib/plugins/lrm/raexechb.c @@ -0,0 +1,416 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: raexechb.c + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for LSB style. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PIL_PLUGINTYPE RA_EXEC_TYPE +#define PIL_PLUGIN heartbeat +#define PIL_PLUGINTYPE_S "RAExec" +#define PIL_PLUGIN_S "heartbeat" +#define PIL_PLUGINLICENSE LICENSE_PUBDOM +#define PIL_PLUGINLICENSEURL URL_PUBDOM + +static const char * RA_PATH = HB_RA_DIR; + +static const char meta_data_template[] = +"\n" +"\n" +"\n" +"1.0\n" +"\n" +"%s" +"\n" +"%s\n" +"\n" +"\n" +"\n" +"This argument will be passed as the first argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"\n" +"argv[1]\n" +"\n" +"\n" +"\n" +"\n" +"This argument will be passed as the second argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"\n" +"argv[2]\n" +"\n" +"\n" +"\n" +"\n" +"This argument will be passed as the third argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"\n" +"argv[3]\n" +"\n" +"\n" +"\n" +"\n" +"This argument will be passed as the fourth argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"\n" +"argv[4]\n" +"\n" +"\n" +"\n" +"\n" +"This argument will be passed as the fifth argument to the " +"heartbeat resource agent (assuming it supports one)\n" +"\n" +"argv[5]\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n"; + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + +static uniform_ret_execra_t map_ra_retvalue(int ret_execra + , const char * op_type, const char * std_output); +static int get_resource_list(GList ** rsc_info); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +#define MAX_PARAMETER_NUM 40 +typedef char * RA_ARGV[MAX_PARAMETER_NUM]; + +static const int MAX_LENGTH_OF_RSCNAME = 40, + MAX_LENGTH_OF_OPNAME = 40; + +static int prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params, RA_ARGV params_argv); +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; +static int idebuglevel = 0; + +/* + * Our plugin initialization and registration function + * It gets called when the plugin gets loaded. + */ +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + if (getenv(HADEBUGVAL) != NULL && atoi(getenv(HADEBUGVAL)) > 0 ) { + idebuglevel = atoi(getenv(HADEBUGVAL)); + cl_log(LOG_DEBUG, "LRM debug level set to %d", idebuglevel); + } + + /* Register our interfaces */ + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +/* + * Real work starts here ;-) + */ + +static int +execra( const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + RA_ARGV params_argv; + char ra_pathname[RA_MAX_NAME_LENGTH]; + uniform_ret_execra_t exit_value; + GString * debug_info; + char * optype_tmp = NULL; + int index_tmp = 0; + + /* How to generate the meta-data? There is nearly no value + * information in meta-data build up in current way. + * Should directly add meta-data to the script itself? + */ + if ( 0 == STRNCMP_CONST(op_type, "meta-data") ) { + printf("%s", get_resource_meta(rsc_type, provider)); + exit(0); + } + + /* To simulate the 'monitor' operation with 'status'. + * Now suppose there is no 'monitor' operation for heartbeat scripts. + */ + if ( 0 == STRNCMP_CONST(op_type, "monitor") ) { + optype_tmp = g_strdup("status"); + } else { + optype_tmp = g_strdup(op_type); + } + + /* Prepare the call parameter */ + if (0 > prepare_cmd_parameters(rsc_type, optype_tmp, params, params_argv)) { + cl_log(LOG_ERR, "HB RA: Error of preparing parameters"); + g_free(optype_tmp); + return -1; + } + g_free(optype_tmp); + + get_ra_pathname(RA_PATH, rsc_type, NULL, ra_pathname); + + /* let this log show only high loglevel. */ + if (idebuglevel > 1) { + debug_info = g_string_new(""); + do { + g_string_append(debug_info, params_argv[index_tmp]); + g_string_append(debug_info, " "); + } while (params_argv[++index_tmp] != NULL); + debug_info->str[debug_info->len-1] = '\0'; + + cl_log(LOG_DEBUG, "RA instance %s executing: heartbeat::%s" + , rsc_id, debug_info->str); + + g_string_free(debug_info, TRUE); + } + + closefiles(); /* don't leak open files */ + execv(ra_pathname, params_argv); + cl_perror("(%s:%s:%d) execv failed for %s" + , __FILE__, __FUNCTION__, __LINE__, ra_pathname); + + switch (errno) { + case ENOENT: /* No such file or directory */ + case EISDIR: /* Is a directory */ + exit_value = EXECRA_NOT_INSTALLED; + break; + default: + exit_value = EXECRA_EXEC_UNKNOWN_ERROR; + } + exit(exit_value); +} + +static int +prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params_ht, RA_ARGV params_argv) +{ + int tmp_len, index; + int ht_size = 0; + int param_num = 0; + char buf_tmp[20]; + void * value_tmp; + + if (params_ht) { + ht_size = g_hash_table_size(params_ht); + } + if ( ht_size+3 > MAX_PARAMETER_NUM ) { + cl_log(LOG_ERR, "Too many parameters"); + return -1; + } + + /* Now suppose the parameter format stored in Hashtabe is as like as + * key="1", value="-Wl,soname=test" + * Moreover, the key is supposed as a string transfered from an integer. + * It may be changed in the future. + */ + /* Notice: if ht_size==0, no actual arguments except op_type */ + for (index = 1; index <= ht_size; index++ ) { + snprintf(buf_tmp, sizeof(buf_tmp), "%d", index); + value_tmp = g_hash_table_lookup(params_ht, buf_tmp); + /* suppose the key is consecutive */ + if ( value_tmp == NULL ) { +/* cl_log(LOG_WARNING, "Parameter ordering error in"\ + "prepare_cmd_parameters, raexeclsb.c"); + cl_log(LOG_WARNING, "search key=%s.", buf_tmp); +*/ continue; + } + param_num ++; + params_argv[param_num] = g_strdup((char *)value_tmp); + } + + tmp_len = strnlen(rsc_type, MAX_LENGTH_OF_RSCNAME); + params_argv[0] = g_strndup(rsc_type, tmp_len); + /* Add operation code as the last argument */ + tmp_len = strnlen(op_type, MAX_LENGTH_OF_OPNAME); + params_argv[param_num+1] = g_strndup(op_type, tmp_len); + /* Add the teminating NULL pointer */ + params_argv[param_num+2] = NULL; + return 0; +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + + /* Now there is no formal related specification for Heartbeat RA + * scripts. Temporarily deal as LSB init script. + */ + /* Except op_type equals 'status', the UNIFORM_RET_EXECRA is compatible + with LSB standard. + */ + const char * stop_pattern1 = "*stopped*", + * stop_pattern2 = "*not*running*", + * running_pattern1 = "*running*", + * running_pattern2 = "*OK*"; + char * lower_std_output = NULL; + + if(ret_execra == EXECRA_NOT_INSTALLED) { + return ret_execra; + } + + if ( 0 == STRNCMP_CONST(op_type, "status") + || 0 == STRNCMP_CONST(op_type, "monitor")) { + if (std_output == NULL ) { + cl_log(LOG_WARNING, "No status output from the (hb) resource agent."); + return EXECRA_NOT_RUNNING; + } + + if (idebuglevel) { + cl_log(LOG_DEBUG, "RA output was: [%s]", std_output); + } + + lower_std_output = g_ascii_strdown(std_output, -1); + + if ( TRUE == g_pattern_match_simple(stop_pattern1 + , lower_std_output) || TRUE == + g_pattern_match_simple(stop_pattern2 + , lower_std_output) ) { + if (idebuglevel) { + cl_log(LOG_DEBUG + , "RA output [%s] matched stopped pattern" + " [%s] or [%s]" + , std_output + , stop_pattern1 + , stop_pattern2); + } + ret_execra = EXECRA_NOT_RUNNING; /* stopped */ + } else if ( TRUE == g_pattern_match_simple(running_pattern1 + , lower_std_output) || TRUE == + g_pattern_match_simple(running_pattern2 + , std_output) ) { + if (idebuglevel) { + cl_log(LOG_DEBUG + , "RA output [%s] matched running" + " pattern [%s] or [%s]" + , std_output, running_pattern1 + , running_pattern2); + } + ret_execra = EXECRA_OK; /* running */ + } else { + /* It didn't say it was running - must be stopped */ + cl_log(LOG_DEBUG, "RA output [%s] didn't match any pattern" + , std_output); + ret_execra = EXECRA_NOT_RUNNING; /* stopped */ + } + g_free(lower_std_output); + } + /* For non-status operation return code */ + if (ret_execra < 0) { + ret_execra = EXECRA_UNKNOWN_ERROR; + } + return ret_execra; +} + +static int +get_resource_list(GList ** rsc_info) +{ + return get_runnable_list(RA_PATH, rsc_info); +} + +static char* +get_resource_meta(const char* rsc_type, const char* provider) +{ + GString * meta_data; + + meta_data = g_string_new(""); + g_string_sprintf( meta_data, meta_data_template, rsc_type + , rsc_type, rsc_type); + return meta_data->str; +} +static int +get_provider_list(const char* ra_type, GList ** providers) +{ + if ( providers == NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: providers==NULL" + , __FUNCTION__, __LINE__); + return -2; + } + + if ( *providers != NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: *providers==NULL." + "This will cause memory leak." + , __FUNCTION__, __LINE__); + } + + /* Now temporarily make it fixed */ + *providers = g_list_append(*providers, g_strdup("heartbeat")); + + return g_list_length(*providers); +} diff --git a/lib/plugins/lrm/raexeclsb.c b/lib/plugins/lrm/raexeclsb.c new file mode 100644 index 0000000..46d7546 --- /dev/null +++ b/lib/plugins/lrm/raexeclsb.c @@ -0,0 +1,609 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: raexeclsb.c + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for LSB style. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ +/* + * Todo + * 1) Use flex&bison to make the analysis functions for lsb compliant comment? + * 2) Support multiple paths which contain lsb compliant RAs. + * 3) Optional and additional actions analysis? + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* Add it for compiling on OSX */ +#include +#include +#include +#include +#include + +#include + +#define PIL_PLUGINTYPE RA_EXEC_TYPE +#define PIL_PLUGIN lsb +#define PIL_PLUGINTYPE_S "RAExec" +#define PIL_PLUGIN_S "lsb" +#define PIL_PLUGINLICENSE LICENSE_PUBDOM +#define PIL_PLUGINLICENSEURL URL_PUBDOM + + +/* meta-data template for lsb scripts */ +/* Note: As for optional actions -- extracted from lsb standard. + * The reload and the try-restart options are optional. Other init script + * actions may be defined by the init script. + */ +#define meta_data_template \ +"\n"\ +"\n"\ +"\n"\ +" 1.0\n"\ +" \n"\ +" %s"\ +" \n"\ +" %s\n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" %s\n"\ +" %s\n"\ +" %s\n"\ +" %s\n"\ +" %s\n"\ +" %s\n"\ +" %s\n"\ +" \n"\ +"\n" + +/* The keywords for lsb-compliant comment */ +#define LSB_INITSCRIPT_INFOBEGIN_TAG "### BEGIN INIT INFO" +#define LSB_INITSCRIPT_INFOEND_TAG "### END INIT INFO" +#define PROVIDES "# Provides:" +#define REQ_START "# Required-Start:" +#define REQ_STOP "# Required-Stop:" +#define SHLD_START "# Should-Start:" +#define SHLD_STOP "# Should-Stop:" +#define DFLT_START "# Default-Start:" +#define DFLT_STOP "# Default-Stop:" +#define SHORT_DSCR "# Short-Description:" +#define DESCRIPTION "# Description:" + +#define ZAPXMLOBJ(m) \ + if ( (m) != NULL ) { \ + xmlFree(m); \ + (m) = NULL; \ + } + +#define RALSB_GET_VALUE(ptr, keyword) \ + if ( (ptr == NULL) & (0 == strncasecmp(buffer, keyword, strlen(keyword))) ) { \ + (ptr) = (char *)xmlEncodeEntitiesReentrant(NULL,BAD_CAST buffer+strlen(keyword)); \ + continue; \ + } +/* + * Are there multiple paths? Now according to LSB init scripts, the answer + * is 'no', but should be 'yes' for lsb none-init scripts? + */ +static const char * RA_PATH = LSB_RA_DIR; +/* Map to the return code of the 'monitor' operation defined in the OCF RA + * specification. + */ +static const int status_op_exitcode_map[] = { + EXECRA_OK, /* LSB_STATUS_OK */ + EXECRA_NOT_RUNNING, /* LSB_STATUS_VAR_PID */ + EXECRA_NOT_RUNNING, /* LSB_STATUS_VAR_LOCK */ + EXECRA_NOT_RUNNING, /* LSB_STATUS_STOPPED */ + EXECRA_UNKNOWN_ERROR /* LSB_STATUS_UNKNOWN */ +}; + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + +static uniform_ret_execra_t map_ra_retvalue(int ret_execra + , const char * op_type, const char * std_output); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_resource_list(GList ** rsc_info); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +#define MAX_PARAMETER_NUM 40 + +const int MAX_LENGTH_OF_RSCNAME = 40, + MAX_LENGTH_OF_OPNAME = 40; + +typedef char * RA_ARGV[MAX_PARAMETER_NUM]; + +static int prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params, RA_ARGV params_argv); +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; + +/* + * Our plugin initialization and registration function + * It gets called when the plugin gets loaded. + */ +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interfaces */ + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +/* + * Real work starts here ;-) + */ + +static int +execra( const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + RA_ARGV params_argv; + char ra_pathname[RA_MAX_NAME_LENGTH]; + GString * debug_info; + char * inherit_debuglevel = NULL; + char * optype_tmp = NULL; + int index_tmp = 0; + int save_errno; + + /* Specially handle the operation "metameta-data". To build up its + * output from templet, dummy data and its comment head. + */ + if ( 0 == STRNCMP_CONST(op_type, "meta-data")) { + printf("%s", get_resource_meta(rsc_type, provider)); + exit(0); + } + + /* To simulate the 'monitor' operation with 'status'. + * Now suppose there is no 'monitor' operation for LSB scripts. + */ + if (0 == STRNCMP_CONST(op_type, "monitor")) { + optype_tmp = g_strdup("status"); + } else { + optype_tmp = g_strdup(op_type); + } + + /* Prepare the call parameter */ + if ( prepare_cmd_parameters(rsc_type, optype_tmp, params, params_argv) + != 0) { + cl_log(LOG_ERR, "lsb RA: Error of preparing parameters"); + g_free(optype_tmp); + return -1; + } + g_free(optype_tmp); + get_ra_pathname(RA_PATH, rsc_type, NULL, ra_pathname); + + /* let this log show only high loglevel. */ + inherit_debuglevel = getenv(HADEBUGVAL); + if ((inherit_debuglevel != NULL) && (atoi(inherit_debuglevel) > 1)) { + debug_info = g_string_new(""); + do { + g_string_append(debug_info, params_argv[index_tmp]); + g_string_append(debug_info, " "); + } while (params_argv[++index_tmp] != NULL); + debug_info->str[debug_info->len-1] = '\0'; + + cl_log(LOG_DEBUG, "RA instance %s executing: lsb::%s" + , rsc_id, debug_info->str); + + g_string_free(debug_info, TRUE); + } + + closefiles(); /* don't leak open files */ + execv(ra_pathname, params_argv); + /* oops, exec failed */ + save_errno = errno; /* cl_perror may change errno */ + cl_perror("(%s:%s:%d) execv failed for %s" + , __FILE__, __FUNCTION__, __LINE__, ra_pathname); + errno = save_errno; + exit(get_failed_exec_rc()); +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + /* Except op_type equals 'status', the UNIFORM_RET_EXECRA is compatible + * with the LSB standard. + */ + if (ret_execra < 0) { + return EXECRA_UNKNOWN_ERROR; + } + + if(ret_execra == EXECRA_NOT_INSTALLED) { + return ret_execra; + } + + if ( 0 == STRNCMP_CONST(op_type, "status") + || 0 == STRNCMP_CONST(op_type, "monitor")) { + if (ret_execra < DIMOF(status_op_exitcode_map)) { + ret_execra = status_op_exitcode_map[ret_execra]; + } + } + return ret_execra; +} + +static int +get_resource_list(GList ** rsc_info) +{ + char ra_pathname[RA_MAX_NAME_LENGTH]; + FILE * fp; + gboolean next_continue, found_begin_tag, is_lsb_script; + int rc = 0; + GList *cur, *tmp; + const size_t BUFLEN = 80; + char buffer[BUFLEN]; + + if ((rc = get_runnable_list(RA_PATH, rsc_info)) <= 0) { + return rc; + } + + /* Use the following comment line as the filter patterns to choose + * the real LSB-compliant scripts. + * "### BEGIN INIT INFO" and "### END INIT INFO" + */ + cur = g_list_first(*rsc_info); + while ( cur != NULL ) { + get_ra_pathname(RA_PATH, cur->data, NULL, ra_pathname); + if ( (fp = fopen(ra_pathname, "r")) == NULL ) { + tmp = g_list_next(cur); + *rsc_info = g_list_remove(*rsc_info, cur->data); + if (cur->data) + g_free(cur->data); + cur = tmp; + continue; + } + is_lsb_script = FALSE; + next_continue = FALSE; + found_begin_tag = FALSE; + while (NULL != fgets(buffer, BUFLEN, fp)) { + /* Handle the lines over BUFLEN(80) columns, only + * the first part is compared. + */ + if ( next_continue == TRUE ) { + continue; + } + if (strlen(buffer) == BUFLEN ) { + next_continue = TRUE; + } else { + next_continue = FALSE; + } + /* Shorten the search time */ + if (buffer[0] != '#' && buffer[0] != ' ' + && buffer[0] != '\n') { + break; /* donnot find */ + } + + if (found_begin_tag == TRUE && 0 == strncasecmp(buffer + , LSB_INITSCRIPT_INFOEND_TAG + , strlen(LSB_INITSCRIPT_INFOEND_TAG)) ) { + is_lsb_script = TRUE; + break; + } + if (found_begin_tag == FALSE && 0 == strncasecmp(buffer + , LSB_INITSCRIPT_INFOBEGIN_TAG + , strlen(LSB_INITSCRIPT_INFOBEGIN_TAG)) ) { + found_begin_tag = TRUE; + } + } + fclose(fp); + tmp = g_list_next(cur); + +/* + * Temporarily remove the filter to the initscript, or many initscripts on + * many distros, such as RHEL4 and fedora5, cannot be used by management GUI. + * Please refer to the bug + * http://www.osdl.org/developer_bugzilla/show_bug.cgi?id=1250 + */ + +#if 0 + if ( is_lsb_script != TRUE ) { + *rsc_info = g_list_remove(*rsc_info, cur->data); + g_free(cur->data); + } +#else + (void) is_lsb_script; +#endif + cur = tmp; + } + + return g_list_length(*rsc_info); +} + +static int +prepare_cmd_parameters(const char * rsc_type, const char * op_type, + GHashTable * params_ht, RA_ARGV params_argv) +{ + int tmp_len; + int ht_size = 0; +#if 0 + /* Reserve it for possible furture use */ + int index; + void * value_tmp = NULL; + char buf_tmp[20]; +#endif + + if (params_ht) { + ht_size = g_hash_table_size(params_ht); + } + + /* Need 3 additonal spaces for accomodating: + * argv[0] = RA_file_name(RA_TYPE) + * argv[1] = operation + * a terminal NULL + */ + if ( ht_size+3 > MAX_PARAMETER_NUM ) { + cl_log(LOG_ERR, "Too many parameters"); + return -1; + } + + tmp_len = strnlen(rsc_type, MAX_LENGTH_OF_RSCNAME); + params_argv[0] = g_strndup(rsc_type, tmp_len); + /* Add operation code as the first argument */ + tmp_len = strnlen(op_type, MAX_LENGTH_OF_OPNAME); + params_argv[1] = g_strndup(op_type, tmp_len); + + /* + * No actual arguments needed except op_type. + * Add the teminating NULL pointer. + */ + params_argv[2] = NULL; + if ( (ht_size != 0) && (0 != STRNCMP_CONST(op_type, "status")) ) { + cl_log(LOG_WARNING, "For LSB init script, no additional " + "parameters are needed."); + } + +/* Actually comment the following code, but I still think it may be used + * in the future for LSB none-initial scripts, so reserver it. + */ +#if 0 + /* Now suppose the parameter formate stored in Hashtabe is like + * key="1", value="-Wl,soname=test" + * Moreover, the key is supposed as a string transfered from an integer. + * It may be changed in the future. + */ + for (index = 1; index <= ht_size; index++ ) { + snprintf(buf_tmp, sizeof(buf_tmp), "%d", index); + value_tmp = g_hash_table_lookup(params_ht, buf_tmp); + /* suppose the key is consecutive */ + if ( value_tmp == NULL ) { + cl_log(LOG_ERR, "Parameter ordering error in"\ + "prepare_cmd_parameters, raexeclsb.c"); + return -1; + } + params_argv[index+1] = g_strdup((char *)value_tmp); + } +#endif + + return 0; +} + +static char* +get_resource_meta(const char* rsc_type, const char* provider) +{ + char ra_pathname[RA_MAX_NAME_LENGTH]; + FILE * fp; + gboolean next_continue; + GString * meta_data; + const size_t BUFLEN = 132; + char buffer[BUFLEN]; + char * provides = NULL, + * req_start = NULL, + * req_stop = NULL, + * shld_start = NULL, + * shld_stop = NULL, + * dflt_start = NULL, + * dflt_stop = NULL, + * s_dscrpt = NULL, + * xml_l_dscrpt = NULL; + GString * l_dscrpt = NULL; + + /* + * Use the following tags to find the LSb-compliant comment block. + * "### BEGIN INIT INFO" and "### END INIT INFO" + */ + get_ra_pathname(RA_PATH, rsc_type, NULL, ra_pathname); + if ( (fp = fopen(ra_pathname, "r")) == NULL ) { + cl_log(LOG_ERR, "Failed to open lsb RA %s. No meta-data gotten." + , rsc_type); + return NULL; + } + meta_data = g_string_new(""); + + next_continue = FALSE; + +/* + * Is not stick to the rule that the description should be located in the + * comment block between "### BEGIN INIT INFO" and "### END INIT INFO". + * Please refer to the bug + * http://www.osdl.org/developer_bugzilla/show_bug.cgi?id=1250 + */ +#if 0 + while (NULL != fgets(buffer, BUFLEN, fp)) { + /* Handle the lines over BUFLEN(80) columns, only + * the first part is compared. + */ + if ( next_continue == TRUE ) { + continue; + } + if (strlen(buffer) == BUFLEN ) { + next_continue = TRUE; + } else { + next_continue = FALSE; + } + + if ( 0 == strncasecmp(buffer , LSB_INITSCRIPT_INFOBEGIN_TAG + , strlen(LSB_INITSCRIPT_INFOBEGIN_TAG)) ) { + break; + } + } +#else + (void) next_continue; +#endif + + /* Enter into the lsb-compliant comment block */ + while ( NULL != fgets(buffer, BUFLEN, fp) ) { + /* Now suppose each of the following eight arguments contain + * only one line + */ + RALSB_GET_VALUE(provides, PROVIDES) + RALSB_GET_VALUE(req_start, REQ_START) + RALSB_GET_VALUE(req_stop, REQ_STOP) + RALSB_GET_VALUE(shld_start, SHLD_START) + RALSB_GET_VALUE(shld_stop, SHLD_STOP) + RALSB_GET_VALUE(dflt_start, DFLT_START) + RALSB_GET_VALUE(dflt_stop, DFLT_STOP) + RALSB_GET_VALUE(s_dscrpt, SHORT_DSCR) + + /* Long description may cross multiple lines */ + if ( (l_dscrpt == NULL) && (0 == strncasecmp(buffer, DESCRIPTION + , strlen(DESCRIPTION))) ) { + l_dscrpt = g_string_new(buffer+strlen(DESCRIPTION)); + /* Between # and keyword, more than one space, or a tab + * character, indicates the continuation line. + * Extracted from LSB init script standard + */ + while ( NULL != fgets(buffer, BUFLEN, fp) ) { + if ( (0 == strncmp(buffer, "# ", 3)) + || (0 == strncmp(buffer, "#\t", 2)) ) { + buffer[0] = ' '; + l_dscrpt = g_string_append(l_dscrpt + , buffer); + } else { + fputs(buffer, fp); + break; /* Long description ends */ + } + } + continue; + } + if( l_dscrpt ) + xml_l_dscrpt = (char *)xmlEncodeEntitiesReentrant(NULL, + BAD_CAST (l_dscrpt->str)); + + if ( 0 == strncasecmp(buffer, LSB_INITSCRIPT_INFOEND_TAG + , strlen(LSB_INITSCRIPT_INFOEND_TAG)) ) { + /* Get to the out border of LSB comment block */ + break; + } + + if ( buffer[0] != '#' ) { + break; /* Out of comment block in the beginning */ + } + } + fclose(fp); + + g_string_sprintf( meta_data, meta_data_template, rsc_type + , (xml_l_dscrpt==NULL)? rsc_type : xml_l_dscrpt + , (s_dscrpt==NULL)? rsc_type : s_dscrpt + , (provides==NULL)? "" : provides + , (req_start==NULL)? "" : req_start + , (req_stop==NULL)? "" : req_stop + , (shld_start==NULL)? "" : shld_start + , (shld_stop==NULL)? "" : shld_stop + , (dflt_start==NULL)? "" : dflt_start + , (dflt_stop==NULL)? "" : dflt_stop ); + + ZAPXMLOBJ(xml_l_dscrpt); + ZAPXMLOBJ(s_dscrpt); + ZAPXMLOBJ(provides); + ZAPXMLOBJ(req_start); + ZAPXMLOBJ(req_stop); + ZAPXMLOBJ(shld_start); + ZAPXMLOBJ(shld_stop); + ZAPXMLOBJ(dflt_start); + ZAPXMLOBJ(dflt_stop); + + if( l_dscrpt ) + g_string_free(l_dscrpt, TRUE); + return meta_data->str; +} + +static int +get_provider_list(const char* ra_type, GList ** providers) +{ + if ( providers == NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: providers==NULL" + , __FUNCTION__, __LINE__); + return -2; + } + + if ( *providers != NULL ) { + cl_log(LOG_ERR, "%s:%d: Parameter error: *providers==NULL." + "This will cause memory leak." + , __FUNCTION__, __LINE__); + } + + /* Now temporarily make it fixed */ + *providers = g_list_append(*providers, g_strdup("heartbeat")); + + return g_list_length(*providers); +} diff --git a/lib/plugins/lrm/raexecocf.c b/lib/plugins/lrm/raexecocf.c new file mode 100644 index 0000000..f7cd7ed --- /dev/null +++ b/lib/plugins/lrm/raexecocf.c @@ -0,0 +1,496 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: raexecocf.c + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for LSB style. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* Add it for compiling on OSX */ +#ifdef HAVE_TIME_H +#include +#endif + +#include + +# define PIL_PLUGINTYPE RA_EXEC_TYPE +# define PIL_PLUGINTYPE_S "RAExec" +# define PIL_PLUGINLICENSE LICENSE_PUBDOM +# define PIL_PLUGINLICENSEURL URL_PUBDOM + +# define PIL_PLUGIN ocf +# define PIL_PLUGIN_S "ocf" +/* + * Are there multiple paths? Now according to OCF spec, the answer is 'no'. + * But actually or for future? + */ +static const char * RA_PATH = OCF_RA_DIR; + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); +static uniform_ret_execra_t map_ra_retvalue(int ret_execra, + const char * op_type, const char * std_output); +static int get_resource_list(GList ** rsc_info); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +static void add_OCF_prefix(GHashTable * params, GHashTable * new_params); +static void add_OCF_env_vars(GHashTable * env, const char * rsc_id, + const char * rsc_type, const char * provider); +static void add_prefix_foreach(gpointer key, gpointer value, + gpointer user_data); + +static void hash_to_str(GHashTable * , GString *); +static void hash_to_str_foreach(gpointer key, gpointer value, + gpointer user_data); + +static int raexec_setenv(GHashTable * env_params); +static void set_env(gpointer key, gpointer value, gpointer user_data); + +static gboolean let_remove_eachitem(gpointer key, gpointer value, + gpointer user_data); +static int get_providers(const char* class_path, const char* op_type, + GList ** providers); +static void merge_string_list(GList** old, GList* new); +static gint compare_str(gconstpointer a, gconstpointer b); + +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; + +/* + * Our plugin initialization and registration function + * It gets called when the plugin gets loaded. + */ +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interfaces */ + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +/* + * The function to execute a RA. + */ +static int +execra(const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + char ra_pathname[RA_MAX_NAME_LENGTH]; + GHashTable * tmp_for_setenv; + GString * params_gstring; + char * inherit_debuglevel = NULL; + int save_errno; + + get_ra_pathname(RA_PATH, rsc_type, provider, ra_pathname); + + /* Setup environment correctly */ + tmp_for_setenv = g_hash_table_new(g_str_hash, g_str_equal); + add_OCF_prefix(params, tmp_for_setenv); + add_OCF_env_vars(tmp_for_setenv, rsc_id, rsc_type, provider); + raexec_setenv(tmp_for_setenv); + g_hash_table_foreach_remove(tmp_for_setenv, let_remove_eachitem, NULL); + g_hash_table_destroy(tmp_for_setenv); + + /* let this log show only high loglevel. */ + inherit_debuglevel = getenv(HADEBUGVAL); + if ((inherit_debuglevel != NULL) && (atoi(inherit_debuglevel) > 1)) { + params_gstring = g_string_new(""); + hash_to_str(params, params_gstring); + cl_log(LOG_DEBUG, "RA instance %s executing: OCF::%s %s. Parameters: " + "{%s}", rsc_id, rsc_type, op_type, params_gstring->str); + g_string_free(params_gstring, TRUE); + } + + closefiles(); /* don't leak open files */ + /* execute the RA */ + execl(ra_pathname, ra_pathname, op_type, (const char *)NULL); + /* oops, exec failed */ + save_errno = errno; /* cl_perror may change errno */ + cl_perror("(%s:%s:%d) execl failed for %s" + , __FILE__, __FUNCTION__, __LINE__, ra_pathname); + errno = save_errno; + exit(get_failed_exec_rc()); +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + /* Because the UNIFORM_RET_EXECRA is compatible with OCF standard, + * no actual mapping except validating, which ensure the return code + * will be in the range 0 to 7. Too strict? + */ + if (ret_execra < 0 || ret_execra > 9) { + cl_log(LOG_WARNING, "mapped the invalid return code %d." + , ret_execra); + ret_execra = EXECRA_UNKNOWN_ERROR; + } + return ret_execra; +} + +static gint +compare_str(gconstpointer a, gconstpointer b) +{ + return strncmp(a,b,RA_MAX_NAME_LENGTH); +} + +static int +get_resource_list(GList ** rsc_info) +{ + struct dirent **namelist; + GList* item; + int file_num; + char subdir[FILENAME_MAX+1]; + + if ( rsc_info == NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list"); + return -2; + } + + if ( *rsc_info != NULL ) { + cl_log(LOG_ERR, "Parameter error: get_resource_list."\ + "will cause memory leak."); + *rsc_info = NULL; + } + file_num = scandir(RA_PATH, &namelist, NULL, alphasort); + if (file_num < 0) { + return -2; + } + while (file_num--) { + GList* ra_subdir = NULL; + struct stat prop; + if ('.' == namelist[file_num]->d_name[0]) { + free(namelist[file_num]); + continue; + } + + snprintf(subdir,FILENAME_MAX,"%s/%s", + RA_PATH, namelist[file_num]->d_name); + + if (stat(subdir, &prop) == -1) { + cl_perror("%s:%s:%d: stat failed for %s" + , __FILE__, __FUNCTION__, __LINE__, subdir); + free(namelist[file_num]); + continue; + } else if (!S_ISDIR(prop.st_mode)) { + free(namelist[file_num]); + continue; + } + + get_runnable_list(subdir,&ra_subdir); + + merge_string_list(rsc_info,ra_subdir); + + while (NULL != (item = g_list_first(ra_subdir))) { + ra_subdir = g_list_remove_link(ra_subdir, item); + g_free(item->data); + g_list_free_1(item); + } + + free(namelist[file_num]); + } + free(namelist); + + return 0; +} + +static void +merge_string_list(GList** old, GList* new) +{ + GList* item = NULL; + char* newitem; + for( item=g_list_first(new); NULL!=item; item=g_list_next(item)){ + if (!g_list_find_custom(*old, item->data,compare_str)){ + newitem = g_strndup(item->data,RA_MAX_NAME_LENGTH); + *old = g_list_append(*old, newitem); + } + } +} + +static int +get_provider_list(const char* ra_type, GList ** providers) +{ + int ret; + ret = get_providers(RA_PATH, ra_type, providers); + if (0>ret) { + cl_log(LOG_ERR, "scandir failed in OCF RA plugin"); + } + return ret; +} + +static char* +get_resource_meta(const char* rsc_type, const char* provider) +{ + const int BUFF_LEN=4096; + int read_len = 0; + char buff[BUFF_LEN]; + char* data = NULL; + GString* g_str_tmp = NULL; + char ra_pathname[RA_MAX_NAME_LENGTH]; + FILE* file = NULL; + GHashTable * tmp_for_setenv; + struct timespec short_sleep = {0,200000000L}; /*20ms*/ + + get_ra_pathname(RA_PATH, rsc_type, provider, ra_pathname); + + strncat(ra_pathname, " meta-data",RA_MAX_NAME_LENGTH-strlen(ra_pathname)-1); + tmp_for_setenv = g_hash_table_new(g_str_hash, g_str_equal); + add_OCF_env_vars(tmp_for_setenv, "DUMMY_INSTANCE", rsc_type, provider); + raexec_setenv(tmp_for_setenv); + g_hash_table_foreach_remove(tmp_for_setenv, let_remove_eachitem, NULL); + g_hash_table_destroy(tmp_for_setenv); + + file = popen(ra_pathname, "r"); + if (NULL==file) { + cl_log(LOG_ERR, "%s: popen failed: %s", __FUNCTION__, strerror(errno)); + return NULL; + } + + g_str_tmp = g_string_new(""); + while(!feof(file)) { + read_len = fread(buff, 1, BUFF_LEN - 1, file); + if (0len) { + g_string_free(g_str_tmp, TRUE); + return NULL; + } + data = (char*)g_new(char, g_str_tmp->len+1); + data[0] = data[g_str_tmp->len] = 0; + strncpy(data, g_str_tmp->str, g_str_tmp->len); + + g_string_free(g_str_tmp, TRUE); + + return data; +} + +static void +add_OCF_prefix(GHashTable * env_params, GHashTable * new_env_params) +{ + if (env_params) { + g_hash_table_foreach(env_params, add_prefix_foreach, + new_env_params); + } +} + +static void +add_prefix_foreach(gpointer key, gpointer value, gpointer user_data) +{ + const int MAX_LENGTH_OF_ENV = 128; + int prefix = STRLEN_CONST("OCF_RESKEY_"); + GHashTable * new_hashtable = (GHashTable *) user_data; + char * newkey; + int keylen = strnlen((char*)key, MAX_LENGTH_OF_ENV-prefix)+prefix+1; + + newkey = g_new(gchar, keylen); + strncpy(newkey, "OCF_RESKEY_", keylen); + strncat(newkey, key, keylen-strlen(newkey)-1); + g_hash_table_insert(new_hashtable, (gpointer)newkey, g_strdup(value)); +} + +static void +hash_to_str(GHashTable * params , GString * str) +{ + if (params) { + g_hash_table_foreach(params, hash_to_str_foreach, str); + } +} + +static void +hash_to_str_foreach(gpointer key, gpointer value, gpointer user_data) +{ + char buffer_tmp[60]; + GString * str = (GString *)user_data; + + snprintf(buffer_tmp, 60, "%s=%s ", (char *)key, (char *)value); + str = g_string_append(str, buffer_tmp); +} + +static gboolean +let_remove_eachitem(gpointer key, gpointer value, gpointer user_data) +{ + g_free(key); + g_free(value); + return TRUE; +} + +static int +raexec_setenv(GHashTable * env_params) +{ + if (env_params) { + g_hash_table_foreach(env_params, set_env, NULL); + } + return 0; +} + +static void +set_env(gpointer key, gpointer value, gpointer user_data) +{ + if (setenv(key, value, 1) != 0) { + cl_log(LOG_ERR, "setenv failed in raexecocf."); + } +} + +static int +get_providers(const char* class_path, const char* ra_type, GList ** providers) +{ + struct dirent **namelist; + int file_num; + + if ( providers == NULL ) { + cl_log(LOG_ERR, "Parameter error: get_providers"); + return -2; + } + + if ( *providers != NULL ) { + cl_log(LOG_ERR, "Parameter error: get_providers."\ + "will cause memory leak."); + *providers = NULL; + } + + file_num = scandir(class_path, &namelist, 0, alphasort); + if (file_num < 0) { + return -2; + }else{ + char tmp_buffer[FILENAME_MAX+1]; + struct stat prop; + + while (file_num--) { + if ('.' == namelist[file_num]->d_name[0]) { + free(namelist[file_num]); + continue; + } + snprintf(tmp_buffer,FILENAME_MAX,"%s/%s", + class_path, namelist[file_num]->d_name); + stat(tmp_buffer, &prop); + if (!S_ISDIR(prop.st_mode)) { + free(namelist[file_num]); + continue; + } + + snprintf(tmp_buffer,FILENAME_MAX,"%s/%s/%s", + class_path, namelist[file_num]->d_name, ra_type); + + if ( filtered(tmp_buffer) == TRUE ) { + *providers = g_list_append(*providers, + g_strdup(namelist[file_num]->d_name)); + } + free(namelist[file_num]); + } + free(namelist); + } + return g_list_length(*providers); +} + +static void +add_OCF_env_vars(GHashTable * env, const char * rsc_id, + const char * rsc_type, const char * provider) +{ + if ( env == NULL ) { + cl_log(LOG_WARNING, "env should not be a NULL pointer."); + return; + } + + g_hash_table_insert(env, g_strdup("OCF_RA_VERSION_MAJOR"), + g_strdup("1")); + g_hash_table_insert(env, g_strdup("OCF_RA_VERSION_MINOR"), + g_strdup("0")); + g_hash_table_insert(env, g_strdup("OCF_ROOT"), + g_strdup(OCF_ROOT_DIR)); + + if ( rsc_id != NULL ) { + g_hash_table_insert(env, g_strdup("OCF_RESOURCE_INSTANCE"), + g_strdup(rsc_id)); + } + + /* Currently the rsc_type=="the filename of the RA script/executable", + * It seems always correct even in the furture. ;-) + */ + if ( rsc_type != NULL ) { + g_hash_table_insert(env, g_strdup("OCF_RESOURCE_TYPE"), + g_strdup(rsc_type)); + } + + /* Notes: this is not added to specification yet. Sept 10,2004 */ + if ( provider != NULL ) { + g_hash_table_insert(env, g_strdup("OCF_RESOURCE_PROVIDER"), + g_strdup(provider)); + } +} + diff --git a/lib/plugins/lrm/raexecupstart.c b/lib/plugins/lrm/raexecupstart.c new file mode 100644 index 0000000..baa0278 --- /dev/null +++ b/lib/plugins/lrm/raexecupstart.c @@ -0,0 +1,222 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: raexecupstart.c + * Copyright (C) 2010 Senko Rasic + * Copyright (c) 2010 Ante Karamatic + * + * Heavily based on raexeclsb.c and raexechb.c: + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + * This code implements the Resource Agent Plugin Module for Upstart. + * It's a part of Local Resource Manager. Currently it's used by lrmd only. + */ + +#define PIL_PLUGINTYPE RA_EXEC_TYPE +#define PIL_PLUGIN upstart +#define PIL_PLUGINTYPE_S "RAExec" +#define PIL_PLUGIN_S "upstart" +#define PIL_PLUGINLICENSE LICENSE_PUBDOM +#define PIL_PLUGINLICENSEURL URL_PUBDOM + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* Add it for compiling on OSX */ +#include +#include +#include +#include +#include + +#include + +#include + +#include "upstart-dbus.h" + +#define meta_data_template \ +"\n"\ +"\n"\ +"\n"\ +" 1.0\n"\ +" \n"\ +" %s"\ +" \n"\ +" %s\n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +" \n"\ +"\n" + +/* The begin of exported function list */ +static int execra(const char * rsc_id, + const char * rsc_type, + const char * provider, + const char * op_type, + const int timeout, + GHashTable * params); + +static uniform_ret_execra_t map_ra_retvalue(int ret_execra + , const char * op_type, const char * std_output); +static char* get_resource_meta(const char* rsc_type, const char* provider); +static int get_resource_list(GList ** rsc_info); +static int get_provider_list(const char* ra_type, GList ** providers); + +/* The end of exported function list */ + +/* The begin of internal used function & data list */ +#define MAX_PARAMETER_NUM 40 + +const int MAX_LENGTH_OF_RSCNAME = 40, + MAX_LENGTH_OF_OPNAME = 40; + +typedef char * RA_ARGV[MAX_PARAMETER_NUM]; + +/* The end of internal function & data list */ + +/* Rource agent execution plugin operations */ +static struct RAExecOps raops = +{ execra, + map_ra_retvalue, + get_resource_list, + get_provider_list, + get_resource_meta +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static void* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin * us, const PILPluginImports* imports) +{ + PluginImports = imports; + OurPlugin = us; + + imports->register_plugin(us, &OurPIExports); + + g_type_init (); + + return imports->register_interface(us, PIL_PLUGINTYPE_S, PIL_PLUGIN_S, + &raops, NULL, &OurInterface, &OurImports, + interfprivate); +} + +static int +execra( const char * rsc_id, const char * rsc_type, const char * provider, + const char * op_type, const int timeout, GHashTable * params) +{ + UpstartJobCommand cmd; + + if (!g_strcmp0(op_type, "meta-data")) { + printf("%s", get_resource_meta(rsc_type, provider)); + exit(EXECRA_OK); + } else if (!g_strcmp0(op_type, "monitor") || !g_strcmp0(op_type, "status")) { + gboolean running = upstart_job_is_running (rsc_type); + printf("%s", running ? "running" : "stopped"); + + if (running) + exit(EXECRA_OK); + else + exit(EXECRA_NOT_RUNNING); + } else if (!g_strcmp0(op_type, "start")) { + cmd = UPSTART_JOB_START; + } else if (!g_strcmp0(op_type, "stop")) { + cmd = UPSTART_JOB_STOP; + } else if (!g_strcmp0(op_type, "restart")) { + cmd = UPSTART_JOB_RESTART; + } else { + exit(EXECRA_UNIMPLEMENT_FEATURE); + } + + /* It'd be better if it returned GError, so we can distinguish + * between failure modes (can't contact upstart, no such job, + * or failure to do action. */ + if (upstart_job_do(rsc_type, cmd, timeout)) { + exit(EXECRA_OK); + } else { + exit(EXECRA_NO_RA); + } +} + +static uniform_ret_execra_t +map_ra_retvalue(int ret_execra, const char * op_type, const char * std_output) +{ + /* no need to map anything, execra() returns correct exit code */ + return ret_execra; +} + +static int +get_resource_list(GList ** rsc_info) +{ + gchar **jobs; + gint i; + *rsc_info = NULL; + + jobs = upstart_get_all_jobs(); + + if (!jobs) + return 0; + + for (i = 0; jobs[i] != NULL; i++) { + *rsc_info = g_list_prepend(*rsc_info, jobs[i]); + } + + /* free the array, but not the strings */ + g_free(jobs); + + *rsc_info = g_list_reverse(*rsc_info); + return g_list_length(*rsc_info); +} + +static char * +get_resource_meta (const gchar *rsc_type, const gchar *provider) +{ + return g_strdup_printf(meta_data_template, rsc_type, + rsc_type, rsc_type); +} + +static int +get_provider_list (const gchar *ra_type, GList **providers) +{ + *providers = g_list_prepend(*providers, g_strdup("upstart")); + return g_list_length(*providers); +} + diff --git a/lib/plugins/lrm/upstart-dbus.c b/lib/plugins/lrm/upstart-dbus.c new file mode 100644 index 0000000..b994d87 --- /dev/null +++ b/lib/plugins/lrm/upstart-dbus.c @@ -0,0 +1,406 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: upstart-dbus.c + * Copyright (C) 2010 Senko Rasic + * Copyright (c) 2010 Ante Karamatic + * + * + * Each exported function is standalone, and creates a new connection to + * the upstart daemon. This is because lrmd plugins fork off for exec, + * and if we try and share the connection, the whole thing blocks + * indefinitely. + */ + +#include "upstart-dbus.h" + +#include +#include + +#include + +#include "dbus/Upstart.h" +#include "dbus/Upstart_Job.h" +#include "dbus/Upstart_Instance.h" + +#include + +#define SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" +#define UPSTART_BUS_ADDRESS "unix:abstract=/com/ubuntu/upstart" +#define UPSTART_SERVICE_NAME "com.ubuntu.Upstart" +#define UPSTART_MANAGER_PATH "/com/ubuntu/Upstart" +#define UPSTART_IFACE "com.ubuntu.Upstart0_6" +#define UPSTART_JOB_IFACE UPSTART_IFACE ".Job" +#define UPSTART_INSTANCE_IFACE UPSTART_IFACE ".Instance" +#define UPSTART_ERROR_ALREADY_STARTED UPSTART_IFACE ".Error.AlreadyStarted" +#define UPSTART_ERROR_UNKNOWN_INSTANCE UPSTART_IFACE ".Error.UnknownInstance" + +static DBusGConnection * +get_connection(void) +{ + GError *error = NULL; + DBusGConnection *conn; + + conn = dbus_g_bus_get_private(DBUS_BUS_SYSTEM, NULL, &error); + + if (error) + { + g_error_free(error); + error = NULL; + + conn = dbus_g_connection_open("unix:abstract=/com/ubuntu/upstart", + &error); + + if (error) + { + g_warning("Can't connect to either system or Upstart " + "DBus bus."); + g_error_free(error); + + return NULL; + } + } + + return conn; +} + +static DBusGProxy * +new_proxy(DBusGConnection *conn, const gchar *object_path, + const gchar *iface) +{ + return dbus_g_proxy_new_for_name(conn, + UPSTART_SERVICE_NAME, + object_path, + iface); +} + +static GHashTable * +get_object_properties(DBusGProxy *obj, const gchar *iface) +{ + GError *error = NULL; + DBusGProxy *proxy; + GHashTable *asv; + GHashTable *retval; + GHashTableIter iter; + gpointer k, v; + + proxy = dbus_g_proxy_new_from_proxy(obj, + DBUS_INTERFACE_PROPERTIES, NULL); + + dbus_g_proxy_call(proxy, "GetAll", &error, G_TYPE_STRING, + iface, G_TYPE_INVALID, + dbus_g_type_get_map("GHashTable", + G_TYPE_STRING, + G_TYPE_VALUE), + &asv, G_TYPE_INVALID); + + if (error) { + g_warning("Error getting %s properties: %s", iface, error->message); + g_error_free(error); + g_object_unref(proxy); + return NULL; + } + + retval = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + g_hash_table_iter_init(&iter, asv); + while (g_hash_table_iter_next(&iter, &k, &v)) { + gchar *key = k; + GValue *val = v; + + /* all known properties are strings */ + if (G_VALUE_TYPE(val) == G_TYPE_STRING) { + g_hash_table_insert(retval, g_strdup(key), + g_value_dup_string(val)); + } + } + + g_hash_table_destroy(asv); + + return retval; +} + +gchar ** +upstart_get_all_jobs(void) +{ + DBusGConnection *conn; + DBusGProxy *manager; + GError *error = NULL; + GPtrArray *array; + gchar **retval = NULL; + gint i, j; + + conn = get_connection(); + if (!conn) + return NULL; + + manager = new_proxy(conn, UPSTART_MANAGER_PATH, UPSTART_IFACE); + + dbus_g_proxy_call(manager, "GetAllJobs", &error, G_TYPE_INVALID, + dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), + &array, G_TYPE_INVALID); + + if (error) + { + g_warning("Can't call GetAllJobs: %s", error->message); + g_error_free(error); + g_object_unref(manager); + dbus_g_connection_unref(conn); + return NULL; + } + + retval = g_new0(gchar *, array->len + 1); + + for (i = 0, j = 0; i < array->len; i++) + { + DBusGProxy *job; + + job = new_proxy(conn, g_ptr_array_index(array, i), + UPSTART_JOB_IFACE); + + if (job) { + GHashTable *props = get_object_properties(job, + UPSTART_JOB_IFACE); + + if (props) { + gchar *name = g_hash_table_lookup(props, + "name"); + + if (name) + retval[j++] = g_strdup(name); + + g_hash_table_destroy(props); + } + + g_object_unref(job); + } + } + + g_ptr_array_free(array, TRUE); + + g_object_unref(manager); + dbus_g_connection_unref(conn); + + return retval; +} + +static DBusGProxy * +upstart_get_job_by_name(DBusGConnection *conn, DBusGProxy *manager, + const gchar *name) +{ + GError *error = NULL; + gchar *object_path; + DBusGProxy *retval; + + dbus_g_proxy_call(manager, "GetJobByName", &error, G_TYPE_STRING, + name, G_TYPE_INVALID, DBUS_TYPE_G_OBJECT_PATH, &object_path, + G_TYPE_INVALID); + + if (error) + { + g_warning("Error calling GetJobByName: %s", error->message); + g_error_free(error); + return NULL; + } + + retval = new_proxy(conn, object_path, UPSTART_JOB_IFACE); + + g_free(object_path); + + return retval; +} + +static gchar ** +get_job_instances(DBusGProxy *job) +{ + GError *error = NULL; + GPtrArray *array; + gchar **retval; + gint i; + + dbus_g_proxy_call(job, "GetAllInstances", &error, G_TYPE_INVALID, + dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), + &array, G_TYPE_INVALID); + + if (error) + { + g_warning("Can't call GetAllInstances: %s", error->message); + g_error_free(error); + return NULL; + } + + retval = g_new0(gchar *, array->len + 1); + + for (i = 0; i < array->len; i++) + { + retval[i] = g_ptr_array_index(array, i); + } + + g_ptr_array_free(array, TRUE); + + return retval; +} + +static DBusGProxy * +get_first_instance(DBusGConnection *conn, DBusGProxy *job) +{ + gchar **instances; + DBusGProxy *instance = NULL; + + instances = get_job_instances(job); + + if (!instances) + return NULL; + + if (*instances) + { + instance = new_proxy(conn, instances[0], + UPSTART_INSTANCE_IFACE); + } + + g_strfreev(instances); + return instance; +} + +gboolean +upstart_job_is_running(const gchar *name) +{ + DBusGConnection *conn; + DBusGProxy *manager; + DBusGProxy *job; + gboolean retval = FALSE; + + conn = get_connection(); + if (!conn) + return FALSE; + + manager = new_proxy(conn, UPSTART_MANAGER_PATH, UPSTART_IFACE); + + job = upstart_get_job_by_name(conn, manager, name); + if (job) { + DBusGProxy *instance = get_first_instance(conn, job); + + if (instance) { + GHashTable *props = get_object_properties(instance, + UPSTART_INSTANCE_IFACE); + + if (props) { + const gchar *state = g_hash_table_lookup(props, + "state"); + + retval = !g_strcmp0(state, "running"); + + g_hash_table_destroy(props); + } + + g_object_unref(instance); + } + + g_object_unref(job); + } + + g_object_unref(manager); + dbus_g_connection_unref(conn); + + return retval; +} + +gboolean +upstart_job_do(const gchar *name, UpstartJobCommand cmd, const int timeout) +{ + DBusGConnection *conn; + DBusGProxy *manager; + DBusGProxy *job; + gboolean retval; + + conn = get_connection(); + if (!conn) + return FALSE; + + manager = new_proxy(conn, UPSTART_MANAGER_PATH, UPSTART_IFACE); + + job = upstart_get_job_by_name(conn, manager, name); + if (job) { + GError *error = NULL; + const gchar *cmd_name = NULL; + gchar *instance_path = NULL; + gchar *no_args[] = { NULL }; + + switch (cmd) { + case UPSTART_JOB_START: + cmd_name = "Start"; + dbus_g_proxy_call_with_timeout (job, cmd_name, + timeout, &error, + G_TYPE_STRV, no_args, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID, + DBUS_TYPE_G_OBJECT_PATH, &instance_path, + G_TYPE_INVALID); + g_free (instance_path); + break; + case UPSTART_JOB_STOP: + cmd_name = "Stop"; + dbus_g_proxy_call_with_timeout(job, cmd_name, + timeout, &error, + G_TYPE_STRV, no_args, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID, + G_TYPE_INVALID); + break; + case UPSTART_JOB_RESTART: + cmd_name = "Restart"; + dbus_g_proxy_call_with_timeout (job, cmd_name, + timeout, &error, + G_TYPE_STRV, no_args, + G_TYPE_BOOLEAN, TRUE, + G_TYPE_INVALID, + DBUS_TYPE_G_OBJECT_PATH, &instance_path, + G_TYPE_INVALID); + g_free (instance_path); + break; + default: + g_assert_not_reached(); + } + + if (error) { + g_warning("Could not issue %s: %s", cmd_name, + error->message); + + /* ignore "already started" or "not running" errors */ + if (dbus_g_error_has_name(error, + UPSTART_ERROR_ALREADY_STARTED) || + dbus_g_error_has_name(error, + UPSTART_ERROR_UNKNOWN_INSTANCE)) { + retval = TRUE; + } else { + retval = FALSE; + } + g_error_free(error); + } else { + retval = TRUE; + } + + g_object_unref(job); + } else { + retval = FALSE; + } + + g_object_unref(manager); + dbus_g_connection_unref(conn); + return retval; +} + + diff --git a/lib/plugins/lrm/upstart-dbus.h b/lib/plugins/lrm/upstart-dbus.h new file mode 100644 index 0000000..bc72c95 --- /dev/null +++ b/lib/plugins/lrm/upstart-dbus.h @@ -0,0 +1,36 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * File: upstart-dbus.c + * Copyright (C) 2010 Senko Rasic + * Copyright (c) 2010 Ante Karamatic + */ +#ifndef _UPSTART_DBUS_H_ +#define _UPSTART_DBUS_H_ + +#include + +typedef enum { + UPSTART_JOB_START, + UPSTART_JOB_STOP, + UPSTART_JOB_RESTART +} UpstartJobCommand; + +G_GNUC_INTERNAL gchar **upstart_get_all_jobs(void); +G_GNUC_INTERNAL gboolean upstart_job_do(const gchar *name, UpstartJobCommand cmd, const int timeout); +G_GNUC_INTERNAL gboolean upstart_job_is_running (const gchar *name); + +#endif /* _UPSTART_DBUS_H_ */ + diff --git a/lib/plugins/stonith/Makefile.am b/lib/plugins/stonith/Makefile.am new file mode 100644 index 0000000..01f2f4a --- /dev/null +++ b/lib/plugins/stonith/Makefile.am @@ -0,0 +1,216 @@ +# +# stonith: STONITH plugins for Linux-HA +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = external + +INCFILES = stonith_expect_helpers.h \ + stonith_plugin_common.h \ + stonith_signal.h \ + stonith_config_xml.h + +idir=$(includedir)/stonith + +i_HEADERS = $(INCFILES) + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + + +AM_CFLAGS = @NON_FATAL_CFLAGS@ + +# +# We need "vacmclient_api.h" and -lvacmclient to make the VACM +# plugin work. +# +if USE_VACM +vacm_LIB = vacm.la +else +vacm_LIB = +endif + +# +# We need , , +# , , -lsnmp and -lcrypto +# for the apcmastersnmp plugin to work +# + +if USE_APC_SNMP +apcmastersnmp_LIB = apcmastersnmp.la wti_mpc.la +else +apcmastersnmp_LIB = +endif +if IPMILAN_BUILD +OPENIPMI_LIB = -lOpenIPMI -lOpenIPMIposix -lOpenIPMIutils +libipmilan_LIB = libipmilan.la +ipmilan_LIB = ipmilan.la +ipmilan_TEST = ipmilantest +else +OPENIPMI_LIB = +libipmilan_LIB = +ipmilan_LIB = +endif +# +# We need , , +# , , +# -lcurl and -lxml2 for the drac3 plugin to work +# +if USE_DRAC3 +drac3_LIB = drac3.la +else +drac3_LIB = +endif + +# +# We need OpenHPI to make the IBM BladeCenter plugin work. +# +if USE_OPENHPI +bladehpi_LIB = bladehpi.la +else +bladehpi_LIB = +endif + +noinst_PROGRAMS = $(ipmilan_TEST) +ipmilantest_SOURCES = ipmilan_test.c +ipmilantest_LDADD = $(libipmilan_LIB) \ + $(top_builddir)/lib/pils/libpils.la \ + $(top_builddir)/lib/stonith/libstonith.la \ + $(OPENIPMI_LIB) + +## libraries + +plugindir = $(stonith_plugindir)/stonith2 + +plugin_LTLIBRARIES = apcmaster.la \ + $(apcmastersnmp_LIB) \ + apcsmart.la \ + baytech.la \ + $(bladehpi_LIB) \ + cyclades.la \ + $(drac3_LIB) \ + external.la \ + rhcs.la \ + ibmhmc.la \ + $(ipmilan_LIB) \ + meatware.la \ + null.la \ + nw_rpc100s.la \ + rcd_serial.la \ + rps10.la \ + ssh.la \ + suicide.la \ + $(vacm_LIB) \ + wti_nps.la +noinst_LTLIBRARIES = $(libipmilan_LIB) + +apcmaster_la_SOURCES = apcmaster.c $(INCFILES) +apcmaster_la_LDFLAGS = -export-dynamic -module -avoid-version +apcmaster_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +apcmastersnmp_la_SOURCES= apcmastersnmp.c $(INCFILES) +apcmastersnmp_la_LDFLAGS= -export-dynamic -module -avoid-version @SNMPLIB@ \ + @CRYPTOLIB@ +apcmastersnmp_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +apcsmart_la_SOURCES = apcsmart.c $(INCFILES) +apcsmart_la_LDFLAGS = -export-dynamic -module -avoid-version +apcsmart_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +baytech_la_SOURCES = baytech.c $(INCFILES) +baytech_la_LDFLAGS = -export-dynamic -module -avoid-version +baytech_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +bladehpi_la_SOURCES = bladehpi.c $(INCFILES) +bladehpi_la_LDFLAGS = -export-dynamic -module -avoid-version +bladehpi_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) -lopenhpi + +cyclades_la_SOURCES = cyclades.c $(INCFILES) +cyclades_la_LDFLAGS = -export-dynamic -module -avoid-version +cyclades_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +drac3_la_SOURCES = drac3.c drac3_command.c drac3_command.h drac3_hash.c drac3_hash.h $(INCFILES) +drac3_la_LDFLAGS = -export-dynamic -module -avoid-version +drac3_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la -lcurl -lxml2 $(GLIBLIB) + +external_la_SOURCES = external.c $(INCFILES) +external_la_LDFLAGS = -export-dynamic -module -avoid-version +external_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +rhcs_la_SOURCES = rhcs.c $(INCFILES) +rhcs_la_LDFLAGS = -export-dynamic -module -avoid-version +rhcs_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +ibmhmc_la_SOURCES = ibmhmc.c $(INCFILES) +ibmhmc_la_LDFLAGS = -export-dynamic -module -avoid-version +ibmhmc_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +ipmilan_la_SOURCES = ipmilan.c ipmilan.h ipmilan_command.c $(INCFILES) +ipmilan_la_LDFLAGS = -export-dynamic -module -avoid-version +ipmilan_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(OPENIPMI_LIB) $(GLIBLIB) + +libipmilan_la_SOURCES = ipmilan.c ipmilan.h ipmilan_command.c $(INCFILES) +libipmilan_la_LDFLAGS = -version-info 1:0:0 +libipmilan_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(OPENIPMI_LIB) $(GLIBLIB) + +meatware_la_SOURCES = meatware.c $(INCFILES) +meatware_la_LDFLAGS = -export-dynamic -module -avoid-version +meatware_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +null_la_SOURCES = null.c $(INCFILES) +null_la_LDFLAGS = -export-dynamic -module -avoid-version +null_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +nw_rpc100s_la_SOURCES = nw_rpc100s.c $(INCFILES) +nw_rpc100s_la_LDFLAGS = -export-dynamic -module -avoid-version +nw_rpc100s_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +rcd_serial_la_SOURCES = rcd_serial.c $(INCFILES) +rcd_serial_la_LDFLAGS = -export-dynamic -module -avoid-version +rcd_serial_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +rps10_la_SOURCES = rps10.c $(INCFILES) +rps10_la_LDFLAGS = -export-dynamic -module -avoid-version +rps10_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +ssh_la_SOURCES = ssh.c $(INCFILES) +ssh_la_LDFLAGS = -export-dynamic -module -avoid-version +ssh_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la + +vacm_la_SOURCES = vacm.c $(INCFILES) +vacm_la_LDFLAGS = -export-dynamic -module -avoid-version +vacm_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la + +wti_nps_la_SOURCES = wti_nps.c $(INCFILES) +wti_nps_la_LDFLAGS = -export-dynamic -module -avoid-version +wti_nps_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(top_builddir)/replace/libreplace.la $(GLIBLIB) + +wti_mpc_la_SOURCES= wti_mpc.c $(INCFILES) +wti_mpc_la_LDFLAGS= -export-dynamic -module -avoid-version @SNMPLIB@ \ + @CRYPTOLIB@ +wti_mpc_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la $(GLIBLIB) + +suicide_la_SOURCES = suicide.c $(INCFILES) +suicide_la_LDFLAGS = -export-dynamic -module -avoid-version +suicide_la_LIBADD = $(top_builddir)/lib/stonith/libstonith.la + +stonithscriptdir = $(stonith_plugindir)/stonith2 + +stonithscript_SCRIPTS = ribcl.py diff --git a/lib/plugins/stonith/apcmaster.c b/lib/plugins/stonith/apcmaster.c new file mode 100644 index 0000000..09a56d3 --- /dev/null +++ b/lib/plugins/stonith/apcmaster.c @@ -0,0 +1,822 @@ +/* +* +* Copyright 2001 Mission Critical Linux, Inc. +* +* All Rights Reserved. +*/ +/* + * Stonith module for APC Master Switch (AP9211) + * + * Copyright (c) 2001 Mission Critical Linux, Inc. + * author: mike ledoux + * author: Todd Wheeling + * mangled by Sun Jiang Dong, , IBM, 2005 + * + * Based strongly on original code from baytech.c by Alan Robertson. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* Observations/Notes + * + * 1. The APC MasterSwitch, unlike the BayTech network power switch, + * accepts only one (telnet) connection/session at a time. When one + * session is active, any subsequent attempt to connect to the MasterSwitch + * will result in a connection refused/closed failure. In a cluster + * environment or other environment utilizing polling/monitoring of the + * MasterSwitch (from multiple nodes), this can clearly cause problems. + * Obviously the more nodes and the shorter the polling interval, the more + * frequently such errors/collisions may occur. + * + * 2. We observed that on busy networks where there may be high occurances + * of broadcasts, the MasterSwitch became unresponsive. In some + * configurations this necessitated placing the power switch onto a + * private subnet. + */ + +#include + +#define DEVICE "APC MasterSwitch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN apcmaster +#define PIL_PLUGIN_S "apcmaster" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +#include "stonith_signal.h" + +static StonithPlugin * apcmaster_new(const char *); +static void apcmaster_destroy(StonithPlugin *); +static const char * const * apcmaster_get_confignames(StonithPlugin *); +static int apcmaster_set_config(StonithPlugin *, StonithNVpair *); +static const char * apcmaster_getinfo(StonithPlugin * s, int InfoType); +static int apcmaster_status(StonithPlugin * ); +static int apcmaster_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcmaster_hostlist(StonithPlugin *); + +static struct stonith_ops apcmasterOps ={ + apcmaster_new, /* Create new STONITH object */ + apcmaster_destroy, /* Destroy STONITH object */ + apcmaster_getinfo, /* Return STONITH info string */ + apcmaster_get_confignames, /* Get configuration parameters */ + apcmaster_set_config, /* Set configuration */ + apcmaster_status, /* Return STONITH device status */ + apcmaster_reset_req, /* Request a reset */ + apcmaster_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &apcmasterOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have an AP9211. This code has been tested with this switch. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * user; + char * passwd; +}; + +static const char * pluginid = "APCMS-Stonith"; +static const char * NOTpluginID = "APCMS device has been destroyed"; + +/* + * Different expect strings that we get from the APC MasterSwitch + */ + +#define APCMSSTR "American Power Conversion" + +static struct Etoken EscapeChar[] = { {"Escape character is '^]'.", 0, 0} + , {NULL,0,0}}; +static struct Etoken login[] = { {"User Name :", 0, 0}, {NULL,0,0}}; +static struct Etoken password[] = { {"Password :", 0, 0} ,{NULL,0,0}}; +static struct Etoken Prompt[] = { {"> ", 0, 0} ,{NULL,0,0}}; +static struct Etoken LoginOK[] = { {APCMSSTR, 0, 0} + , {"User Name :", 1, 0} ,{NULL,0,0}}; +static struct Etoken Separator[] = { {"-----", 0, 0} ,{NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Processing[] = { {"Press to continue", 0, 0} + , {"Enter 'YES' to continue", 1, 0} + , {NULL,0,0}}; + +#include "stonith_config_xml.h" + +static const char *apcmasterXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +static int MS_connect_device(struct pluginDevice * ms); +static int MSLogin(struct pluginDevice * ms); +static int MSRobustLogin(struct pluginDevice * ms); +static int MSNametoOutlet(struct pluginDevice*, const char * name); +static int MSReset(struct pluginDevice*, int outletNum, const char * host); +static int MSLogout(struct pluginDevice * ms); + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int apcmaster_onoff(struct pluginDevice*, int outletnum, const char * unitid +, int request); +#endif + +/* Login to the APC Master Switch */ + +static int +MSLogin(struct pluginDevice * ms) +{ + EXPECT(ms->rdfd, EscapeChar, 10); + + /* + * We should be looking at something like this: + * User Name : + */ + EXPECT(ms->rdfd, login, 10); + SEND(ms->wrfd, ms->user); + SEND(ms->wrfd, "\r"); + + /* Expect "Password :" */ + EXPECT(ms->rdfd, password, 10); + SEND(ms->wrfd, ms->passwd); + SEND(ms->wrfd, "\r"); + + switch (StonithLookFor(ms->rdfd, LoginOK, 30)) { + + case 0: /* Good! */ + LOG(PIL_INFO, "Successful login to %s.", ms->idinfo); + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", ms->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + + return(S_OK); +} + +/* Attempt to login up to 20 times... */ + +static int +MSRobustLogin(struct pluginDevice * ms) +{ + int rc = S_OOPS; + int j = 0; + + for ( ; ; ) { + if (MS_connect_device(ms) == S_OK) { + rc = MSLogin(ms); + if( rc == S_OK ) { + break; + } + } + if ((++j) == 20) { + break; + } else { + sleep(1); + } + } + + return rc; +} + +/* Log out of the APC Master Switch */ + +static +int MSLogout(struct pluginDevice* ms) +{ + int rc; + + /* Make sure we're in the right menu... */ + /*SEND(ms->wrfd, "\033\033\033\033\033\033\033"); */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect "> " */ + rc = StonithLookFor(ms->rdfd, Prompt, 5); + + /* "4" is logout */ + SEND(ms->wrfd, "4\r"); + + close(ms->wrfd); + close(ms->rdfd); + ms->wrfd = ms->rdfd = -1; + + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} +/* Reset (power-cycle) the given outlets */ +static int +MSReset(struct pluginDevice* ms, int outletNum, const char *host) +{ + char unum[32]; + + /* Make sure we're in the top level menu */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect ">" */ + EXPECT(ms->rdfd, Prompt, 5); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Select requested outlet */ + EXPECT(ms->rdfd, Prompt, 5); + snprintf(unum, sizeof(unum), "%i\r", outletNum); + SEND(ms->wrfd, unum); + + /* Select menu 1 (Control Outlet) */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "1\r"); + + /* Select menu 3 (Immediate Reboot) */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "3\r"); + + /* Expect "Press " or "Enter 'YES'" (if confirmation turned on) */ + retry: + switch (StonithLookFor(ms->rdfd, Processing, 5)) { + case 0: /* Got "Press " Do so */ + SEND(ms->wrfd, "\r"); + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(ms->wrfd, "YES\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + + LOG(PIL_INFO, "Host being rebooted: %s", host); + + /* Expect ">" */ + if (StonithLookFor(ms->rdfd, Prompt, 10) < 0) { + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + /* All Right! Power is back on. Life is Good! */ + + LOG(PIL_INFO, "Power restored to host: %s", host); + + /* Return to top level menu */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + return(S_OK); +} + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int +apcmaster_onoff(struct pluginDevice* ms, int outletNum, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "1\r" : "2\r"); + int rc; + + if ((rc = MSRobustLogin(ms) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(rc); + } + + /* Make sure we're in the top level menu */ + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect ">" */ + EXPECT(ms->rdfd, Prompt, 5); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Select requested outlet */ + snprintf(unum, sizeof(unum), "%d\r", outletNum); + SEND(ms->wrfd, unum); + + /* Select menu 1 (Control Outlet) */ + SEND(ms->wrfd, "1\r"); + + /* Send ON/OFF command for given outlet */ + SEND(ms->wrfd, onoff); + + /* Expect "Press " or "Enter 'YES'" (if confirmation turned on) */ + retry: + switch (StonithLookFor(ms->rdfd, Processing, 5)) { + case 0: /* Got "Press " Do so */ + SEND(ms->wrfd, "\r"); + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(ms->wrfd, "YES\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + EXPECT(ms->rdfd, Prompt, 10); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to MS outlet(s) %d turned %s", outletNum, onoff); + /* Pop back to main menu */ + SEND(ms->wrfd, "\033\033\033\033\033\033\033\r"); + return(S_OK); +} +#endif /* defined(ST_POWERON) && defined(ST_POWEROFF) */ + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +MSNametoOutlet(struct pluginDevice* ms, const char * name) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + int times = 0; + int ret = -1; + + /* Verify that we're in the top-level menu */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + + /* Expect ">" */ + EXPECT(ms->rdfd, Prompt, 5); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Expect: "-----" so we can skip over it... */ + EXPECT(ms->rdfd, Separator, 5); + EXPECT(ms->rdfd, CRNL, 5); + EXPECT(ms->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + times++; + NameMapping[0] = EOS; + SNARF(ms->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d- %23c",&sockno, sockname) == 2) { + + char * last = sockname+23; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strcasecmp(name, sockname) == 0) { + ret = sockno; + } + } + } while (strlen(NameMapping) > 2 && times < 8); + + /* Pop back out to the top level menu */ + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + EXPECT(ms->rdfd, Prompt, 5); + SEND(ms->wrfd, "\033"); + return(ret); +} + +static int +apcmaster_status(StonithPlugin *s) +{ + struct pluginDevice* ms; + int rc; + + ERRIFNOTCONFIGED(s,S_OOPS); + + ms = (struct pluginDevice*) s; + + if ((rc = MSRobustLogin(ms) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(rc); + } + + /* Expect ">" */ + SEND(ms->wrfd, "\033\r"); + EXPECT(ms->rdfd, Prompt, 5); + + return(MSLogout(ms)); +} + +/* + * Return the list of hosts (outlet names) for the devices on this MS unit + */ + +static char ** +apcmaster_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* ms; + unsigned int i; + + ERRIFNOTCONFIGED(s,NULL); + + ms = (struct pluginDevice*) s; + + if (MSRobustLogin(ms) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(NULL); + } + + /* Expect ">" */ + NULLEXPECT(ms->rdfd, Prompt, 10); + + /* Request menu 1 (Device Control) */ + SEND(ms->wrfd, "1\r"); + + /* Expect: "-----" so we can skip over it... */ + NULLEXPECT(ms->rdfd, Separator, 5); + NULLEXPECT(ms->rdfd, CRNL, 5); + NULLEXPECT(ms->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + do { + int sockno; + char sockname[64]; + NameMapping[0] = EOS; + NULLSNARF(ms->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d- %23c",&sockno, sockname) == 2) { + + char * last = sockname+23; + char * nm; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (numnames >= DIMOF(NameList)-1) { + break; + } + if ((nm = (char*)STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } + } while (strlen(NameMapping) > 2); + + /* Pop back out to the top level menu */ + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + SEND(ms->wrfd, "\033"); + NULLEXPECT(ms->rdfd, Prompt, 10); + + + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)MSLogout(ms); + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; iOpenStreamSocket(ms->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + ms->rdfd = ms->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this StonithPlugin device. + */ +static int +apcmaster_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + int lorc = 0; + struct pluginDevice* ms; + + ERRIFNOTCONFIGED(s,S_OOPS); + + ms = (struct pluginDevice*) s; + + if ((rc = MSRobustLogin(ms)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo); + return(rc); + }else{ + int noutlet; + noutlet = MSNametoOutlet(ms, host); + if (noutlet < 1) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , ms->device, host); + return(S_BADHOST); + } + switch(request) { + +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + rc = apcmaster_onoff(ms, noutlet, host, request); + break; + case ST_POWEROFF: + rc = apcmaster_onoff(ms, noutlet, host, request); + break; +#endif + case ST_GENERIC_RESET: + rc = MSReset(ms, noutlet, host); + break; + default: + rc = S_INVAL; + break; + } + } + + lorc = MSLogout(ms); + return(rc != S_OK ? rc : lorc); +} + +/* + * Get the configuration parameters names + */ +static const char * const * +apcmaster_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +/* + * Set the configuration parameters + */ +static int +apcmaster_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->device = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->passwd = namestocopy[2].s_value; + + return(S_OK); +} + +static const char * +apcmaster_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ms; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ms = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ms->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = ms->device; + break; + + case ST_DEVICEDESCR: + ret = "APC MasterSwitch (via telnet)\n" + "NOTE: The APC MasterSwitch accepts only one (telnet)\n" + "connection/session a time. When one session is active,\n" + "subsequent attempts to connect to the MasterSwitch" + " will fail."; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmasterXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * APC MasterSwitch StonithPlugin destructor... + */ +static void +apcmaster_destroy(StonithPlugin *s) +{ + struct pluginDevice* ms; + + VOIDERRIFWRONGDEV(s); + + ms = (struct pluginDevice *)s; + + ms->pluginid = NOTpluginID; + if (ms->rdfd >= 0) { + close(ms->rdfd); + ms->rdfd = -1; + } + if (ms->wrfd >= 0) { + close(ms->wrfd); + ms->wrfd = -1; + } + if (ms->device != NULL) { + FREE(ms->device); + ms->device = NULL; + } + if (ms->user != NULL) { + FREE(ms->user); + ms->user = NULL; + } + if (ms->passwd != NULL) { + FREE(ms->passwd); + ms->passwd = NULL; + } + FREE(ms); +} + +/* Create a new APC Master Switch StonithPlugin device. */ + +static StonithPlugin * +apcmaster_new(const char *subplugin) +{ + struct pluginDevice* ms = ST_MALLOCT(struct pluginDevice); + + if (ms == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ms, 0, sizeof(*ms)); + ms->pluginid = pluginid; + ms->pid = -1; + ms->rdfd = -1; + ms->wrfd = -1; + ms->user = NULL; + ms->device = NULL; + ms->passwd = NULL; + ms->idinfo = DEVICE; + ms->sp.s_ops = &apcmasterOps; + + return(&(ms->sp)); +} diff --git a/lib/plugins/stonith/apcmastersnmp.c b/lib/plugins/stonith/apcmastersnmp.c new file mode 100644 index 0000000..a9eeaeb --- /dev/null +++ b/lib/plugins/stonith/apcmastersnmp.c @@ -0,0 +1,890 @@ +/* + * Stonith module for APC Masterswitch (SNMP) + * Copyright (c) 2001 Andreas Piesk + * Mangled by Sun Jiang Dong , IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version.* + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +/* device ID */ +#define DEVICE "APC MasterSwitch (SNMP)" + +#include "stonith_plugin_common.h" +#undef FREE /* defined by snmp stuff */ + +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif + +#ifdef HAVE_NET_SNMP_NET_SNMP_CONFIG_H +# include +# include +# include +# define INIT_AGENT() init_master_agent() +#else +# include +# include +# include +# ifndef NETSNMP_DS_APPLICATION_ID +# define NETSNMP_DS_APPLICATION_ID DS_APPLICATION_ID +# endif +# ifndef NETSNMP_DS_AGENT_ROLE +# define NETSNMP_DS_AGENT_ROLE DS_AGENT_ROLE +# endif +# define netsnmp_ds_set_boolean ds_set_boolean +# define INIT_AGENT() init_master_agent(161, NULL, NULL) +#endif + +#define PIL_PLUGIN apcmastersnmp +#define PIL_PLUGIN_S "apcmastersnmp" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +#define DEBUGCALL \ + if (Debug) { \ + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); \ + } + +static StonithPlugin * apcmastersnmp_new(const char *); +static void apcmastersnmp_destroy(StonithPlugin *); +static const char * const * apcmastersnmp_get_confignames(StonithPlugin *); +static int apcmastersnmp_set_config(StonithPlugin *, StonithNVpair *); +static const char * apcmastersnmp_getinfo(StonithPlugin * s, int InfoType); +static int apcmastersnmp_status(StonithPlugin * ); +static int apcmastersnmp_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcmastersnmp_hostlist(StonithPlugin *); + +static struct stonith_ops apcmastersnmpOps ={ + apcmastersnmp_new, /* Create new STONITH object */ + apcmastersnmp_destroy, /* Destroy STONITH object */ + apcmastersnmp_getinfo, /* Return STONITH info string */ + apcmastersnmp_get_confignames, /* Get configuration parameters */ + apcmastersnmp_set_config, /* Set configuration */ + apcmastersnmp_status, /* Return STONITH device status */ + apcmastersnmp_reset_req, /* Request a reset */ + apcmastersnmp_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + DEBUGCALL; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &apcmastersnmpOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * APCMaster tested with APC Masterswitch 9212 + */ + +/* outlet commands / status codes */ +#define OUTLET_ON 1 +#define OUTLET_OFF 2 +#define OUTLET_REBOOT 3 +#define OUTLET_NO_CMD_PEND 2 + +/* oids */ +#define OID_IDENT ".1.3.6.1.4.1.318.1.1.12.1.5.0" +#define OID_NUM_OUTLETS ".1.3.6.1.4.1.318.1.1.12.1.8.0" +#define OID_OUTLET_NAMES ".1.3.6.1.4.1.318.1.1.12.3.4.1.1.2.%i" +#define OID_OUTLET_STATE ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.%i" +#define OID_OUTLET_COMMAND_PENDING ".1.3.6.1.4.1.318.1.1.12.3.5.1.1.5.%i" +#define OID_OUTLET_REBOOT_DURATION ".1.3.6.1.4.1.318.1.1.12.3.4.1.1.6.%i" + +/* + snmpset -c private -v1 172.16.0.32:161 + ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.1" i 1 + The last octet in the OID is the plug number. The value can + be 1 thru 8 because there are 8 power plugs on this device. + The integer that can be set is as follows: 1=on, 2=off, and + 3=reset +*/ + +/* own defines */ +#define MAX_STRING 128 +#define ST_PORT "port" + +/* structur of stonith object */ +struct pluginDevice { + StonithPlugin sp; /* StonithPlugin object */ + const char* pluginid; /* id of object */ + const char* idinfo; /* type of device */ + struct snmp_session* sptr; /* != NULL->session created */ + char * hostname; /* masterswitch's hostname */ + /* or ip addr */ + int port; /* snmp port */ + char * community; /* snmp community (r/w) */ + int num_outlets; /* number of outlets */ +}; + +/* for checking hardware (issue a warning if mismatch) */ +static const char* APC_tested_ident[] = {"AP9606","AP7920","AP7921","AP7900","AP_other_well_tested"}; + +/* constant strings */ +static const char *pluginid = "APCMS-SNMP-Stonith"; +static const char *NOTpluginID = "APCMS SNMP device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number on which the SNMP server is running on the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +static const char *apcmastersnmpXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PORT_PARM + XML_COMMUNITY_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +static void APC_error(struct snmp_session *sptr, const char *fn +, const char *msg); +static struct snmp_session *APC_open(char *hostname, int port +, char *community); +static void *APC_read(struct snmp_session *sptr, const char *objname +, int type); +static int APC_write(struct snmp_session *sptr, const char *objname +, char type, char *value); + +static void +APC_error(struct snmp_session *sptr, const char *fn, const char *msg) +{ + int snmperr = 0; + int cliberr = 0; + char *errstr; + + snmp_error(sptr, &cliberr, &snmperr, &errstr); + LOG(PIL_CRIT + , "%s: %s (cliberr: %i / snmperr: %i / error: %s)." + , fn, msg, cliberr, snmperr, errstr); + free(errstr); +} + + +/* + * creates a snmp session + */ +static struct snmp_session * +APC_open(char *hostname, int port, char *community) +{ + static struct snmp_session session; + struct snmp_session *sptr; + + DEBUGCALL; + + /* create session */ + snmp_sess_init(&session); + + /* fill session */ + session.peername = hostname; + session.version = SNMP_VERSION_1; + session.remote_port = port; + session.community = (u_char *)community; + session.community_len = strlen(community); + session.retries = 5; + session.timeout = 1000000; + + /* open session */ + sptr = snmp_open(&session); + + if (sptr == NULL) { + APC_error(&session, __FUNCTION__, "cannot open snmp session"); + } + + /* return pointer to opened session */ + return (sptr); +} + +/* + * parse config + */ + +/* + * read value of given oid and return it as string + */ +static void * +APC_read(struct snmp_session *sptr, const char *objname, int type) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct variable_list *vars; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + static char response_str[MAX_STRING]; + static int response_int; + + DEBUGCALL; + + /* convert objname into oid; return NULL if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (NULL); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) != NULL) { + + /* get-request have no values */ + snmp_add_null_var(pdu, name, namelen); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == SNMPERR_SUCCESS) { + + /* request succeed, got valid response ? */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* go through the returned vars */ + for (vars = resp->variables; vars; + vars = vars->next_variable) { + + /* return response as string */ + if ((vars->type == type) && (type == ASN_OCTET_STR)) { + memset(response_str, 0, MAX_STRING); + strncpy(response_str, (char *)vars->val.string, + MIN(vars->val_len, MAX_STRING)); + snmp_free_pdu(resp); + return ((void *) response_str); + } + /* return response as integer */ + if ((vars->type == type) && (type == ASN_INTEGER)) { + response_int = *vars->val.integer; + snmp_free_pdu(resp); + return ((void *) &response_int); + } + } + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + APC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free repsonse pdu (necessary?) */ + snmp_free_pdu(resp); + }else{ + APC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error: return nothing */ + return (NULL); +} + +/* + * write value of given oid + */ +static int +APC_write(struct snmp_session *sptr, const char *objname, char type, + char *value) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + + DEBUGCALL; + + /* convert objname into oid; return FALSE if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (FALSE); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_SET)) != NULL) { + + /* add to be written value to pdu */ + snmp_add_var(pdu, name, namelen, type, value); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == STAT_SUCCESS) { + + /* go through the returned vars */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* request successful done */ + snmp_free_pdu(resp); + return (TRUE); + + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + APC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free pdu (again: necessary?) */ + snmp_free_pdu(resp); + }else{ + APC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error */ + return (FALSE); +} + +/* + * return the status for this device + */ + +static int +apcmastersnmp_status(StonithPlugin * s) +{ + struct pluginDevice *ad; + char *ident; + int i; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + if ((ident = APC_read(ad->sptr, OID_IDENT, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read ident.", __FUNCTION__); + return (S_ACCESS); + } + + /* issue a warning if ident mismatches */ + for(i=DIMOF(APC_tested_ident) -1; i >=0 ; i--) { + if (strcmp(ident, APC_tested_ident[i]) == 0) { + break; + } + } + + if (i<0) { + LOG(PIL_WARN + , "%s: module not tested with this hardware '%s'." + , __FUNCTION__, ident); + } + /* status ok */ + return (S_OK); +} + +/* + * return the list of hosts configured for this device + */ + +static char ** +apcmastersnmp_hostlist(StonithPlugin * s) +{ + char **hl; + struct pluginDevice *ad; + int j, h, num_outlets; + char *outlet_name; + char objname[MAX_STRING]; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, NULL); + + ad = (struct pluginDevice *) s; + + /* allocate memory for array of up to NUM_OUTLETS strings */ + if ((hl = (char **)MALLOC((ad->num_outlets+1) * sizeof(char *))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + /* clear hostlist array */ + memset(hl, 0, (ad->num_outlets + 1) * sizeof(char *)); + num_outlets = 0; + + /* read NUM_OUTLETS values and put them into hostlist array */ + for (j = 0; j < ad->num_outlets; ++j) { + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_NAMES, j + 1); + + /* read outlet name */ + if ((outlet_name = APC_read(ad->sptr, objname, ASN_OCTET_STR)) == + NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, j+1); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + + /* Check whether the host is already listed */ + for (h = 0; h < num_outlets; ++h) { + if (strcasecmp(hl[h],outlet_name) == 0) + break; + } + + if (h >= num_outlets) { + /* put outletname in hostlist */ + if (Debug) { + LOG(PIL_DEBUG, "%s: added %s to hostlist." + , __FUNCTION__, outlet_name); + } + + if ((hl[num_outlets] = STRDUP(outlet_name)) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + strdown(hl[num_outlets]); + num_outlets++; + } + } + + + if (Debug) { + LOG(PIL_DEBUG, "%s: %d unique hosts connected to %d outlets." + , __FUNCTION__, num_outlets, j); + } + /* return list */ + return (hl); +} + +/* + * reset the host + */ + +static int +apcmastersnmp_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *ad; + char objname[MAX_STRING]; + char value[MAX_STRING]; + char *outlet_name; + int req_oid = OUTLET_REBOOT; + int expect_state = OUTLET_ON; + int i, h, num_outlets, outlet, reboot_duration, *state, bad_outlets; + int outlets[8]; /* Assume that one node is connected to a + maximum of 8 outlets */ + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + num_outlets = 0; + reboot_duration = 0; + bad_outlets = 0; + + /* read max. as->num_outlets values */ + for (outlet = 1; outlet <= ad->num_outlets; outlet++) { + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_NAMES, outlet); + + /* read outlet name */ + if ((outlet_name = APC_read(ad->sptr, objname, ASN_OCTET_STR)) + == NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (Debug) { + LOG(PIL_DEBUG, "%s: found outlet: %s.", __FUNCTION__, outlet_name); + } + + /* found one */ + if (strcasecmp(outlet_name, host) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: found %s at outlet %d." + , __FUNCTION__, host, outlet); + } + /* Check that the outlet is not administratively down */ + + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + + /* get outlet's state */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) == NULL) { + LOG(PIL_CRIT + , "%s: cannot read state for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + /* prepare oid */ + snprintf(objname, MAX_STRING, OID_OUTLET_REBOOT_DURATION + , outlet); + + /* read reboot duration of the port */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read reboot duration for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (num_outlets == 0) { + /* save the inital value of the first port */ + reboot_duration = *state; + } else if (reboot_duration != *state) { + LOG(PIL_WARN, "%s: outlet %d has a different reboot duration!" + , __FUNCTION__, outlet); + if (reboot_duration < *state) + reboot_duration = *state; + } + + /* Ok, add it to the list of outlets to control */ + outlets[num_outlets]=outlet; + num_outlets++; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: outlet: %i.", __FUNCTION__, outlet); + } + + /* host not found in outlet names */ + if (num_outlets < 1) { + LOG(PIL_CRIT, "%s: no active outlet for '%s'.", __FUNCTION__, host); + return (S_BADHOST); + } + + + /* choose the OID for the stonith request */ + switch (request) { + case ST_POWERON: + req_oid = OUTLET_ON; + expect_state = OUTLET_ON; + break; + case ST_POWEROFF: + req_oid = OUTLET_OFF; + expect_state = OUTLET_OFF; + break; + case ST_GENERIC_RESET: + req_oid = OUTLET_REBOOT; + expect_state = OUTLET_ON; + break; + default: break; + } + + /* Turn them all off */ + + for (outlet=outlets[0], i=0 ; i < num_outlets; i++, outlet = outlets[i]) { + /* prepare objname */ + snprintf(objname, MAX_STRING, OID_OUTLET_COMMAND_PENDING, outlet); + + /* are there pending commands ? */ + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read pending commands for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + if (*state != OUTLET_NO_CMD_PEND) { + LOG(PIL_CRIT, "%s: command pending.", __FUNCTION__); + return (S_RESETFAIL); + } + + /* prepare objnames */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + snprintf(value, MAX_STRING, "%i", req_oid); + + /* send reboot cmd */ + if (!APC_write(ad->sptr, objname, 'i', value)) { + LOG(PIL_CRIT + , "%s: cannot send reboot command for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + } + + /* wait max. 2*reboot_duration for all outlets to go back on */ + for (i = 0; i < reboot_duration << 1; i++) { + + sleep(1); + + bad_outlets = 0; + for (outlet=outlets[0], h=0 ; h < num_outlets; h++, + outlet = outlets[h]) { + + /* prepare objname of the first outlet */ + snprintf(objname, MAX_STRING, OID_OUTLET_STATE, outlet); + /* get outlet's state */ + + if ((state = APC_read(ad->sptr, objname, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read state for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + + if (*state != expect_state) + bad_outlets++; + } + + if (bad_outlets == 0) + return (S_OK); + } + + if (bad_outlets == num_outlets) { + /* reset failed */ + LOG(PIL_CRIT, "%s: stonith operation for '%s' failed." + , __FUNCTION__, host); + return (S_RESETFAIL); + } else { + /* Not all outlets back on, but at least one; implies node was */ + /* rebooted correctly */ + LOG(PIL_WARN,"%s: Not all outlets in the expected state!" + , __FUNCTION__); + return (S_OK); + } +} + +/* + * Get the configuration parameter names. + */ + +static const char * const * +apcmastersnmp_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_PORT, ST_COMMUNITY, NULL}; + return ret; +} + +/* + * Set the configuration parameters. + */ + +static int +apcmastersnmp_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + int * i; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_COMMUNITY, NULL} + , {NULL, NULL} + }; + + DEBUGCALL; + ERRIFWRONGDEV(s,S_INVAL); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->hostname = namestocopy[0].s_value; + sd->port = atoi(namestocopy[1].s_value); + PluginImports->mfree(namestocopy[1].s_value); + sd->community = namestocopy[2].s_value; + + /* try to resolve the hostname/ip-address */ + if (gethostbyname(sd->hostname) != NULL) { + /* init snmp library */ + init_snmp("apcmastersnmp"); + + /* now try to get a snmp session */ + if ((sd->sptr = APC_open(sd->hostname, sd->port, sd->community)) != NULL) { + + /* ok, get the number of outlets from the masterswitch */ + if ((i = APC_read(sd->sptr, OID_NUM_OUTLETS, ASN_INTEGER)) + == NULL) { + LOG(PIL_CRIT + , "%s: cannot read number of outlets." + , __FUNCTION__); + return (S_ACCESS); + } + /* store the number of outlets */ + sd->num_outlets = *i; + if (Debug) { + LOG(PIL_DEBUG, "%s: number of outlets: %i." + , __FUNCTION__, sd->num_outlets ); + } + + /* Everything went well */ + return (S_OK); + }else{ + LOG(PIL_CRIT, "%s: cannot create snmp session." + , __FUNCTION__); + } + }else{ + LOG(PIL_CRIT, "%s: cannot resolve hostname '%s', h_errno %d." + , __FUNCTION__, sd->hostname, h_errno); + } + + /* not a valid config */ + return (S_BADCONFIG); +} + +/* + * get info about the stonith device + */ + +static const char * +apcmastersnmp_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad; + const char *ret = NULL; + + DEBUGCALL; + + ERRIFWRONGDEV(s, NULL); + + ad = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->hostname; + break; + + case ST_DEVICEDESCR: + ret = "APC MasterSwitch (via SNMP)\n" + "The APC MasterSwitch can accept multiple simultaneous SNMP clients"; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmastersnmpXML; + break; + + } + return ret; +} + + +/* + * APC StonithPlugin destructor... + */ + +static void +apcmastersnmp_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad; + + DEBUGCALL; + + VOIDERRIFWRONGDEV(s); + + ad = (struct pluginDevice *) s; + + ad->pluginid = NOTpluginID; + + /* release snmp session */ + if (ad->sptr != NULL) { + snmp_close(ad->sptr); + ad->sptr = NULL; + } + + /* reset defaults */ + if (ad->hostname != NULL) { + PluginImports->mfree(ad->hostname); + ad->hostname = NULL; + } + if (ad->community != NULL) { + PluginImports->mfree(ad->community); + ad->community = NULL; + } + ad->num_outlets = 0; + + PluginImports->mfree(ad); +} + +/* + * Create a new APC StonithPlugin device. Too bad this function can't be + * static + */ + +static StonithPlugin * +apcmastersnmp_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + DEBUGCALL; + + /* no memory for stonith-object */ + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + /* clear stonith-object */ + memset(ad, 0, sizeof(*ad)); + + /* set defaults */ + ad->pluginid = pluginid; + ad->sptr = NULL; + ad->hostname = NULL; + ad->community = NULL; + ad->idinfo = DEVICE; + ad->sp.s_ops = &apcmastersnmpOps; + + /* return the object */ + return (&(ad->sp)); +} diff --git a/lib/plugins/stonith/apcmastersnmp.cfg.example b/lib/plugins/stonith/apcmastersnmp.cfg.example new file mode 100644 index 0000000..76fea08 --- /dev/null +++ b/lib/plugins/stonith/apcmastersnmp.cfg.example @@ -0,0 +1,39 @@ +# +# this is an example config for the stonith module apcmastersnmp +# +# 1. what does the fields on the line mean ? +# +# all parameters must be given on a single line. blank lines and lines +# starting with '#' are ignored. only the first not ignored line will +# be processed. all subsequent lines will be ignored. the different +# fields must be seperated by white-spaces (blanks and/or tabs). +# +# the first field is the either the hostname or the ip address. the +# hostname must be resolvable. the second fields specifies the snmp port +# the masterswitch is listening. for snmp the default is 161. the last +# field contains the so called 'community' string. this must be the same +# as the one in the masterswitch configuration. +# +# +# 2. how must the masterswitch be configured ? +# +# as said above, the community string must be set to the same value entered +# in this config. the different outlets must be named after the connected +# hosts. that means, the outlet names must be the same as the node names +# in /etc/ha.d/ha.cf. the reset values should be set to reasonable values. +# +# the module DON'T configure the module in any way! +# +# +# 3. how does the module work ? +# +# in case of a stonith the module receives the nodename of the host, which +# should be reset. the module looks up this nodename in the list of outlet +# names. that's why the names must be identical (see 2.). if it finds the +# name, it'll reset the appropriate outlet using the configured values +# (eg. delay, duration). then the module waits for the outlet to coming +# up. if it comes up, a successful stonith will be reported back. otherwise +# the stonith failed and a failure code will be returned. +# + +192.168.1.110 161 private diff --git a/lib/plugins/stonith/apcsmart.c b/lib/plugins/stonith/apcsmart.c new file mode 100644 index 0000000..18d1612 --- /dev/null +++ b/lib/plugins/stonith/apcsmart.c @@ -0,0 +1,1028 @@ +/* + * Stonith module for APCSmart Stonith device + * Copyright (c) 2000 Andreas Piesk + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version.* + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Original version of this UPS code was taken from: + * 'Network UPS Tools' by Russell Kroll + * homepage: http://www.networkupstools.org/ + * + * Significantly mangled by Alan Robertson + */ + +#include + +#define DEVICE "APCSmart" + +#include "stonith_plugin_common.h" + +/* + * APCSmart (tested with old 900XLI, APC SmartUPS 700 and SmartUPS-1000) + * + * The reset is a combined reset: "S" and "@000" + * The "S" command tells the ups that if it is on-battery, it should + * remain offline until the power is back. + * If that command is not accepted, the "@000" command will be sent + * to tell the ups to turn off and back on right away. + * In both cases, if the UPS supports a 20 second shutdown grace + * period (such as on the 900XLI), the shutdown will delay that long, + * otherwise the shutdown will happen immediately (the code searches + * for the smallest possible delay). + */ + +#define CFG_FILE "/etc/ha.d/apcsmart.cfg" + +#define MAX_DEVICES 1 + +#define SERIAL_TIMEOUT 3 /* timeout in sec */ +#define SEND_DELAY 50000 /* in microseconds */ +#define ENDCHAR 10 /* use LF */ +#define MAX_STRING 512 +#define MAX_DELAY_STRING 16 +#define SWITCH_TO_NEXT_VAL "-" /* APC cmd for cycling through + * the values + */ + +#define CMD_SMART_MODE "Y" +#define RSP_SMART_MODE "SM" +#define CMD_GET_STATUS "Q" +#define RSP_GET_STATUS NULL +#define CMD_RESET "S" /* turn off & stay off if on battery */ +#define CMD_RESET2 "@000" /* turn off & immediately turn on */ +#define RSP_RESET "*" /* RESET response from older models */ +#define RSP_RESET2 "OK" /* RESET response from newer models */ +#define RSP_NA "NA" +#define CMD_READREG1 "~" +#define CMD_OFF "Z" +#define CMD_ON "\016" /* (control-n) */ +#define CMD_SHUTDOWN_DELAY "p" +#define CMD_WAKEUP_DELAY "r" + +#define CR 13 + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; /* of object */ + const char * idinfo; /* type of device */ + char ** hostlist; /* served by the device (only 1) */ + int hostcount;/* of hosts (1) */ + char * upsdev; /* */ + int upsfd; /* for serial port */ + int retries; + char shutdown_delay[MAX_DELAY_STRING]; + char old_shutdown_delay[MAX_DELAY_STRING]; + char wakeup_delay[MAX_DELAY_STRING]; + char old_wakeup_delay[MAX_DELAY_STRING]; +}; + +/* saving old settings */ +/* FIXME! These should be part of pluginDevice struct above */ +static struct termios old_tio; + +static int f_serialtimeout; /* flag for timeout */ +static const char *pluginid = "APCSmart-Stonith"; +static const char *NOTpluginID = "APCSmart device has been destroyed"; + +/* + * stonith prototypes + */ + +#define PIL_PLUGIN apcsmart +#define PIL_PLUGIN_S "apcsmart" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +#include "stonith_signal.h" + +static StonithPlugin * apcsmart_new(const char *); +static void apcsmart_destroy(StonithPlugin *); +static const char * const * apcsmart_get_confignames(StonithPlugin*); +static int apcsmart_set_config(StonithPlugin *, StonithNVpair*); +static const char * apcsmart_get_info(StonithPlugin * s, int InfoType); +static int apcsmart_status(StonithPlugin * ); +static int apcsmart_reset_req(StonithPlugin * s, int request, const char * host); +static char ** apcsmart_hostlist(StonithPlugin *); + +static struct stonith_ops apcsmartOps ={ + apcsmart_new, /* Create new STONITH object */ + apcsmart_destroy, /* Destroy STONITH object */ + apcsmart_get_info, /* Return STONITH info string */ + apcsmart_get_confignames, /* Return STONITH info string */ + apcsmart_set_config, /* Get configuration from NVpairs */ + apcsmart_status, /* Return STONITH device status */ + apcsmart_reset_req, /* Request a reset */ + apcsmart_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &apcsmartOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#include "stonith_config_xml.h" + +static const char *apcsmartXML = + XML_PARAMETERS_BEGIN + XML_TTYDEV_PARM + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +int APC_open_serialport(const char *port, speed_t speed); +void APC_close_serialport(const char *port, int upsfd); +void APC_sh_serial_timeout(int sig); +int APC_send_cmd(int upsfd, const char *cmd); +int APC_recv_rsp(int upsfd, char *rsp); +int APC_enter_smartmode(int upsfd); +int APC_set_ups_var(int upsfd, const char *cmd, char *newval); +int APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay); +int APC_init( struct pluginDevice *ad ); +void APC_deinit( struct pluginDevice *ad ); + +/* + * Signal handler for serial port timeouts + */ + +void +APC_sh_serial_timeout(int sig) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + STONITH_IGNORE_SIG(SIGALRM); + + if (Debug) { + LOG(PIL_DEBUG, "%s: serial port timed out.", __FUNCTION__); + } + + f_serialtimeout = TRUE; + + return; +} + +/* + * Open serial port and set it to b2400 + */ + +int +APC_open_serialport(const char *port, speed_t speed) +{ + struct termios tio; + int fd; + int rc; + int errno_save; + int fflags; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if ((rc = OurImports->TtyLock(port)) < 0) { + LOG(PIL_CRIT, "%s: Could not lock tty %s [rc=%d]." + , __FUNCTION__, port, rc); + return -1; + } + + STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout); + alarm(SERIAL_TIMEOUT); + f_serialtimeout = FALSE; + + fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK | O_EXCL); + errno_save = errno; + + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + + if (fd < 0) { + LOG(PIL_CRIT, "%s: Open of %s %s [%s].", __FUNCTION__ + , port + , f_serialtimeout ? "timed out" : "failed" + , strerror(errno_save)); + OurImports->TtyUnlock(port); + return -1; + } + + if ((fflags = fcntl(fd, F_GETFL)) < 0 + || fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) { + LOG(PIL_CRIT, "%s: Setting flags on %s failed [%s]." + , __FUNCTION__ + , port + , strerror(errno_save)); + close(fd); + OurImports->TtyUnlock(port); + return -1; + } + + if (tcgetattr(fd, &old_tio) < 0) { + LOG(PIL_CRIT, "%s: tcgetattr of %s failed [%s].", __FUNCTION__ + , port + , strerror(errno)); + close(fd); + OurImports->TtyUnlock(port); + return -1; + } + + memcpy(&tio, &old_tio, sizeof(struct termios)); + tio.c_cflag = CS8 | CLOCAL | CREAD; + tio.c_iflag = IGNPAR; + tio.c_oflag = 0; + tio.c_lflag = 0; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + + cfsetispeed(&tio, speed); + cfsetospeed(&tio, speed); + + tcflush(fd, TCIOFLUSH); + tcsetattr(fd, TCSANOW, &tio); + + return (fd); +} + +/* + * Close serial port and restore old settings + */ + +void +APC_close_serialport(const char *port, int upsfd) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + if (upsfd < 0) { + return; + } + + tcflush(upsfd, TCIFLUSH); + tcsetattr(upsfd, TCSANOW, &old_tio); + close(upsfd); + if (port != NULL) { + OurImports->TtyUnlock(port); + } +} + +/* + * Send a command to the ups + */ + +int +APC_send_cmd(int upsfd, const char *cmd) +{ + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s(\"%s\")", __FUNCTION__, cmd); + } + + tcflush(upsfd, TCIFLUSH); + for (i = strlen(cmd); i > 0; i--) { + if (write(upsfd, cmd++, 1) != 1) { + return (S_ACCESS); + } + + usleep(SEND_DELAY); + } + return (S_OK); +} + +/* + * Get the response from the ups + */ + +int +APC_recv_rsp(int upsfd, char *rsp) +{ + char *p = rsp; + char inp; + int num = 0; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + *p = '\0'; + + STONITH_SIGNAL(SIGALRM, APC_sh_serial_timeout); + + alarm(SERIAL_TIMEOUT); + + while (num < MAX_STRING) { + + if (read(upsfd, &inp, 1) == 1) { + + /* shutdown sends only a '*' without LF */ + if ((inp == '*') && (num == 0)) { + *p++ = inp; + num++; + inp = ENDCHAR; + } + + if (inp == ENDCHAR) { + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + + *p = '\0'; + if (Debug) { + LOG(PIL_DEBUG, "return(\"%s\")/*%s*/;" + , rsp, __FUNCTION__); + } + return (S_OK); + } + + if (inp != CR) { + *p++ = inp; + num++; + } + }else{ + alarm(0); + STONITH_IGNORE_SIG(SIGALRM); + *p = '\0'; + LOG(PIL_DEBUG, "%s: %s.", __FUNCTION__, + f_serialtimeout ? "timeout" : + "can't access device" ); + return (f_serialtimeout ? S_TIMEOUT : S_ACCESS); + } + } + return (S_ACCESS); +} + +/* + * Enter smart mode + */ + +int +APC_enter_smartmode(int upsfd) +{ + int rc; + char resp[MAX_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + strcpy(resp, RSP_SMART_MODE); + + if (((rc = APC_send_cmd(upsfd, CMD_SMART_MODE)) == S_OK) + && ((rc = APC_recv_rsp(upsfd, resp)) == S_OK) + && (strcmp(RSP_SMART_MODE, resp) == 0)) { + return (S_OK); + } + + return (S_ACCESS); +} + +/* + * Set a value in the hardware using the '-' (repeat) approach + */ + +int +APC_set_ups_var(int upsfd, const char *cmd, char *newval) +{ + char resp[MAX_STRING]; + char orig[MAX_STRING]; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) { + return (rc); + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: var '%s' original val %s" + , __FUNCTION__, cmd, orig); + } + + if (strcmp(orig, newval) == 0) { + return (S_OK); /* already set */ + } + + *resp = '\0'; + + while (strcmp(resp, orig) != 0) { + if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (strcmp(resp, newval) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: var '%s' set to %s" + , __FUNCTION__, cmd, newval); + } + + strcpy(newval, orig); /* return the old value */ + return (S_OK); /* got it */ + } + } + + LOG(PIL_CRIT, "%s(): Could not set variable '%s' to %s!" + , __FUNCTION__, cmd, newval); + LOG(PIL_CRIT, "%s(): This UPS may not support STONITH :-(" + , __FUNCTION__); + + return (S_OOPS); +} + +/* + * Query the smallest delay supported by the hardware using the + * '-' (repeat) approach and looping through all possible values, + * saving the smallest + */ + +int +APC_get_smallest_delay(int upsfd, const char *cmd, char *smdelay) +{ + char resp[MAX_DELAY_STRING]; + char orig[MAX_DELAY_STRING]; + int delay, smallest; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, orig)) != S_OK)) { + return (rc); + } + + smallest = atoi(orig); + strcpy(smdelay, orig); + + *resp = '\0'; + + /* search for smallest delay; need to loop through all possible + * values so that we leave delay the way we found it */ + while (strcmp(resp, orig) != 0) { + if (((rc = APC_send_cmd(upsfd, SWITCH_TO_NEXT_VAL)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if (((rc = APC_enter_smartmode(upsfd)) != S_OK) + || ((rc = APC_send_cmd(upsfd, cmd)) != S_OK) + || ((rc = APC_recv_rsp(upsfd, resp)) != S_OK)) { + return (rc); + } + + if ((delay = atoi(resp)) < smallest) { + smallest = delay; + strcpy(smdelay, resp); + } + } + + return (S_OK); +} + +/* + * Initialize the ups + */ + +int +APC_init(struct pluginDevice *ad) +{ + int upsfd; + char value[MAX_DELAY_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + /* if ad->upsfd != -1 device has already been configured. */ + /* Just enter smart mode again because otherwise a SmartUPS-1000 */ + /* has been observed to sometimes not respond. */ + if(ad->upsfd >= 0) { + if(APC_enter_smartmode(ad->upsfd) != S_OK) { + return(S_OOPS); + } + return S_OK; + } + + /* open serial port and store the fd in ad->upsfd */ + if ((upsfd = APC_open_serialport(ad->upsdev, B2400)) == -1) { + return S_OOPS; + } + + /* switch into smart mode */ + if (APC_enter_smartmode(upsfd) != S_OK) { + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + + /* get the smallest possible delays for this particular hardware */ + if (APC_get_smallest_delay(upsfd, CMD_SHUTDOWN_DELAY + , ad->shutdown_delay) != S_OK + || APC_get_smallest_delay(upsfd, CMD_WAKEUP_DELAY + , ad->wakeup_delay) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't retrieve smallest delay from UPS" + , __FUNCTION__); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + + /* get the old settings and store them */ + strcpy(value, ad->shutdown_delay); + if (APC_set_ups_var(upsfd, CMD_SHUTDOWN_DELAY, value) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't set shutdown delay to %s" + , __FUNCTION__, ad->shutdown_delay); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + strcpy(ad->old_shutdown_delay, value); + strcpy(value, ad->wakeup_delay); + if (APC_set_ups_var(upsfd, CMD_WAKEUP_DELAY, value) != S_OK) { + LOG(PIL_CRIT, "%s: couldn't set wakeup delay to %s" + , __FUNCTION__, ad->wakeup_delay); + APC_close_serialport(ad->upsdev, upsfd); + ad->upsfd = -1; + return S_OOPS; + } + strcpy(ad->old_wakeup_delay, value); + + ad->upsfd = upsfd; + return S_OK; +} + +/* + * Restore original settings and close the port + */ + +void +APC_deinit(struct pluginDevice *ad) +{ + APC_enter_smartmode( ad->upsfd ); + + APC_set_ups_var(ad->upsfd, CMD_SHUTDOWN_DELAY, ad->old_shutdown_delay); + APC_set_ups_var(ad->upsfd, CMD_WAKEUP_DELAY, ad->old_wakeup_delay); + + /* close serial port */ + if (ad->upsfd >= 0) { + APC_close_serialport(ad->upsdev, ad->upsfd); + ad->upsfd = -1; + } +} +static const char * const * +apcsmart_get_confignames(StonithPlugin* sp) +{ + static const char * names[] = {ST_TTYDEV, ST_HOSTLIST, NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + +/* + * Stash away the config info we've been given... + */ + +static int +apcsmart_set_config(StonithPlugin * s, StonithNVpair* list) +{ + struct pluginDevice * ad = (struct pluginDevice*)s; + StonithNamesToGet namestocopy [] = + { {ST_TTYDEV, NULL} + , {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + ERRIFWRONGDEV(s, S_OOPS); + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + ad->upsdev = namestocopy[0].s_value; + ad->hostlist = OurImports->StringToHostList(namestocopy[1].s_value); + FREE(namestocopy[1].s_value); + + if (ad->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (ad->hostcount = 0; ad->hostlist[ad->hostcount] + ; ad->hostcount++) { + strdown(ad->hostlist[ad->hostcount]); + } + if (access(ad->upsdev, R_OK|W_OK|F_OK) < 0) { + LOG(PIL_CRIT,"Cannot access tty [%s]", ad->upsdev); + return S_BADCONFIG; + } + + return ad->hostcount ? S_OK : S_BADCONFIG; +} + +/* + * return the status for this device + */ + +static int +apcsmart_status(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + char resp[MAX_STRING]; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + + /* get status */ + if (((rc = APC_init( ad )) == S_OK) + && ((rc = APC_send_cmd(ad->upsfd, CMD_GET_STATUS)) == S_OK) + && ((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK)) { + return (S_OK); /* everything ok. */ + } + if (Debug) { + LOG(PIL_DEBUG, "%s: failed, rc=%d.", __FUNCTION__, rc); + } + return (rc); +} + + +/* + * return the list of hosts configured for this device + */ + +static char ** +apcsmart_hostlist(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + ERRIFNOTCONFIGED(s,NULL); + + return OurImports->CopyHostList((const char **)(void*)ad->hostlist); +} + +static gboolean +apcsmart_RegisterBitsSet(struct pluginDevice * ad, int nreg, unsigned bits +, gboolean* waserr) +{ + const char* reqregs[4] = {"?", "~", "'", "8"}; + unsigned regval; + char resp[MAX_STRING]; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + + if (APC_enter_smartmode(ad->upsfd) != S_OK + || APC_send_cmd(ad->upsfd, reqregs[nreg]) != S_OK + || APC_recv_rsp(ad->upsfd, resp) != S_OK + || (sscanf(resp, "%02x", ®val) != 1)) { + if (waserr){ + *waserr = TRUE; + } + return FALSE; + } + if (waserr){ + *waserr = FALSE; + } + return ((regval & bits) == bits); +} + +#define apcsmart_IsPoweredOff(ad, err) apcsmart_RegisterBitsSet(ad,1,0x40,err) +#define apcsmart_ResetHappening(ad,err) apcsmart_RegisterBitsSet(ad,3,0x08,err) + + +static int +apcsmart_ReqOnOff(struct pluginDevice * ad, int request) +{ + const char * cmdstr; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + cmdstr = (request == ST_POWEROFF ? CMD_OFF : CMD_ON); + /* enter smartmode, send on/off command */ + if ((rc =APC_enter_smartmode(ad->upsfd)) != S_OK + || (rc = APC_send_cmd(ad->upsfd, cmdstr)) != S_OK) { + return rc; + } + sleep(2); + if ((rc = APC_send_cmd(ad->upsfd, cmdstr)) == S_OK) { + gboolean ison; + gboolean waserr; + sleep(1); + ison = !apcsmart_IsPoweredOff(ad, &waserr); + if (waserr) { + return S_RESETFAIL; + } + if (request == ST_POWEROFF) { + return ison ? S_RESETFAIL : S_OK; + }else{ + return ison ? S_OK : S_RESETFAIL; + } + } + return rc; +} + +/* + * reset the host + */ + +static int +apcsmart_ReqGenericReset(struct pluginDevice *ad) +{ + char resp[MAX_STRING]; + int rc = S_RESETFAIL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + /* send reset command(s) */ + if (((rc = APC_init(ad)) == S_OK) + && ((rc = APC_send_cmd(ad->upsfd, CMD_RESET)) == S_OK)) { + if (((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK) + && (strcmp(resp, RSP_RESET) == 0 + || strcmp(resp, RSP_RESET2) == 0)) { + /* first kind of reset command was accepted */ + } else if (((rc = APC_send_cmd(ad->upsfd, CMD_RESET2)) == S_OK) + && ((rc = APC_recv_rsp(ad->upsfd, resp)) == S_OK) + && (strcmp(resp, RSP_RESET) == 0 + || strcmp(resp, RSP_RESET2) == 0)) { + /* second kind of command was accepted */ + } else { + if (Debug) { + LOG(PIL_DEBUG, "APC: neither reset command " + "was accepted"); + } + rc = S_RESETFAIL; + } + } + if (rc == S_OK) { + /* we wait grace period + up to 10 seconds after shutdown */ + int maxdelay = atoi(ad->shutdown_delay)+10; + int j; + + for (j=0; j < maxdelay; ++j) { + gboolean err; + if (apcsmart_ResetHappening(ad, &err)) { + return err ? S_RESETFAIL : S_OK; + } + sleep(1); + } + LOG(PIL_CRIT, "%s: timed out waiting for reset to end." + , __FUNCTION__); + return S_RESETFAIL; + + }else{ + if (strcmp(resp, RSP_NA) == 0){ + gboolean iserr; + /* This means it's currently powered off */ + /* or busy on a previous command... */ + if (apcsmart_IsPoweredOff(ad, &iserr)) { + if (iserr) { + LOG(PIL_DEBUG, "%s: power off " + "detection failed.", __FUNCTION__); + return S_RESETFAIL; + } + if (Debug) { + LOG(PIL_DEBUG, "APC: was powered off, " + "powering back on."); + } + return apcsmart_ReqOnOff(ad, ST_POWERON); + } + } + } + strcpy(resp, "?"); + + /* reset failed */ + + return S_RESETFAIL; +} + +static int +apcsmart_reset_req(StonithPlugin * s, int request, const char *host) +{ + char ** hl; + int b_found=FALSE; + struct pluginDevice * ad = (struct pluginDevice *) s; + int rc; + + ERRIFNOTCONFIGED(s, S_OOPS); + + if (host == NULL) { + LOG(PIL_CRIT, "%s: invalid hostname argument.", __FUNCTION__); + return (S_INVAL); + } + + /* look through the hostlist */ + hl = ad->hostlist; + + while (*hl && !b_found ) { + if( strcasecmp( *hl, host ) == 0 ) { + b_found = TRUE; + break; + }else{ + ++hl; + } + } + + /* host not found in hostlist */ + if( !b_found ) { + LOG(PIL_CRIT, "%s: host '%s' not in hostlist." + , __FUNCTION__, host); + return S_BADHOST; + } + if ((rc = APC_init(ad)) != S_OK) { + return rc; + } + + if (request == ST_POWERON || request == ST_POWEROFF) { + return apcsmart_ReqOnOff(ad, request); + } + return apcsmart_ReqGenericReset(ad); +} + + +/* + * get info about the stonith device + */ + +static const char * +apcsmart_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + const char *ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->upsdev; + break; + + case ST_DEVICEDESCR: + ret = "APC Smart UPS\n" + " (via serial port - NOT USB!). \n" + " Works with higher-end APC UPSes, like\n" + " Back-UPS Pro, Smart-UPS, Matrix-UPS, etc.\n" + " (Smart-UPS may have to be >= Smart-UPS 700?).\n" + " See http://www.networkupstools.org/protocols/apcsmart.html\n" + " for protocol compatibility details."; + break; + + case ST_DEVICEURL: + ret = "http://www.apc.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcsmartXML; + break; + + default: + ret = NULL; + break; + } + return (ret); +} + +/* + * APC Stonith destructor... + */ + +static void +apcsmart_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad = (struct pluginDevice *) s; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + VOIDERRIFWRONGDEV(s); + + if (ad->upsfd >= 0 && ad->upsdev) { + APC_deinit( ad ); + } + + ad->pluginid = NOTpluginID; + + if (ad->hostlist) { + stonith_free_hostlist(ad->hostlist); + ad->hostlist = NULL; + } + if (ad->upsdev != NULL) { + FREE(ad->upsdev); + ad->upsdev = NULL; + } + + ad->hostcount = -1; + ad->upsfd = -1; + + FREE(ad); + +} + +/* + * Create a new APC Stonith device. Too bad this function can't be + * static + */ + +static StonithPlugin * +apcsmart_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + memset(ad, 0, sizeof(*ad)); + + ad->pluginid = pluginid; + ad->hostlist = NULL; + ad->hostcount = -1; + ad->upsfd = -1; + ad->idinfo = DEVICE; + ad->sp.s_ops = &apcsmartOps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: returning successfully.", __FUNCTION__); + } + return &(ad->sp); +} diff --git a/lib/plugins/stonith/apcsmart.cfg.example b/lib/plugins/stonith/apcsmart.cfg.example new file mode 100644 index 0000000..278f925 --- /dev/null +++ b/lib/plugins/stonith/apcsmart.cfg.example @@ -0,0 +1 @@ +/dev/ups hostname diff --git a/lib/plugins/stonith/baytech.c b/lib/plugins/stonith/baytech.c new file mode 100644 index 0000000..33093ad --- /dev/null +++ b/lib/plugins/stonith/baytech.c @@ -0,0 +1,924 @@ +/* + * Stonith module for BayTech Remote Power Controllers (RPC-x devices) + * + * Copyright (c) 2000 Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#define DEVICE "BayTech power switch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN baytech +#define PIL_PLUGIN_S "baytech" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +#include "stonith_signal.h" + +static StonithPlugin * baytech_new(const char *); +static void baytech_destroy(StonithPlugin *); +static int baytech_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * baytech_get_confignames(StonithPlugin * s); +static const char * baytech_get_info(StonithPlugin * s, int InfoType); +static int baytech_status(StonithPlugin *); +static int baytech_reset_req(StonithPlugin * s, int request, const char * host); +static char ** baytech_hostlist(StonithPlugin *); + +static struct stonith_ops baytechOps ={ + baytech_new, /* Create new STONITH object */ + baytech_destroy, /* Destroy STONITH object */ + baytech_get_info, /* Return STONITH info string */ + baytech_get_confignames, /* Return STONITH config vars */ + baytech_set_config, /* set configuration from vars */ + baytech_status, /* Return STONITH device status */ + baytech_reset_req, /* Request a reset */ + baytech_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_expect_helpers.h" + +#define MAXOUTLET 32 + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &baytechOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have an RPC-5. This code has been tested with this switch. + * + * The BayTech switches are quite nice, but the dialogues are a bit of a + * pain for mechanical parsing. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * unitid; + const struct BayTechModelInfo* modelinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * user; + char * passwd; +}; + +struct BayTechModelInfo { + const char * type; /* Baytech model info */ + size_t socklen; /* Length of socket name string */ + struct Etoken * expect; /* Expect string before outlet list */ +}; + +static int parse_socket_line(struct pluginDevice*,const char * +, int *, char *); + +static const char * pluginid = "BayTech-Stonith"; +static const char * NOTpluginID = "BayTech device has been destroyed"; + +/* + * Different expect strings that we get from the Baytech + * Remote Power Controllers... + */ + +#define BAYTECHASSOC "Bay Technical Associates" + +static struct Etoken BayTechAssoc[] = { {BAYTECHASSOC, 0, 0}, {NULL,0,0}}; +static struct Etoken UnitId[] = { {"Unit ID: ", 0, 0}, {NULL,0,0}}; +static struct Etoken login[] = { {"username>", 0, 0} ,{NULL,0,0}}; +static struct Etoken password[] = { {"password>", 0, 0} + , {"username>", 0, 0} ,{NULL,0,0}}; +static struct Etoken Selection[] = { {"election>", 0, 0} ,{NULL,0,0}}; +static struct Etoken RPC[] = { {"RPC", 0, 0} ,{NULL,0,0}}; +static struct Etoken LoginOK[] = { {"RPC", 0, 0}, {"Invalid password", 1, 0} + , {NULL,0,0}}; +static struct Etoken GTSign[] = { {">", 0, 0} ,{NULL,0,0}}; +static struct Etoken Menu[] = { {"Menu:", 0, 0} ,{NULL,0,0}}; +static struct Etoken Temp[] = { {"emperature: ", 0, 0} + , {NULL,0,0}}; +static struct Etoken Break[] = { {"Status", 0, 0} + , {NULL,0,0}}; +static struct Etoken PowerApplied[] = { {"ower applied to outlet", 0, 0} + , {NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Rebooting[] = { {"ebooting selected outlet", 0, 0} + , {"(Y/N)>", 1, 0} + , {"already off.", 2, 0} + , {NULL,0,0}}; + +static struct Etoken TurningOnOff[] = { {"RPC", 0, 0} + , {"(Y/N)>", 1, 0} + , {"already ", 2, 0} + , {NULL,0,0}}; + + +static struct BayTechModelInfo ModelInfo [] = { + {"BayTech RPC-5", 18, Temp},/* This first model will be the default */ + {"BayTech RPC-3", 10, Break}, + {"BayTech RPC-3A", 10, Break}, + {NULL, 0, NULL}, +}; + +#include "stonith_config_xml.h" + +static const char *baytechXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +static int RPC_connect_device(struct pluginDevice * bt); +static int RPCLogin(struct pluginDevice * bt); +static int RPCRobustLogin(struct pluginDevice * bt); +static int RPCNametoOutletList(struct pluginDevice*, const char * name +, int outletlist[]); +static int RPCReset(struct pluginDevice*, int unitnum, const char * rebootid); +static int RPCLogout(struct pluginDevice * bt); + + +static int RPC_onoff(struct pluginDevice*, int unitnum, const char * unitid +, int request); + +/* Login to the Baytech Remote Power Controller (RPC) */ + +static int +RPCLogin(struct pluginDevice * bt) +{ + char IDinfo[128]; + static char IDbuf[128]; + char * idptr = IDinfo; + char * delim; + int j; + + EXPECT(bt->rdfd, RPC, 10); + + /* Look for the unit type info */ + if (EXPECT_TOK(bt->rdfd, BayTechAssoc, 2, IDinfo + , sizeof(IDinfo), Debug) < 0) { + LOG(PIL_CRIT, "No initial response from %s.", bt->idinfo); + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + idptr += strspn(idptr, WHITESPACE); + /* + * We should be looking at something like this: + * RPC-5 Telnet Host + * Revision F 4.22, (C) 1999 + * Bay Technical Associates + */ + + /* Truncate the result after the RPC-5 part */ + if ((delim = strchr(idptr, ' ')) != NULL) { + *delim = EOS; + } + snprintf(IDbuf, sizeof(IDbuf), "BayTech RPC%s", idptr); + REPLSTR(bt->idinfo, IDbuf); + if (bt->idinfo == NULL) { + return(S_OOPS); + } + + bt->modelinfo = &ModelInfo[0]; + + for (j=0; ModelInfo[j].type != NULL; ++j) { + /* + * TIMXXX - + * Look at device ID as this really describes the model. + */ + if (strcasecmp(ModelInfo[j].type, IDbuf) == 0) { + bt->modelinfo = &ModelInfo[j]; + break; + } + } + + /* Look for the unit id info */ + EXPECT(bt->rdfd, UnitId, 10); + SNARF(bt->rdfd, IDbuf, 2); + delim = IDbuf + strcspn(IDbuf, WHITESPACE); + *delim = EOS; + REPLSTR(bt->unitid, IDbuf); + if (bt->unitid == NULL) { + return(S_OOPS); + } + + /* Expect "username>" */ + EXPECT(bt->rdfd, login, 2); + + SEND(bt->wrfd, bt->user); + SEND(bt->wrfd, "\r"); + + /* Expect "password>" */ + + switch (StonithLookFor(bt->rdfd, password, 5)) { + case 0: /* Good! */ + break; + + case 1: /* OOPS! got another username prompt */ + LOG(PIL_CRIT, "Invalid username for %s.", bt->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + + SEND(bt->wrfd, bt->passwd); + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + + switch (StonithLookFor(bt->rdfd, LoginOK, 5)) { + + case 0: /* Good! */ + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", bt->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + EXPECT(bt->rdfd, Menu, 2); + + return(S_OK); +} + +static int +RPCRobustLogin(struct pluginDevice * bt) +{ + int rc=S_OOPS; + int j; + + for (j=0; j < 20 && rc != S_OK; ++j) { + + + if (RPC_connect_device(bt) != S_OK) { + continue; + } + + rc = RPCLogin(bt); + } + return rc; +} + +/* Log out of the Baytech RPC */ + +static int +RPCLogout(struct pluginDevice* bt) +{ + int rc; + + /* Make sure we're in the right menu... */ + SEND(bt->wrfd, "\r"); + + /* Expect "Selection>" */ + rc = StonithLookFor(bt->rdfd, Selection, 5); + + /* Option 6 is Logout */ + SEND(bt->wrfd, "6\r"); + + close(bt->wrfd); + close(bt->rdfd); + bt->wrfd = bt->rdfd = -1; + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} + +/* Reset (power-cycle) the given outlet number */ +static int +RPCReset(struct pluginDevice* bt, int unitnum, const char * rebootid) +{ + char unum[32]; + + + SEND(bt->wrfd, "\r"); + + /* Make sure we're in the top level menu */ + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + + /* Send REBOOT command for given outlet */ + snprintf(unum, sizeof(unum), "REBOOT %d\r", unitnum); + SEND(bt->wrfd, unum); + + /* Expect "ebooting "... or "(Y/N)" (if confirmation turned on) */ + + retry: + switch (StonithLookFor(bt->rdfd, Rebooting, 5)) { + case 0: /* Got "Rebooting" Do nothing */ + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(bt->wrfd, "Y\r"); + goto retry; + + case 2: /* Outlet is turned off */ + LOG(PIL_CRIT, "Host is OFF: %s.", rebootid); + return(S_ISOFF); + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + LOG(PIL_INFO, "Host %s (outlet %d) being rebooted." + , rebootid, unitnum); + + /* Expect "ower applied to outlet" */ + if (StonithLookFor(bt->rdfd, PowerApplied, 30) < 0) { + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + /* All Right! Power is back on. Life is Good! */ + + LOG(PIL_INFO, "Power restored to host %s (outlet %d)." + , rebootid, unitnum); + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC,5); + EXPECT(bt->rdfd, GTSign, 5); + + /* Pop back to main menu */ + SEND(bt->wrfd, "MENU\r"); + return(S_OK); +} + +static int +RPC_onoff(struct pluginDevice* bt, int unitnum, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "on" : "off"); + int rc; + + + if ((rc = RPCRobustLogin(bt) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(rc); + } + SEND(bt->wrfd, "\r"); + + /* Make sure we're in the top level menu */ + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + + /* Send ON/OFF command for given outlet */ + snprintf(unum, sizeof(unum), "%s %d\r" + , onoff, unitnum); + SEND(bt->wrfd, unum); + + /* Expect "RPC->x "... or "(Y/N)" (if confirmation turned on) */ + + if (StonithLookFor(bt->rdfd, TurningOnOff, 10) == 1) { + /* They've turned on that annoying command confirmation :-( */ + SEND(bt->wrfd, "Y\r"); + EXPECT(bt->rdfd, TurningOnOff, 10); + } + + EXPECT(bt->rdfd, GTSign, 10); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to host %s (outlet %d) turned %s." + , unitid, unitnum, onoff); + /* Pop back to main menu */ + SEND(bt->wrfd, "MENU\r"); + return(S_OK); +} + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +RPCNametoOutletList(struct pluginDevice* bt, const char * name +, int outletlist[]) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + int maxfound = 0; + + + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, GTSign, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(bt->wrfd, "STATUS\r"); + + /* Expect: "emperature:" so we can skip over it... */ + EXPECT(bt->rdfd, bt->modelinfo->expect, 5); + EXPECT(bt->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + char * last; + NameMapping[0] = EOS; + SNARF(bt->rdfd, NameMapping, 5); + + if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) { + continue; + } + + last = sockname+bt->modelinfo->socklen; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strcasecmp(name, sockname) == 0) { + outletlist[maxfound] = sockno; + ++maxfound; + } + } while (strlen(NameMapping) > 2 && maxfound < MAXOUTLET); + + /* Pop back out to the top level menu */ + SEND(bt->wrfd, "MENU\r"); + return(maxfound); +} + +static int +baytech_status(StonithPlugin *s) +{ + struct pluginDevice* bt; + int rc; + + ERRIFNOTCONFIGED(s,S_OOPS); + + bt = (struct pluginDevice*) s; + + if ((rc = RPCRobustLogin(bt) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(rc); + } + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + EXPECT(bt->rdfd, RPC, 5); + EXPECT(bt->rdfd, Menu, 5); + + return(RPCLogout(bt)); +} +/* + * Return the list of hosts (outlet names) for the devices on this BayTech unit + */ + +static char ** +baytech_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* bt; + unsigned int i; + + ERRIFNOTCONFIGED(s,NULL); + + bt = (struct pluginDevice*) s; + + if (RPCRobustLogin(bt) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + return(NULL); + } + + /* Verify that we're in the top-level menu */ + SEND(bt->wrfd, "\r"); + + /* Expect "RPC-x Menu" */ + NULLEXPECT(bt->rdfd, RPC, 5); + NULLEXPECT(bt->rdfd, Menu, 5); + + /* OK. Request sub-menu 1 (Outlet Control) */ + SEND(bt->wrfd, "1\r"); + + /* Verify that we're in the sub-menu */ + + /* Expect: "RPC-x>" */ + NULLEXPECT(bt->rdfd, RPC, 5); + NULLEXPECT(bt->rdfd, GTSign, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(bt->wrfd, "STATUS\r"); + + /* Expect: "emperature:" so we can skip over it... */ + NULLEXPECT(bt->rdfd, bt->modelinfo->expect, 5); + NULLEXPECT(bt->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + int sockno; + char sockname[64]; + char * last; + char * nm; + + NameMapping[0] = EOS; + + NULLSNARF(bt->rdfd, NameMapping, 5); + + if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) { + continue; + } + + last = sockname+bt->modelinfo->socklen; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (numnames >= DIMOF(NameList)-1) { + break; + } + if ((nm = (char*)STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } while (strlen(NameMapping) > 2); + + /* Pop back out to the top level menu */ + SEND(bt->wrfd, "MENU\r"); + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)RPCLogout(bt); + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; iOpenStreamSocket(bt->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + bt->rdfd = bt->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +baytech_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = 0; + struct pluginDevice* bt; + + ERRIFNOTCONFIGED(s,S_OOPS); + + bt = (struct pluginDevice*) s; + + if ((rc = RPCRobustLogin(bt)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s." + , bt->idinfo ? bt->idinfo : DEVICE); + }else{ + int noutlets; + int outlets[MAXOUTLET]; + int j; + noutlets = RPCNametoOutletList(bt, host, outlets); + + if (noutlets < 1) { + LOG(PIL_CRIT, "%s %s doesn't control host [%s]" + , bt->idinfo, bt->unitid, host); + return(S_BADHOST); + } + switch(request) { + + case ST_POWERON: + case ST_POWEROFF: + for (j=0; rc == S_OK && j < noutlets;++j) { + rc = RPC_onoff(bt, outlets[j], host, request); + } + break; + case ST_GENERIC_RESET: + /* + * Our strategy here: + * 1. Power off all outlets except the last one + * 2. reset the last outlet + * 3. power the other outlets back on + */ + + for (j=0; rc == S_OK && j < noutlets-1; ++j) { + rc = RPC_onoff(bt,outlets[j],host + , ST_POWEROFF); + } + if (rc == S_OK) { + rc = RPCReset(bt, outlets[j], host); + } + for (j=0; rc == S_OK && j < noutlets-1; ++j) { + rc = RPC_onoff(bt, outlets[j], host + , ST_POWERON); + } + break; + default: + rc = S_INVAL; + break; + } + } + + lorc = RPCLogout(bt); + + return(rc != S_OK ? rc : lorc); +} + +static const char * const * +baytech_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +baytech_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* bt = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (bt->sp.isconfigured) { + return S_OOPS; + } + + if ((rc =OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + bt->device = namestocopy[0].s_value; + bt->user = namestocopy[1].s_value; + bt->passwd = namestocopy[2].s_value; + + return(S_OK); +} + +static const char * +baytech_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* bt; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + bt = (struct pluginDevice *)s; + + switch (reqtype) { + + case ST_DEVICEID: /* What type of device? */ + ret = bt->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = bt->device; + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "Bay Technical Associates (Baytech) RPC " + "series power switches (via telnet).\n" + "The RPC-5, RPC-3 and RPC-3A switches are well tested" + "."; + break; + + case ST_DEVICEURL: /* Manufacturer's web site */ + ret = "http://www.baytech.net/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = baytechXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Baytech Stonith destructor... + */ +static void +baytech_destroy(StonithPlugin *s) +{ + struct pluginDevice* bt; + + VOIDERRIFWRONGDEV(s); + + bt = (struct pluginDevice *)s; + + bt->pluginid = NOTpluginID; + if (bt->rdfd >= 0) { + close(bt->rdfd); + bt->rdfd = -1; + } + if (bt->wrfd >= 0) { + close(bt->wrfd); + bt->wrfd = -1; + } + if (bt->device != NULL) { + FREE(bt->device); + bt->device = NULL; + } + if (bt->user != NULL) { + FREE(bt->user); + bt->user = NULL; + } + if (bt->passwd != NULL) { + FREE(bt->passwd); + bt->passwd = NULL; + } + if (bt->idinfo != NULL) { + FREE(bt->idinfo); + bt->idinfo = NULL; + } + if (bt->unitid != NULL) { + FREE(bt->unitid); + bt->unitid = NULL; + } + FREE(bt); +} + +/* Create a new BayTech Stonith device. */ + +static StonithPlugin * +baytech_new(const char *subplugin) +{ + struct pluginDevice* bt = ST_MALLOCT(struct pluginDevice); + + if (bt == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(bt, 0, sizeof(*bt)); + bt->pluginid = pluginid; + bt->pid = -1; + bt->rdfd = -1; + bt->wrfd = -1; + REPLSTR(bt->idinfo, DEVICE); + if (bt->idinfo == NULL) { + FREE(bt); + return(NULL); + } + bt->modelinfo = &ModelInfo[0]; + bt->sp.s_ops = &baytechOps; + + return &(bt->sp); /* same as "bt" */ +} + +static int +parse_socket_line(struct pluginDevice * bt, const char *NameMapping +, int *sockno, char *sockname) +{ +#if 0 + char format[64]; + snprintf(format, sizeof(format), "%%7d %%%dc" + , bt->modelinfo->socklen); + /* 7 digits, 7 blanks, then 'socklen' characters */ + /* [0-6]: digits, NameMapping[13] begins the sockname */ + /* NameMapping strlen must be >= socklen + 14 */ + + if (sscanf(NameMapping, format, sockno, sockname) != 2) { + return FALSE; + } +#else +# define OFFSET 14 + + if (sscanf(NameMapping, "%7d", sockno) != 1 + || strlen(NameMapping) < OFFSET+bt->modelinfo->socklen) { + return FALSE; + } + strncpy(sockname, NameMapping+OFFSET, bt->modelinfo->socklen); + sockname[bt->modelinfo->socklen] = EOS; +#endif + return TRUE; +} diff --git a/lib/plugins/stonith/bladehpi.c b/lib/plugins/stonith/bladehpi.c new file mode 100644 index 0000000..ae9a4cf --- /dev/null +++ b/lib/plugins/stonith/bladehpi.c @@ -0,0 +1,1101 @@ +/* + * Stonith module for BladeCenter via OpenHPI, an implementation of Service + * Availability Forum's Hardware Platfrom Interface + * + * Author: Dave Blaschke + * + * Copyright (c) 2005 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#define DEVICE "IBM BladeCenter (OpenHPI)" + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN bladehpi +#define PIL_PLUGIN_S "bladehpi" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +#include + +/* Maximum number of seconds to wait for host to power off */ +#define MAX_POWEROFF_WAIT 60 + +/* entity_root, the one required plugin parameter */ +#define ST_ENTITYROOT "entity_root" + +/* String format of entity_root */ +#define SYSTEM_CHASSIS_FMT "{SYSTEM_CHASSIS,%d}" + +/* soft_reset, the one optional plugin parameter */ +#define ST_SOFTRESET "soft_reset" + +#define OPENHPIURL "http://www.openhpi.org/" + +/* OpenHPI resource types of interest to this plugin */ +#define OHRES_NONE 0 +#define OHRES_BLADECENT 1 +#define OHRES_MGMTMOD 2 +#define OHRES_BLADE 3 + +/* IBMBC_WAIT_FOR_OFF - This constant has to do with the problem that + saHpiResourcePowerStateSet can return before the desired state has been + achieved by the blade. In the SAHPI_POWER_OFF case this is not good, + as whoever calls this plugin assumes that the power is actually off + when the plugin returns with a successful return code. Define this + constant to build code that loops in one second intervals after calling + saHpiResourcePowerStateSet(SAHPI_POWER_OFF) to make sure the power is + really off. +#define IBMBC_WAIT_FOR_OFF */ + +static StonithPlugin * bladehpi_new(const char *); +static void bladehpi_destroy(StonithPlugin *); +static const char * bladehpi_getinfo(StonithPlugin *, int); +static const char * const * bladehpi_get_confignames(StonithPlugin *); +static int bladehpi_status(StonithPlugin *); +static int bladehpi_reset_req(StonithPlugin *, int, const char *); +static char ** bladehpi_hostlist(StonithPlugin *); +static int bladehpi_set_config(StonithPlugin *, StonithNVpair *); + +static struct stonith_ops bladehpiOps = { + bladehpi_new, /* Create new STONITH object */ + bladehpi_destroy, /* Destroy STONITH object */ + bladehpi_getinfo, /* Return STONITH info string */ + bladehpi_get_confignames, /* Return configuration parameters */ + bladehpi_set_config, /* Set configuration */ + bladehpi_status, /* Return STONITH device status */ + bladehpi_reset_req, /* Request a reset */ + bladehpi_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports * PluginImports; +static PILPlugin * OurPlugin; +static PILInterface * OurInterface; +static StonithImports * OurImports; +static void * interfprivate; + + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin *us, const PILPluginImports *imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin *us, const PILPluginImports *imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us + , PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &bladehpiOps + , NULL /* close */ + , &OurInterface + , (void *)&OurImports + , &interfprivate); +} + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * device; + int softreset; + GList * hostlist; + SaHpiVersionT ohver; /* OpenHPI interface version */ + SaHpiSessionIdT ohsession; /* session ID */ + SaHpiUint32T ohrptcnt; /* RPT count for hostlist */ + SaHpiResourceIdT ohdevid; /* device resource ID */ + SaHpiResourceIdT ohsensid; /* sensor resource ID */ + SaHpiSensorNumT ohsensnum; /* sensor number */ +}; + +static int open_hpi_session(struct pluginDevice *dev); +static void close_hpi_session(struct pluginDevice *dev); + +static const char *pluginid = "BladeCenterDevice-Stonith"; +static const char *NOTpluginID = "IBM BladeCenter device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_ENTITYROOT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_ENTITYROOT \ + XML_PARM_SHORTDESC_END + +#define XML_ENTITYROOT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The entity_root of the STONITH device from the OpenHPI config file" \ + XML_PARM_LONGDESC_END + +#define XML_ENTITYROOT_PARM \ + XML_PARAMETER_BEGIN(ST_ENTITYROOT, "string", "1", "0") \ + XML_ENTITYROOT_SHORTDESC \ + XML_ENTITYROOT_LONGDESC \ + XML_PARAMETER_END + +#define XML_SOFTRESET_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_SOFTRESET \ + XML_PARM_SHORTDESC_END + +#define XML_SOFTRESET_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "Soft reset indicator, true|1 if STONITH device should use soft reset (power cycle) to reset nodes, false|0 if device should use hard reset (power off, wait, power on); default is false" \ + XML_PARM_LONGDESC_END + +#define XML_SOFTRESET_PARM \ + XML_PARAMETER_BEGIN(ST_SOFTRESET, "string", "0", "0") \ + XML_SOFTRESET_SHORTDESC \ + XML_SOFTRESET_LONGDESC \ + XML_PARAMETER_END + +static const char *bladehpiXML = + XML_PARAMETERS_BEGIN + XML_ENTITYROOT_PARM + XML_SOFTRESET_PARM + XML_PARAMETERS_END; + +static int get_resource_type(char *, SaHpiRptEntryT *); +static int get_sensor_num(SaHpiSessionIdT, SaHpiResourceIdT); +static int get_bladehpi_hostlist(struct pluginDevice *); +static void free_bladehpi_hostlist(struct pluginDevice *); +static int get_num_tokens(char *str); + +struct blade_info { + char * name; /* blade name */ + SaHpiResourceIdT resourceId; /* blade resource ID */ + SaHpiCapabilitiesT resourceCaps; /* blade capabilities */ +}; + + +static int +bladehpi_status(StonithPlugin *s) +{ + struct pluginDevice * dev; + SaErrorT ohrc; + SaHpiDomainInfoT ohdi; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, S_OOPS); + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return rc; + + /* Refresh the hostlist only if RPTs updated */ + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + rc = S_BADCONFIG; + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + rc = S_BADCONFIG; + goto done; + } + } + + /* At this point, hostlist is up to date */ + if (dev->ohsensid && dev->ohsensnum) { + /* + * For accurate status, need to make a call that goes out to + * BladeCenter MM because the calls made so far by this + * function (and perhaps get_bladehpi_hostlist) only retrieve + * information from memory cached by OpenHPI + */ + ohrc = saHpiSensorReadingGet(dev->ohsession + , dev->ohsensid, dev->ohsensnum, NULL, NULL); + if (ohrc == SA_ERR_HPI_BUSY || ohrc == SA_ERR_HPI_NO_RESPONSE) { + LOG(PIL_CRIT, "Unable to connect to BladeCenter in %s" + , __FUNCTION__); + rc = S_OOPS; + goto done; + } + } + +done: + close_hpi_session(dev); + return (rc == S_OK) ? (dev->ohdevid ? S_OK : S_OOPS) : rc; +} + + +/* + * Return the list of hosts configured for this HMC device + */ + +static char ** +bladehpi_hostlist(StonithPlugin *s) +{ + struct pluginDevice * dev; + int numnames = 0, j; + char ** ret = NULL; + GList * node = NULL; + SaErrorT ohrc; + SaHpiDomainInfoT ohdi; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, NULL); + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return NULL; + + /* Refresh the hostlist only if RPTs updated */ + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + goto done; + } + } + + /* At this point, hostlist is up to date */ + numnames = g_list_length(dev->hostlist); + if (numnames < 0) { + LOG(PIL_CRIT, "Unconfigured stonith object in %s" + , __FUNCTION__); + goto done; + } + + ret = (char **)MALLOC((numnames+1) * sizeof(char *)); + if (ret == NULL) { + LOG(PIL_CRIT, "Out of memory for malloc in %s", __FUNCTION__); + goto done; + } + + memset(ret, 0, (numnames+1) * sizeof(char *)); + for (node = g_list_first(dev->hostlist), j = 0 + ; NULL != node + ; j++, node = g_list_next(node)) { + ret[j] = STRDUP(((struct blade_info *)node->data)->name); + if (ret[j] == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s" + , __FUNCTION__); + stonith_free_hostlist(ret); + ret = NULL; + goto done; + } + strdown(ret[j]); + } + +done: + close_hpi_session(dev); + return ret; +} + + +static const char * const * +bladehpi_get_confignames(StonithPlugin *s) +{ + static const char * names[] = {ST_ENTITYROOT, NULL}; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + return names; +} + + +/* + * Reset the given host, and obey the request type. + */ + +static int +bladehpi_reset_req(StonithPlugin *s, int request, const char *host) +{ + GList * node = NULL; + struct pluginDevice * dev = NULL; + struct blade_info * bi = NULL; + SaHpiPowerStateT ohcurstate, ohnewstate; + SaHpiDomainInfoT ohdi; + SaErrorT ohrc; + int rc = S_OK; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, request=%d, host=%s" + , __FUNCTION__, request, host); + } + + ERRIFWRONGDEV(s, S_OOPS); + + if (host == NULL) { + LOG(PIL_CRIT, "Invalid host argument to %s", __FUNCTION__); + rc = S_OOPS; + goto done; + } + + dev = (struct pluginDevice *)s; + rc = open_hpi_session(dev); + if( rc != S_OK ) + return rc; + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + rc = S_BADCONFIG; + goto done; + } + if (dev->ohrptcnt != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if (get_bladehpi_hostlist(dev) != S_OK) { + LOG(PIL_CRIT, "Unable to obtain list of hosts in %s" + , __FUNCTION__); + rc = S_OOPS; + goto done; + } + } + + for (node = g_list_first(dev->hostlist) + ; node != NULL + ; node = g_list_next(node)) { + bi = ((struct blade_info *)node->data); + if (Debug) { + LOG(PIL_DEBUG, "Found host %s in hostlist", bi->name); + } + + if (!strcasecmp(bi->name, host)) { + break; + } + } + + if (!node || !bi) { + LOG(PIL_CRIT + , "Host %s is not configured in this STONITH module, " + "please check your configuration information", host); + rc = S_OOPS; + goto done; + } + + /* Make sure host has proper capabilities for get */ + if (!(bi->resourceCaps & SAHPI_CAPABILITY_POWER)) { + LOG(PIL_CRIT + , "Host %s does not have power capability", host); + rc = S_OOPS; + goto done; + } + + ohrc = saHpiResourcePowerStateGet(dev->ohsession, bi->resourceId + , &ohcurstate); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get host %s power state (%d)" + , host, ohrc); + rc = S_OOPS; + goto done; + } + + switch (request) { + case ST_POWERON: + if (ohcurstate == SAHPI_POWER_ON) { + LOG(PIL_INFO, "Host %s already on", host); + goto done; + } + ohnewstate = SAHPI_POWER_ON; + + break; + + case ST_POWEROFF: + if (ohcurstate == SAHPI_POWER_OFF) { + LOG(PIL_INFO, "Host %s already off", host); + goto done; + } + ohnewstate = SAHPI_POWER_OFF; + + break; + + case ST_GENERIC_RESET: + if (ohcurstate == SAHPI_POWER_OFF) { + ohnewstate = SAHPI_POWER_ON; + } else { + ohnewstate = SAHPI_POWER_CYCLE; + } + + break; + + default: + LOG(PIL_CRIT, "Invalid request argument to %s" + , __FUNCTION__); + rc = S_INVAL; + goto done; + } + + if (!dev->softreset && (ohnewstate == SAHPI_POWER_CYCLE)) { + int maxwait; + + ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, SAHPI_POWER_OFF); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state to" + " OFF (%d)", host, ohrc); + rc = S_OOPS; + goto done; + } + + /* + * Must wait for power off here or subsequent power on request + * may take place while power is still on and thus ignored + */ + maxwait = MAX_POWEROFF_WAIT; + do { + maxwait--; + sleep(1); + ohrc = saHpiResourcePowerStateGet(dev->ohsession + , bi->resourceId, &ohcurstate); + } while ((ohrc == SA_OK) + && (ohcurstate != SAHPI_POWER_OFF) + && (maxwait > 0)); + + if (Debug) { + LOG(PIL_DEBUG, "Waited %d seconds for power off" + , MAX_POWEROFF_WAIT - maxwait); + } + + ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, SAHPI_POWER_ON); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state to" + " ON (%d)", host, ohrc); + rc = S_OOPS; + goto done; + } + } else { + /* Make sure host has proper capabilities to reset */ + if ((ohnewstate == SAHPI_POWER_CYCLE) && + (!(bi->resourceCaps & SAHPI_CAPABILITY_RESET))) { + LOG(PIL_CRIT + , "Host %s does not have reset capability" + , host); + rc = S_OOPS; + goto done; + } + + if ((ohrc = saHpiResourcePowerStateSet(dev->ohsession + , bi->resourceId, ohnewstate)) != SA_OK) { + LOG(PIL_CRIT, "Unable to set host %s power state (%d)" + , host, ohrc); + rc = S_OOPS; + goto done; + } + } + +#ifdef IBMBC_WAIT_FOR_OFF + if (ohnewstate == SAHPI_POWER_OFF) { + int maxwait = MAX_POWEROFF_WAIT; + + do { + maxwait--; + sleep(1); + ohrc = saHpiResourcePowerStateGet(dev->ohsession + , bi->resourceId, &ohcurstate); + } while ((ohrc == SA_OK) + && (ohcurstate != SAHPI_POWER_OFF) + && (maxwait > 0)); + + if (Debug) { + LOG(PIL_DEBUG, "Waited %d seconds for power off" + , MAX_POWEROFF_WAIT - maxwait); + } + } +#endif + + LOG(PIL_INFO, "Host %s %s %d.", host, __FUNCTION__, request); + +done: + close_hpi_session(dev); + return rc; +} + + +/* + * Parse the information in the given configuration file, + * and stash it away... + */ + +static int +bladehpi_set_config(StonithPlugin *s, StonithNVpair *list) +{ + struct pluginDevice * dev = NULL; + StonithNamesToGet namestocopy [] = + { {ST_ENTITYROOT, NULL} + , {NULL, NULL} + }; + int rc, i; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + ERRIFWRONGDEV(s, S_OOPS); + + dev = (struct pluginDevice *)s; + + if (Debug) { + LOG(PIL_DEBUG, "%s conditionally compiled with:" +#ifdef IBMBC_WAIT_FOR_OFF + " IBMBC_WAIT_FOR_OFF" +#endif + , dev->pluginid); + } + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s = %s", ST_ENTITYROOT + , namestocopy[0].s_value); + } + + if (get_num_tokens(namestocopy[0].s_value) == 1) { + /* name=value pairs on command line, look for soft_reset */ + const char *softreset = + OurImports->GetValue(list, ST_SOFTRESET); + if (softreset != NULL) { + if (!strcasecmp(softreset, "true") || + !strcmp(softreset, "1")) { + dev->softreset = 1; + } else if (!strcasecmp(softreset, "false") || + !strcmp(softreset, "0")) { + dev->softreset = 0; + } else { + LOG(PIL_CRIT, "Invalid %s %s, must be " + "true, 1, false or 0" + , ST_SOFTRESET, softreset); + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + } else { + /* -p or -F option with args "entity_root [soft_reset]..." */ + char *pch = namestocopy[0].s_value; + + /* skip over entity_root and null-terminate */ + pch += strcspn(pch, WHITESPACE); + *pch = EOS; + + /* skip over white-space up to next token */ + pch++; + pch += strspn(pch, WHITESPACE); + if (!strcasecmp(pch, "true") || !strcmp(pch, "1")) { + dev->softreset = 1; + } else if (!strcasecmp(pch, "false") || !strcmp(pch, "0")) { + dev->softreset = 0; + } else { + LOG(PIL_CRIT, "Invalid %s %s, must be " + "true, 1, false or 0" + , ST_SOFTRESET, pch); + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + + dev->device = STRDUP(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + if (dev->device == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s", __FUNCTION__); + return S_OOPS; + } + + if (strcspn(dev->device, WHITESPACE) != strlen(dev->device) || + sscanf(dev->device, SYSTEM_CHASSIS_FMT, &i) != 1 || i < 0) { + LOG(PIL_CRIT, "Invalid %s %s, must be of format %s" + , ST_ENTITYROOT, dev->device, SYSTEM_CHASSIS_FMT); + return S_BADCONFIG; + } + + dev->ohver = saHpiVersionGet(); + if (dev->ohver > SAHPI_INTERFACE_VERSION) { + LOG(PIL_CRIT, "Installed OpenHPI interface (%x) greater than " + "one used by plugin (%x), incompatibilites may exist" + , dev->ohver, SAHPI_INTERFACE_VERSION); + return S_BADCONFIG; + } + return S_OK; +} + +static int +open_hpi_session(struct pluginDevice *dev) +{ + SaErrorT ohrc; + + ohrc = saHpiSessionOpen(SAHPI_UNSPECIFIED_DOMAIN_ID + , &dev->ohsession, NULL); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to open HPI session (%d)", ohrc); + return S_BADCONFIG; + } + + ohrc = saHpiDiscover(dev->ohsession); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to discover resources (%d)", ohrc); + return S_BADCONFIG; + } + + return S_OK; +} +static void +close_hpi_session(struct pluginDevice *dev) +{ + if (dev && dev->ohsession) { + saHpiSessionClose(dev->ohsession); + dev->ohsession = 0; + } +} + +static const char * +bladehpi_getinfo(StonithPlugin *s, int reqtype) +{ + struct pluginDevice * dev; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, reqtype=%d" + , __FUNCTION__, reqtype); + } + + ERRIFWRONGDEV(s, NULL); + + dev = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = dev->idinfo; + break; + + case ST_DEVICENAME: + ret = dev->device; + break; + + case ST_DEVICEDESCR: + ret = "IBM BladeCenter via OpenHPI\n" + "Use for IBM xSeries systems managed by BladeCenter\n" + " Required parameter name " ST_ENTITYROOT " is " + "a string (no white-space) of\n" + "the format \""SYSTEM_CHASSIS_FMT"\" " + "which is entity_root of BladeCenter\n" + "from OpenHPI config file, where %d is a positive " + "integer\n" + " Optional parameter name " ST_SOFTRESET " is " + "true|1 if STONITH device should\n" + "use soft reset (power cycle) to reset nodes or " + "false|0 if device should\n" + "use hard reset (power off, wait, power on); " + "default is false"; + break; + + case ST_DEVICEURL: + ret = OPENHPIURL; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = bladehpiXML; + break; + + default: + ret = NULL; + break; + } + + return ret; +} + + +/* + * HMC Stonith destructor... + */ + +static void +bladehpi_destroy(StonithPlugin *s) +{ + struct pluginDevice * dev; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + dev = (struct pluginDevice *)s; + + dev->pluginid = NOTpluginID; + if (dev->device) { + FREE(dev->device); + dev->device = NULL; + } + if (dev->idinfo) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + free_bladehpi_hostlist(dev); + + if (dev->ohsession) { + saHpiSessionClose(dev->ohsession); + dev->ohsession = 0; + } + + FREE(dev); +} + + +static StonithPlugin * +bladehpi_new(const char *subplugin) +{ + struct pluginDevice * dev = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called", __FUNCTION__); + } + + if (dev == NULL) { + LOG(PIL_CRIT, "Out of memory in %s", __FUNCTION__); + return NULL; + } + + memset(dev, 0, sizeof(*dev)); + + dev->pluginid = pluginid; + dev->device = NULL; + dev->hostlist = NULL; + REPLSTR(dev->idinfo, DEVICE); + if (dev->idinfo == NULL) { + FREE(dev); + return NULL; + } + dev->sp.s_ops = &bladehpiOps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: returning successfully", __FUNCTION__); + } + + return ((void *)dev); +} + + +static int +get_resource_type(char *entityRoot, SaHpiRptEntryT *ohRPT) +{ + int i, rc = OHRES_NONE; + int foundBlade = 0, foundExp = 0, foundMgmt = 0; + int foundRoot = 0, foundOther = 0; + char rootName[64]; + SaHpiEntityPathT * ohep = &ohRPT->ResourceEntity; + + if (ohep == NULL || entityRoot == NULL) { + return 0; + } + + /* First find root of entity path, which is last entity in entry */ + for (i = 0; i < SAHPI_MAX_ENTITY_PATH; i++) { + if (ohep->Entry[i].EntityType == SAHPI_ENT_ROOT) { + break; + } + } + + /* Then back up through entries looking for specific entity */ + for (i--; i >= 0; i--) { + switch (ohep->Entry[i].EntityType) { + case SAHPI_ENT_SBC_BLADE: + foundBlade = 1; + break; + + case SAHPI_ENT_SYS_EXPANSION_BOARD: + foundExp = 1; + break; + + case SAHPI_ENT_SYS_MGMNT_MODULE: + if (ohep->Entry[i].EntityLocation == 0) { + foundMgmt = 1; + } + break; + + case SAHPI_ENT_SYSTEM_CHASSIS: + snprintf(rootName, sizeof(rootName) + , SYSTEM_CHASSIS_FMT + , ohep->Entry[i].EntityLocation); + if (!strcmp(entityRoot, rootName)) { + foundRoot = 1; + } + break; + + default: + foundOther = 1; + break; + } + } + + /* We are only interested in specific entities on specific device */ + if (foundRoot) { + if (foundMgmt && !(foundBlade||foundExp||foundOther)) { + rc = OHRES_MGMTMOD; + } else if (!(foundMgmt||foundBlade||foundExp||foundOther)) { + rc = OHRES_BLADECENT; + } else if (foundBlade && !foundExp) { + rc = OHRES_BLADE; + } + } + + return rc; +} + + +static int +get_sensor_num(SaHpiSessionIdT ohsession, SaHpiResourceIdT ohresid) +{ + SaErrorT ohrc = SA_OK; + SaHpiEntryIdT ohnextid; + SaHpiRdrT ohRDR; + + ohnextid = SAHPI_FIRST_ENTRY; + do { + ohrc = saHpiRdrGet(ohsession, ohresid, ohnextid + , &ohnextid, &ohRDR); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get RDR entry in %s (%d)" + , __FUNCTION__, ohrc); + } else if (ohRDR.RdrType == SAHPI_SENSOR_RDR) { + return ohRDR.RdrTypeUnion.SensorRec.Num; + } + } while (ohrc == SA_OK && ohnextid != SAHPI_LAST_ENTRY); + + return 0; +} + + +/* + * Get RPT update count + * Loop through all RPT entries + * If entry is BladeCenter, save resource ID in dev->ohdevid + * If entry is MgmtMod and has sensor, save resource ID in dev->ohsensid + * and sensor number in dev->ohsensnum + * If entry is blade, save blade_info and add to dev->hostlist + * Get RPT update count + * If RPT update count changed since start of loop, repeat loop + * Save RPT update count in dev->ohrptcnt + * + * Note that not only does this function update hostlist, it also + * updates ohrptcnt, ohdevid, ohsensid and ohsensnum. However, with + * this logic it does not need to be called again until the RPT update + * count changes. + */ + +static int +get_bladehpi_hostlist(struct pluginDevice *dev) +{ + struct blade_info * bi; + SaErrorT ohrc; + SaHpiEntryIdT ohnextid; + SaHpiRptEntryT ohRPT; + SaHpiDomainInfoT ohdi; + SaHpiUint32T ohupdate; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called, dev->device=%s" + , __FUNCTION__, dev->device); + } + + if (dev->device == NULL || *dev->device == 0) { + LOG(PIL_CRIT, "Unconfigured stonith object in %s" + , __FUNCTION__); + return S_BADCONFIG; + } + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + return S_BADCONFIG; + } + +try_again: + ohupdate = ohdi.RptUpdateCount; + dev->ohdevid = dev->ohsensid = dev->ohsensnum = 0; + ohnextid = SAHPI_FIRST_ENTRY; + do { + char blname[SAHPI_MAX_TEXT_BUFFER_LENGTH]; + int blnum; + + ohrc = saHpiRptEntryGet(dev->ohsession, ohnextid + , &ohnextid, &ohRPT); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get RPT entry in %s (%d)" + , __FUNCTION__, ohrc); + free_bladehpi_hostlist(dev); + return S_BADCONFIG; + } + + switch (get_resource_type(dev->device, &ohRPT)) { + case OHRES_BLADECENT: + dev->ohdevid = ohRPT.ResourceId; + + if (Debug) { + LOG(PIL_DEBUG, "BladeCenter '%s' has id %d" + , (char*)ohRPT.ResourceTag.Data + , dev->ohdevid); + } + break; + + case OHRES_MGMTMOD: + if (ohRPT.ResourceCapabilities&SAHPI_CAPABILITY_SENSOR){ + dev->ohsensnum = get_sensor_num(dev->ohsession + , ohRPT.ResourceId); + + if (dev->ohsensnum) { + dev->ohsensid = ohRPT.ResourceId; + + if (Debug) { + LOG(PIL_DEBUG + , "MgmtModule '%s' has id %d " + "with sensor #%d" + , (char*)ohRPT.ResourceTag.Data + , dev->ohsensid + , dev->ohsensnum); + } + } + } + break; + + case OHRES_BLADE: + if ((bi = (struct blade_info *) + MALLOC(sizeof(struct blade_info))) == NULL) { + LOG(PIL_CRIT, "Out of memory in %s" + , __FUNCTION__); + free_bladehpi_hostlist(dev); + return S_OOPS; + } + + /* + * New format consists of "Blade N - name" while older + * format consists only of "name"; we only need to + * stash name because ResourceID is the important info + */ + if (sscanf((char*)ohRPT.ResourceTag.Data, "Blade %d - %s" + , &blnum, blname) == 2) { + bi->name = STRDUP(blname); + } else { + bi->name = STRDUP((char*)ohRPT.ResourceTag.Data); + } + if (bi->name == NULL) { + LOG(PIL_CRIT, "Out of memory for strdup in %s" + , __FUNCTION__); + free_bladehpi_hostlist(dev); + return S_OOPS; + } + + bi->resourceId = ohRPT.ResourceId; + bi->resourceCaps = ohRPT.ResourceCapabilities; + dev->hostlist = g_list_append(dev->hostlist, bi); + + if (Debug) { + LOG(PIL_DEBUG, "Blade '%s' has id %d, caps %x" + , bi->name, bi->resourceId, bi->resourceCaps); + } + break; + } + } while (ohrc == SA_OK && ohnextid != SAHPI_LAST_ENTRY); + + ohrc = saHpiDomainInfoGet(dev->ohsession, &ohdi); + if (ohrc != SA_OK) { + LOG(PIL_CRIT, "Unable to get domain info in %s (%d)" + , __FUNCTION__, ohrc); + free_bladehpi_hostlist(dev); + return S_BADCONFIG; + } + + if (ohupdate != ohdi.RptUpdateCount) { + free_bladehpi_hostlist(dev); + if(Debug){ + LOG(PIL_DEBUG, "Looping through entries again," + " count changed from %d to %d" + , ohupdate, ohdi.RptUpdateCount); + } + goto try_again; + } + + dev->ohrptcnt = ohupdate; + + return S_OK; +} + + +static void +free_bladehpi_hostlist(struct pluginDevice *dev) +{ + if (dev->hostlist) { + GList *node; + while (NULL != (node = g_list_first(dev->hostlist))) { + dev->hostlist = + g_list_remove_link(dev->hostlist, node); + FREE(((struct blade_info *)node->data)->name); + FREE(node->data); + g_list_free(node); + } + dev->hostlist = NULL; + } + dev->ohdevid = dev->ohsensid = dev->ohsensnum = 0; +} + + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} diff --git a/lib/plugins/stonith/cyclades.c b/lib/plugins/stonith/cyclades.c new file mode 100644 index 0000000..6744cd4 --- /dev/null +++ b/lib/plugins/stonith/cyclades.c @@ -0,0 +1,650 @@ +/* + * Stonith module for Cyclades AlterPath PM + * Bases off the SSH plugin + * + * Copyright (c) 2004 Cyclades corp. + * + * Author: Jon Taylor + * + * Rewritten from scratch using baytech.c structure and code + * and currently maintained by + * Marcelo Tosatti + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#define DEVICE "Cyclades AlterPath PM" + +#define DOESNT_USE_STONITHSCANLINE + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN cyclades +#define PIL_PLUGIN_S "cyclades" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +#include "stonith_signal.h" + +static StonithPlugin * cyclades_new(const char *); +static void cyclades_destroy(StonithPlugin *); +static int cyclades_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * cyclades_get_confignames(StonithPlugin * s); +static const char * cyclades_get_info(StonithPlugin * s, int InfoType); +static int cyclades_status(StonithPlugin *); +static int cyclades_reset_req(StonithPlugin * s, int request, const char * host); +static char ** cyclades_hostlist(StonithPlugin *); + + + +static struct stonith_ops cycladesOps ={ + cyclades_new, /* Create new STONITH object */ + cyclades_destroy, /* Destroy STONITH object */ + cyclades_get_info, /* Return STONITH info string */ + cyclades_get_confignames, /* Return STONITH config vars */ + cyclades_set_config, /* set configuration from vars */ + cyclades_status, /* Return STONITH device status */ + cyclades_reset_req, /* Request a reset */ + cyclades_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &cycladesOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Cyclades STONITH device + * + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char * device; + char * user; + + int serial_port; + + /* pid of ssh client process and its in/out file descriptors */ + pid_t pid; + int rdfd, wrfd; +}; + +static struct Etoken StatusOutput[] = { + { "Outlet\t\tName\t\tStatus\t\tUsers\t\tInterval (s)", 1, 0}, + { "Outlet\tName\t\t\tStatus\t\tInterval (s)\tUsers", 2, 0}, + { "Outlet Name Status Post-on Delay(s)", 3, 0}, + { NULL, 0, 0} +}; + +static struct Etoken CRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + + +/* Commands of PM devices */ +static char status_all[] = "status all"; +static char cycle[] = "cycle"; + +static int CYC_robust_cmd(struct pluginDevice *, char *); + +static const char * pluginid = "CycladesDevice-Stonith"; +static const char * NOTpluginID = "Cyclades device has been destroyed"; + +#define MAX_OUTLETS 128 + +#define ST_SERIALPORT "serialport" + +#define ZEROEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(0); \ + } + +#define RESETEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) { \ + FREE(outletstr); \ + return(errno == ETIMEDOUT \ + ? S_RESETFAIL : S_OOPS); \ + } \ + } + +#include "stonith_config_xml.h" + +#define XML_SERIALPORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_SERIALPORT \ + XML_PARM_SHORTDESC_END + +#define XML_SERIALPORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The serial port of the IPDU which can powercycle the node" \ + XML_PARM_LONGDESC_END + +#define XML_SERIALPORT_PARM \ + XML_PARAMETER_BEGIN(ST_SERIALPORT, "string", "1", "0") \ + XML_SERIALPORT_SHORTDESC \ + XML_SERIALPORT_LONGDESC \ + XML_PARAMETER_END + +static const char *cycladesXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_SERIALPORT_PARM + XML_PARAMETERS_END; + +static int +CYCScanLine(struct pluginDevice *sd, int timeout, char * buf, int max) +{ + if (EXPECT_TOK(sd->rdfd, CRNL, timeout, buf, max, Debug) < 0) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + return(S_OOPS); + } + return(S_OK); +} + +static int +cyclades_status(StonithPlugin *s) +{ + struct pluginDevice *sd; + char *cmd = status_all; + + ERRIFNOTCONFIGED(s,S_OOPS); + + sd = (struct pluginDevice*) s; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return(S_OOPS); + } + + EXPECT(sd->rdfd, StatusOutput, 50); + + return(S_OK); +} + +static int CYC_run_command(struct pluginDevice *sd, char *cmd) +{ + char SshCommand[MAX_OUTLETS*4]; + + snprintf(SshCommand, sizeof(SshCommand), + "exec ssh -q %s@%s /bin/pmCommand %d %s 2>/dev/null", + sd->user, sd->device, sd->serial_port, cmd); + + sd->pid = STARTPROC(SshCommand, &sd->rdfd, &sd->wrfd); + + if (sd->pid <= 0) { + return(S_OOPS); + } + + return(S_OK); +} + +static int +CYC_robust_cmd(struct pluginDevice *sd, char *cmd) +{ + int rc = S_OOPS; + int i; + + for (i=0; i < 20 && rc != S_OK; i++) { + + if (sd->pid > 0) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + } + + if (CYC_run_command(sd, cmd) != S_OK) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + continue; + } + rc = S_OK; + } + + return rc; +} + +#define MAXSAVE 512 +static int CYCNametoOutlet(struct pluginDevice *sd, const char *host, int *outlets, int maxoutlet) +{ + char *cmd = status_all; + char savebuf[MAXSAVE]; + int err; + int outlet, numoutlet = 0; + char name[17], locked[11], on[4]; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return 0; + } + + ZEROEXPECT(sd->rdfd, StatusOutput, 50); + + ZEROEXPECT(sd->rdfd, CRNL, 50); + + do { + + memset(savebuf, 0, sizeof(savebuf)); + memset(name, 0, sizeof(name)); + memset(locked, 0, sizeof(locked)); + memset(on, 0, sizeof(on)); + + err = CYCScanLine(sd, 2, savebuf, sizeof(savebuf)); + + if ((err == S_OK) && + (sscanf(savebuf,"%3d %16s %10s %3s", &outlet, + name, locked, on) > 0)) { + if (!strncasecmp(name, host, strlen(host))) { + if (numoutlet >= maxoutlet) { + LOG(PIL_CRIT, "too many outlets"); + return 0; + } + outlets[numoutlet++] = outlet; + } + } + + } while (err == S_OK); + + return (numoutlet); +} + + +/* + * Return the list of hosts configured for this Cyclades device + */ + +static char ** +cyclades_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + char *cmd = status_all; + char savebuf[MAXSAVE]; + int err, i; + int outlet; + int numnames = 0; + char name[17], locked[11], on[4]; + char *NameList[MAX_OUTLETS]; + char **ret = NULL; + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return (NULL); + } + + memset(savebuf, 0, sizeof(savebuf)); + + NULLEXPECT(sd->rdfd, StatusOutput, 50); + + NULLEXPECT(sd->rdfd, CRNL, 50); + + do { + char *nm; + + memset(savebuf, 0, sizeof(savebuf)); + memset(name, 0, sizeof(name)); + memset(locked, 0, sizeof(locked)); + memset(on, 0, sizeof(on)); + + err = CYCScanLine(sd, 2, savebuf, sizeof(savebuf)); + + if ((err == S_OK) && + (sscanf(savebuf,"%3d %16s %10s %3s", &outlet, + name, locked, on) > 0)) { + nm = (char *) STRDUP (name); + if (!nm) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + numnames++; + NameList[numnames] = NULL; + } + + } while (err == S_OK); + + if (numnames) { + + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + } else { + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + return (ret); + } + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; irdfd, exp, 50); + } + + LOG(PIL_DEBUG, "Power to host %s turned %s", unitid, onoff); + + FREE(outletstr); + return (S_OK); +} + +static int cyclades_reset(struct pluginDevice *sd, int *outlet, int numoutlet, + const char *unitid) +{ + char cmd[MAX_OUTLETS*4], expstring[64]; + struct Etoken exp[] = {{NULL, 0, 0}, {NULL, 0, 0}}; + char *outletstr; + int i; + + memset(cmd, 0, sizeof(cmd)); + + outletstr = cyclades_outletstr(outlet, numoutlet); + if (outletstr == NULL) { + LOG(PIL_CRIT, "out of memory"); + return (S_OOPS); + } + snprintf(cmd, sizeof(cmd), "%s %s", cycle, outletstr); + + LOG(PIL_INFO, "Host %s being rebooted.", unitid); + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run cycle command"); + FREE(outletstr); + return(S_OOPS); + } + + for (i = 0; i < numoutlet; i++) { + memset(expstring, 0, sizeof(expstring)); + snprintf(expstring, sizeof(expstring) + , "%d: Outlet turned off.", outlet[i]); + + exp[0].string = expstring; + RESETEXPECT(sd->rdfd, exp, 50); + } + + for (i = 0; i < numoutlet; i++) { + memset(expstring, 0, sizeof(expstring)); + snprintf(expstring, sizeof(expstring) + , "%d: Outlet turned on.", outlet[i]); + + exp[0].string = expstring; + RESETEXPECT(sd->rdfd, exp, 50); + } + + FREE(outletstr); + return (S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +cyclades_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice *sd; + int rc = 0; + int numoutlet, outlets[MAX_OUTLETS]; + + ERRIFNOTCONFIGED(s,S_OOPS); + + sd = (struct pluginDevice*) s; + + numoutlet = CYCNametoOutlet(sd, host, outlets, MAX_OUTLETS); + + if (!numoutlet) { + LOG(PIL_CRIT, "Unknown host %s to Cyclades PM", host); + return (S_OOPS); + } + + + switch (request) { + case ST_POWERON: + case ST_POWEROFF: + rc = cyclades_onoff(sd, outlets, numoutlet, host, request); + break; + + case ST_GENERIC_RESET: + rc = cyclades_reset(sd, outlets, numoutlet, host); + break; + default: + rc = S_INVAL; + break; + } + + return rc; +} + +static const char * const * +cyclades_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_SERIALPORT, NULL}; + return ret; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +cyclades_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy[] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_SERIALPORT, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->device = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->serial_port = atoi(namestocopy[2].s_value); + FREE(namestocopy[2].s_value); + + return(S_OK); +} + +static const char * +cyclades_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice * sd; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + sd = (struct pluginDevice*) s; + + switch (reqtype) { + case ST_DEVICEID: /* What type of device? */ + /* FIXME: could inform the exact PM model */ + ret = sd->idinfo; + break; + + case ST_DEVICENAME: /* What particular device? */ + ret = sd->device; + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "Cyclades AlterPath PM " + "series power switches (via TS/ACS/KVM)."; + break; + + case ST_DEVICEURL: /* Manufacturer's web site */ + ret = "http://www.cyclades.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = cycladesXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Cyclades Stonith destructor... + */ +static void +cyclades_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd; + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice*) s; + + sd->pluginid = NOTpluginID; + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + if (sd->device != NULL) { + FREE(sd->device); + sd->device = NULL; + } + if (sd->user != NULL) { + FREE(sd->user); + sd->user = NULL; + } + + FREE(sd); +} + +/* Create a new cyclades Stonith device */ +static StonithPlugin * +cyclades_new(const char *plugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->pid = -1; + sd->rdfd = -1; + sd->wrfd = -1; + sd->idinfo = DEVICE; + sd->sp.s_ops = &cycladesOps; + + return &(sd->sp); /* same as sd */ +} diff --git a/lib/plugins/stonith/drac3.c b/lib/plugins/stonith/drac3.c new file mode 100644 index 0000000..95be775 --- /dev/null +++ b/lib/plugins/stonith/drac3.c @@ -0,0 +1,359 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda + * Tiny bits Copyright 2005 International Business Machines + * Significantly Mangled by Sun Jiang Dong , IBM, 2005 + * + * (Using snippets of other stonith modules code) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define DEVICE "Dell DRACIII Card" +#include "stonith_plugin_common.h" + +#include +#include "drac3_command.h" + +#define PIL_PLUGIN drac3 +#define PIL_PLUGIN_S "drac3" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include +#include "stonith_signal.h" + +static StonithPlugin * drac3_new(const char *); +static void drac3_destroy(StonithPlugin *); +static const char * const * drac3_get_confignames(StonithPlugin *); +static int drac3_set_config(StonithPlugin *, StonithNVpair *); +static const char * drac3_getinfo(StonithPlugin * s, int InfoType); +static int drac3_status(StonithPlugin * ); +static int drac3_reset_req(StonithPlugin * s, int request, const char * host); +static char ** drac3_hostlist(StonithPlugin *); + +static struct stonith_ops drac3Ops ={ + drac3_new, /* Create new STONITH object */ + drac3_destroy, /* Destroy STONITH object */ + drac3_getinfo, /* Return STONITH info string */ + drac3_get_confignames, /* Return configuration parameters */ + drac3_set_config, /* Set configuration */ + drac3_status, /* Return STONITH device status */ + drac3_reset_req, /* Request a reset */ + drac3_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &drac3Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define BUFLEN 1024 +#define ST_HOST "host" + +struct pluginDevice { + StonithPlugin sp; + const char *pluginid; + const char *idinfo; + CURL *curl; + char *host; + char *user; + char *pass; +}; + +static const char *pluginid = "Dell-DRACIII-Stonith"; +static const char *NOTpluginID = "Dell DRACIII device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_HOST_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_HOST \ + XML_PARM_SHORTDESC_END + +#define XML_HOST_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hostname of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_HOST_PARM \ + XML_PARAMETER_BEGIN(ST_HOST, "string", "1", "1") \ + XML_HOST_SHORTDESC \ + XML_HOST_LONGDESC \ + XML_PARAMETER_END + +static const char *drac3XML = + XML_PARAMETERS_BEGIN + XML_HOST_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/* ------------------------------------------------------------------ */ +/* STONITH PLUGIN API */ +/* ------------------------------------------------------------------ */ +static StonithPlugin * +drac3_new(const char *subplugin) +{ + struct pluginDevice *drac3d = ST_MALLOCT(struct pluginDevice); + + if (drac3d == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(drac3d, 0, sizeof(*drac3d)); + drac3d->pluginid = pluginid; + drac3d->curl = curl_easy_init(); + drac3InitCurl(drac3d->curl); + drac3d->host = NULL; + drac3d->user = NULL; + drac3d->pass = NULL; + drac3d->idinfo = DEVICE; + drac3d->sp.s_ops = &drac3Ops; + return (&(drac3d->sp)); +} + +/* ------------------------------------------------------------------ */ +static void +drac3_destroy(StonithPlugin * s) +{ + struct pluginDevice *drac3d; + + VOIDERRIFWRONGDEV(s); + + drac3d = (struct pluginDevice *) s; + + drac3d->pluginid = NOTpluginID; + + /* release curl connection */ + if (drac3d->curl != NULL) { + drac3Logout(drac3d->curl, drac3d->host); + curl_easy_cleanup(drac3d->curl); + drac3d->curl = NULL; + } + + if (drac3d->host != NULL) { + FREE(drac3d->host); + drac3d->host = NULL; + } + if (drac3d->user != NULL) { + FREE(drac3d->user); + drac3d->user = NULL; + } + if (drac3d->pass != NULL) { + FREE(drac3d->pass); + drac3d->pass = NULL; + } + + /* release stonith-object itself */ + FREE(drac3d); +} + +/* ------------------------------------------------------------------ */ +static const char * const * +drac3_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_HOST, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +/* ------------------------------------------------------------------ */ +static int +drac3_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_HOST, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->host = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->pass = namestocopy[2].s_value; + + return(S_OK); +} + +/* ------------------------------------------------------------------ */ +const char * +drac3_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *drac3d; + const char *ret = NULL; + + ERRIFWRONGDEV(s,NULL); + + drac3d = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = drac3d->idinfo; + break; + case ST_DEVICENAME: + ret = drac3d->host; + break; + case ST_DEVICEDESCR: + ret = "Dell DRACIII (via HTTPS)\n" + "The Dell Remote Access Controller accepts XML " + "commands over HTTPS"; + break; + case ST_DEVICEURL: + ret = "http://www.dell.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = drac3XML; + break; + default: + ret = NULL; + break; + } + + return(ret); +} + +/* ------------------------------------------------------------------ */ +int +drac3_status(StonithPlugin *s) +{ + struct pluginDevice *drac3d; + + ERRIFNOTCONFIGED(s,S_OOPS); + + drac3d = (struct pluginDevice *) s; + + if (drac3VerifyLogin(drac3d->curl, drac3d->host)) { + if (drac3Login(drac3d->curl, drac3d->host, + drac3d->user, drac3d->pass)) { + LOG(PIL_CRIT, "%s: cannot log into %s at %s", + __FUNCTION__, + drac3d->idinfo, + drac3d->host); + return(S_ACCESS); + } + } + + if (drac3GetSysInfo(drac3d->curl, drac3d->host)) { + return(S_ACCESS); + }else{ + return(S_OK); + } +} + +/* ------------------------------------------------------------------ */ +int +drac3_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *drac3d; + int rc = S_OK; + + ERRIFNOTCONFIGED(s,S_OOPS); + + drac3d = (struct pluginDevice *) s; + + if (strcasecmp(host, drac3d->host)) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , drac3d->idinfo, host); + return(S_BADHOST); + } + + if (drac3VerifyLogin(drac3d->curl, drac3d->host)) { + if (drac3Login(drac3d->curl, drac3d->host, + drac3d->user, drac3d->pass)) { + LOG(PIL_CRIT, "%s: cannot log into %s at %s", + __FUNCTION__, + drac3d->idinfo, + drac3d->host); + return(S_ACCESS); + } + } + + switch(request) { +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + case ST_POWEROFF: + /* TODO... */ +#endif + case ST_GENERIC_RESET: + if (drac3PowerCycle(drac3d->curl, drac3d->host)) + rc = S_ACCESS; + break; + default: + rc = S_INVAL; + break; + } + + return(rc); +} + +/* ------------------------------------------------------------------ */ +char ** +drac3_hostlist(StonithPlugin * s) +{ + struct pluginDevice *drac3d; + char **hl; + + ERRIFNOTCONFIGED(s,NULL); + + drac3d = (struct pluginDevice *) s; + + hl = OurImports->StringToHostList(drac3d->host); + if (hl == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + } else { + strdown(hl[0]); + } + + return(hl); +} diff --git a/lib/plugins/stonith/drac3_command.c b/lib/plugins/stonith/drac3_command.c new file mode 100644 index 0000000..4d9002d --- /dev/null +++ b/lib/plugins/stonith/drac3_command.c @@ -0,0 +1,342 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "drac3_command.h" +#include "drac3_hash.h" + +#define BUFLEN 1024 /* buffer */ +#define SBUFLEN 256 /* small buffer */ +#define MD5LEN 16 /* md5 buffer */ + +#define DEBUG 0 + +/* Hardcoded XML commands and response codes */ +#define CMD_POWERCYCLE "powercycle\n" +#define CMD_GETSYSINFO "getsysinfo -A\n" +#define RC_OK "0x0\n" + +struct Chunk { + char *memory; + size_t size; +}; + +/* prototypes */ +int xmlGetXPathString (const char *str, const char * expr, char * rc, const int len); +size_t writeFunction (void *ptr, size_t size, size_t nmemb, void *data); + + +/* ---------------------------------------------------------------------- * + * XML PARSING * + * ---------------------------------------------------------------------- */ + +int +xmlGetXPathString (const char *str, + const char * expr, + char * rc, + const int len) +{ + xmlDocPtr doc; + xmlNodePtr cur; + xmlXPathContextPtr ctx; + xmlXPathObjectPtr path; + xmlChar *xmlRC; + + if (!strchr(str,'<')) { + fprintf(stderr,"%s malformed\n", str); + rc[0] = 0x00; + return(1); + } + + doc = xmlParseMemory(str, strlen(str)); + xmlXPathInit(); + ctx = xmlXPathNewContext(doc); + path = xmlXPathEvalExpression((const xmlChar *)expr, ctx); + cur = path->nodesetval->nodeTab[0]; + + if (cur != NULL) { + xmlRC = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); + snprintf(rc, len, "%s\n", xmlRC); + xmlFree(xmlRC); + xmlFreeDoc(doc); + xmlCleanupParser(); + xmlXPathFreeObject(path); + xmlXPathFreeContext(ctx); + + return(0); + } else { + fprintf(stderr,"error in obtaining XPath %s\n", expr); + xmlFreeDoc(doc); + xmlCleanupParser(); + xmlXPathFreeObject(path); + xmlXPathFreeContext(ctx); + + rc[0] = 0x00; + return(1); + } +} + + +/* ---------------------------------------------------------------------- * + * CURL CALLBACKS * + * ---------------------------------------------------------------------- */ + +size_t +writeFunction (void *ptr, size_t size, size_t nmemb, void *data) +{ + + register int realsize = size * nmemb; + struct Chunk *mem = (struct Chunk *)data; + + mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1); + if (mem->memory) { + memcpy(&(mem->memory[mem->size]), ptr, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + } + return realsize; +} + + +/* ---------------------------------------------------------------------- * + * DRAC3 CURL COMMANDS * + * ---------------------------------------------------------------------- */ + +int +drac3InitCurl (CURL *curl) +{ +#ifdef CURLOPT_NOSIGNAL + if (curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1)) return(1); +#endif + if (curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30)) return(1); + if (curl_easy_setopt(curl, CURLOPT_VERBOSE, 0)) return(1); + if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction)) return(1); + if (curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/dev/null")) return(1); + if (curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0)) return(1); + if (curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0)) return(1); + return(0); +} + +int +drac3Login (CURL *curl, + const char *host, + const char *user, + const char *pass) +{ + char url[BUFLEN]; + char chall[BUFLEN]; + char token[BUFLEN]; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) + return(1); + + /* ask for challenge */ + snprintf(url, BUFLEN, "https://%s/cgi/challenge", host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) + return(1); + if (curl_easy_perform(curl)) + return(1); + + /* extract challenge */ + status = xmlGetXPathString(chunk.memory, "//CHALLENGE", chall, BUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + /* calculate authToken */ + drac3AuthHash(chall, pass, token, BUFLEN); + + if (DEBUG) printf("T: %s\n", token); + + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + if (status) return(1); + chunk.memory = NULL; + chunk.size = 0; + + /* sends authToken */ + snprintf(url, BUFLEN, "https://%s/cgi/login?user=%s&hash=%s", + host, + user, + token); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) + return(1); + if (curl_easy_perform(curl)) + return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + +int +drac3PowerCycle (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char cmd[]=CMD_POWERCYCLE; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/bin", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cmd)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + + +int +drac3GetSysInfo (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char cmd[]=CMD_GETSYSINFO; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/bin", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_setopt(curl, CURLOPT_POSTFIELDS, cmd)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + + +int +drac3Logout (CURL *curl, + const char *host) +{ + char url[BUFLEN]; + char rc[SBUFLEN]; + int status; + struct Chunk chunk; + + chunk.memory = NULL; + chunk.size = 0; + if (curl_easy_setopt(curl, CURLOPT_FILE, (void *)&chunk)) return(1); + + snprintf(url, BUFLEN, "https://%s/cgi/logout", + host); + url[BUFLEN-1] = 0x00; + + if (curl_easy_setopt(curl, CURLOPT_URL, url)) return(1); + if (curl_easy_perform(curl)) return(1); + + if (DEBUG) printf("R: %s\n", chunk.memory); + status = xmlGetXPathString(chunk.memory, "//RC", rc, SBUFLEN); + if (status) { + free(chunk.memory); + return(1); + } + if (DEBUG) printf("RC: %s\n", rc); + + status = (strcmp(rc, RC_OK) == 0) ? 0 : 1; + free(chunk.memory); + return(status); +} + +int +drac3VerifyLogin (CURL *curl, + const char *host) +{ + /*We try to do a GetSysInfo */ + return(drac3GetSysInfo (curl, host)); +} + +/* -------------------------------------------------------------------- */ + diff --git a/lib/plugins/stonith/drac3_command.h b/lib/plugins/stonith/drac3_command.h new file mode 100644 index 0000000..cd03e15 --- /dev/null +++ b/lib/plugins/stonith/drac3_command.h @@ -0,0 +1,29 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +int drac3InitCurl (CURL *curl); +int drac3Login (CURL *curl, const char *host, const char *user, const char *pass); +int drac3PowerCycle (CURL *curl, const char *host); +int drac3GetSysInfo (CURL *curl, const char *host); +int drac3Logout (CURL *curl, const char *host); +int drac3VerifyLogin (CURL *curl, const char *host); + diff --git a/lib/plugins/stonith/drac3_hash.c b/lib/plugins/stonith/drac3_hash.c new file mode 100644 index 0000000..605a126 --- /dev/null +++ b/lib/plugins/stonith/drac3_hash.c @@ -0,0 +1,106 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include +#include +#include +#include +#include + +#include "drac3_hash.h" + +#define BUFLEN 1024 /* buffer */ +#define SBUFLEN 256 /* small buffer */ +#define MD5LEN 16 /* md5 buffer */ + +/* Hash functions for DRAC3 authentication */ + +guint16 +drac3Crc16(const char *str, + const int l) { + + int i,j; + guint16 crc = 0; + + for (i=0; i challBytes */ + memset(challBytes, 0, MD5LEN); + chall_dup = g_strdup(chall); + if (chall_dup[strlen(chall_dup) - 1] == '\n' ) { + chall_dup[strlen(chall_dup) - 1] = '\0'; + } + base64_to_binary(chall_dup, strlen(chall_dup), challBytes, MD5LEN); + + /* gets MD5 from pass -> passMD5 */ + MD5((const unsigned char *)pass, strlen(pass), (unsigned char *)passMD5); + + /* calculate challBytes and passMD5 xor -> xorBytes */ + for (i=0; i xorBytesMD5 */ + MD5((unsigned char *)xorBytes, MD5LEN, (unsigned char *)xorBytesMD5); + + /* calculate xorBytesMD5 crc16 */ + crc = drac3Crc16(xorBytesMD5, MD5LEN); + + /* joins xorBytesMD5 and crc16 -> response */ + memcpy(response, xorBytesMD5, MD5LEN); + memcpy(response+MD5LEN, &crc, 2); + + /* calculate response base64 -> responseb64 */ + memset(responseb64, 0, SBUFLEN); + binary_to_base64(response, MD5LEN+2, responseb64, SBUFLEN); + + /* assuring null-termination */ + responseb64[SBUFLEN-1]=0x00; + + snprintf(token, len, "%s", responseb64); + token[len-1]=0x00; +} diff --git a/lib/plugins/stonith/drac3_hash.h b/lib/plugins/stonith/drac3_hash.h new file mode 100644 index 0000000..fab2f58 --- /dev/null +++ b/lib/plugins/stonith/drac3_hash.h @@ -0,0 +1,28 @@ +/* + * Stonith module for Dell DRACIII (Dell Remote Access Card) + * + * Copyright (C) 2003 Alfa21 Outsourcing + * Copyright (C) 2003 Roberto Moreda + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include + +guint16 drac3Crc16(const char *str, const int l); +void drac3AuthHash(const char *chall, const char *pass, char *token, int len); + diff --git a/lib/plugins/stonith/external.c b/lib/plugins/stonith/external.c new file mode 100644 index 0000000..da03665 --- /dev/null +++ b/lib/plugins/stonith/external.c @@ -0,0 +1,868 @@ +/* + * Stonith module for EXTERNAL Stonith device + * + * Copyright (c) 2001 SuSE Linux AG + * Portions Copyright (c) 2004, tummy.com, ltd. + * + * Based on ssh.c, Authors: Joachim Gleissner , + * Lars Marowsky-Bree + * Modified for external.c: Scott Kleihege + * Reviewed, tested, and config parsing: Sean Reifschneider + * And overhauled by Lars Marowsky-Bree , so the circle + * closes... + * Mangled by Zhaokai , IBM, 2005 + * Changed to allow full-featured external plugins by Dave Blaschke + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN external +#define PIL_PLUGIN_S "external" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#include + +static StonithPlugin * external_new(const char *); +static void external_destroy(StonithPlugin *); +static int external_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * external_get_confignames(StonithPlugin *); +static const char * external_getinfo(StonithPlugin * s, int InfoType); +static int external_status(StonithPlugin * ); +static int external_reset_req(StonithPlugin * s, int request, const char * host); +static char ** external_hostlist(StonithPlugin *); + +static struct stonith_ops externalOps ={ + external_new, /* Create new STONITH object */ + external_destroy, /* Destroy STONITH object */ + external_getinfo, /* Return STONITH info string */ + external_get_confignames, /* Return STONITH info string */ + external_set_config, /* Get configuration from NVpairs */ + external_status, /* Return STONITH device status */ + external_reset_req, /* Request a reset */ + external_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &externalOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * EXTERNAL STONITH device + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + GHashTable * cmd_opts; + char * subplugin; + char ** confignames; + char * outputbuf; +}; + +static const char * pluginid = "ExternalDevice-Stonith"; +static const char * NOTpluginID = "External device has been destroyed"; + +/* Prototypes */ + +/* Run the command with op and return the exit status + the output + * (NULL -> discard output) */ +static int external_run_cmd(struct pluginDevice *sd, const char *op, + char **output); +/* Just free up the configuration and the memory, if any */ +static void external_unconfig(struct pluginDevice *sd); + +static int +external_status(StonithPlugin *s) +{ + struct pluginDevice * sd; + const char * op = "status"; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + rc = external_run_cmd(sd, op, NULL); + if (rc != 0) { + LOG(PIL_WARN, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static char ** +external_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + const char * op = "gethosts"; + int rc, i, namecount; + char ** ret; + char * output = NULL; + char * tmp; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + rc = external_run_cmd(sd, op, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return NULL; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + + if (!output) { + LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist", + __FUNCTION__, sd->subplugin, op); + return NULL; + } + + namecount = get_num_tokens(output); + ret = MALLOC((namecount+1)*sizeof(char *)); + if (!ret) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + FREE(output); + return NULL; + } + memset(ret, 0, (namecount+1)*sizeof(char *)); + + /* White-space split the output here */ + i = 0; + tmp = strtok(output, WHITESPACE); + while (tmp != NULL) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s host %s", + __FUNCTION__, sd->subplugin, tmp); + } + ret[i] = STRDUP(tmp); + if (!ret[i]) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + FREE(output); + stonith_free_hostlist(ret); + return NULL; + } + i++; + tmp = strtok(NULL, WHITESPACE); + } + + FREE(output); + + if (i == 0) { + LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist", + __FUNCTION__, sd->subplugin, op); + stonith_free_hostlist(ret); + ret = NULL; + } + + return(ret); +} + +static int +external_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice * sd; + const char * op; + int rc; + char * args1and2; + int argslen; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "Host external-reset initiating on %s", host); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + switch (request) { + case ST_GENERIC_RESET: + op = "reset"; + break; + + case ST_POWEROFF: + op = "off"; + break; + + case ST_POWERON: + op = "on"; + break; + + default: + LOG(PIL_CRIT, "%s: Unknown stonith request %d", + __FUNCTION__, request); + return S_OOPS; + break; + } + + argslen = strlen(op) + strlen(host) + 2 /* 1 for blank, 1 for EOS */; + args1and2 = (char *)MALLOC(argslen); + if (args1and2 == NULL) { + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + return S_OOPS; + } + rc = snprintf(args1and2, argslen, "%s %s", op, host); + if (rc <= 0 || rc >= argslen) { + FREE(args1and2); + return S_OOPS; + } + + rc = external_run_cmd(sd, args1and2, NULL); + FREE(args1and2); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' for host %s failed with rc %d", + __FUNCTION__, sd->subplugin, op, host, rc); + return S_RESETFAIL; + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + return S_OK; + } + +} + +static int +external_parse_config_info(struct pluginDevice* sd, StonithNVpair * info) +{ + char * key; + char * value; + StonithNVpair * nv; + + sd->cmd_opts = g_hash_table_new(g_str_hash, g_str_equal); + + /* TODO: Maybe treat "" as delimeters too so + * whitespace can be passed to the plugins... */ + for (nv = info; nv->s_name; nv++) { + if (!nv->s_name || !nv->s_value) { + continue; + } + + key = STRDUP(nv->s_name); + if (!key) { + goto err_mem; + } + value = STRDUP(nv->s_value); + if (!value) { + FREE(key); + goto err_mem; + } + g_hash_table_insert(sd->cmd_opts, key, value); + } + + return(S_OK); + +err_mem: + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + external_unconfig(sd); + + return(S_OOPS); +} + +static gboolean +let_remove_eachitem(gpointer key, gpointer value, gpointer user_data) +{ + if (key) { + FREE(key); + } + if (value) { + FREE(value); + } + return TRUE; +} + +static void +external_unconfig(struct pluginDevice *sd) { + if (sd->cmd_opts) { + g_hash_table_foreach_remove(sd->cmd_opts, + let_remove_eachitem, NULL); + g_hash_table_destroy(sd->cmd_opts); + sd->cmd_opts = NULL; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +external_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice * sd; + char ** p; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + /* make sure that command has not already been set */ + if (s->isconfigured) { + return(S_OOPS); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + if (sd->confignames == NULL) { + /* specified by name=value pairs, check required parms */ + if (external_get_confignames(s) == NULL) { + return(S_OOPS); + } + + for (p = sd->confignames; *p; p++) { + if (OurImports->GetValue(list, *p) == NULL) { + LOG(PIL_DEBUG, "Cannot get parameter %s from " + "StonithNVpair", *p); + } + } + } + + return external_parse_config_info(sd, list); +} + + +/* Only interested in regular files that are also executable */ +static int +exec_select(const struct dirent *dire) +{ + struct stat statf; + char filename[FILENAME_MAX]; + int rc; + + rc = snprintf(filename, FILENAME_MAX, "%s/%s", + STONITH_EXT_PLUGINDIR, dire->d_name); + if (rc <= 0 || rc >= FILENAME_MAX) { + return 0; + } + + if ((stat(filename, &statf) == 0) && + (S_ISREG(statf.st_mode)) && + (statf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) { + if (statf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_WARN, "Executable file %s ignored " + "(writable by group/others)", filename); + return 0; + }else{ + return 1; + } + } + + return 0; +} + +/* + * Return STONITH config vars + */ +static const char * const * +external_get_confignames(StonithPlugin* p) +{ + struct pluginDevice * sd; + const char * op = "getconfignames"; + int i, rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + sd = (struct pluginDevice *)p; + + if (sd->subplugin != NULL) { + /* return list of subplugin's required parameters */ + char *output = NULL, *pch; + int namecount; + + rc = external_run_cmd(sd, op, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return NULL; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_DEBUG, "plugin output: %s", output); + } + } + + namecount = get_num_tokens(output); + sd->confignames = (char **)MALLOC((namecount+1)*sizeof(char *)); + if (sd->confignames == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + if (output) { FREE(output); } + return NULL; + } + + /* now copy over confignames */ + pch = strtok(output, WHITESPACE); + for (i = 0; i < namecount; i++) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s configname %s", + __FUNCTION__, sd->subplugin, pch); + } + sd->confignames[i] = STRDUP(pch); + pch = strtok(NULL, WHITESPACE); + } + FREE(output); + sd->confignames[namecount] = NULL; + }else{ + /* return list of subplugins in external directory */ + struct dirent ** files = NULL; + int dircount; + + /* get the external plugin's confignames (list of subplugins) */ + dircount = scandir(STONITH_EXT_PLUGINDIR, &files, + SCANSEL_CAST exec_select, NULL); + if (dircount < 0) { + return NULL; + } + + sd->confignames = (char **)MALLOC((dircount+1)*sizeof(char *)); + if (!sd->confignames) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + + for (i = 0; i < dircount; i++) { + sd->confignames[i] = STRDUP(files[i]->d_name); + free(files[i]); + files[i] = NULL; + } + free(files); + sd->confignames[dircount] = NULL; + } + + return (const char * const *)sd->confignames; +} + +/* + * Return STONITH info string + */ +static const char * +external_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd; + char * output = NULL; + const char * op; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + sd = (struct pluginDevice *)s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + switch (reqtype) { + case ST_DEVICEID: + op = "getinfo-devid"; + break; + + case ST_DEVICENAME: + op = "getinfo-devname"; + break; + + case ST_DEVICEDESCR: + op = "getinfo-devdescr"; + break; + + case ST_DEVICEURL: + op = "getinfo-devurl"; + break; + + case ST_CONF_XML: + op = "getinfo-xml"; + break; + + default: + return NULL; + } + + rc = external_run_cmd(sd, op, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + } + sd->outputbuf = output; + return(output); + } + return(NULL); +} + +/* + * EXTERNAL Stonith destructor... + */ +static void +external_destroy(StonithPlugin *s) +{ + struct pluginDevice * sd; + char ** p; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginID; + external_unconfig(sd); + if (sd->confignames != NULL) { + for (p = sd->confignames; *p; p++) { + FREE(*p); + } + FREE(sd->confignames); + sd->confignames = NULL; + } + if (sd->subplugin != NULL) { + FREE(sd->subplugin); + sd->subplugin = NULL; + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + FREE(sd); +} + +/* Create a new external Stonith device */ +static StonithPlugin * +external_new(const char *subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + if (subplugin != NULL) { + sd->subplugin = STRDUP(subplugin); + if (sd->subplugin == NULL) { + FREE(sd); + return(NULL); + } + } + sd->sp.s_ops = &externalOps; + return &(sd->sp); +} + +static void +ext_add_to_env(gpointer key, gpointer value, gpointer user_data) +{ + if (setenv((char *)key, (char *)value, 1) != 0) { + LOG(PIL_CRIT, "%s: setenv failed.", __FUNCTION__); + } +} + +static void +ext_del_from_env(gpointer key, gpointer value, gpointer user_data) +{ + unsetenv((char *)key); +} + +#define LOGTAG_VAR "HA_LOGTAG" + +/* Run the command with op as command line argument(s) and return the exit + * status + the output */ +static int +external_run_cmd(struct pluginDevice *sd, const char *op, char **output) +{ + const int BUFF_LEN=4096; + char buff[BUFF_LEN]; + int read_len = 0; + int status, rc; + char * data = NULL; + FILE * file; + char cmd[FILENAME_MAX+64]; + struct stat buf; + int slen; + char *path, *new_path, *logtag, *savevar = NULL; + int new_path_len, logtag_len; + gboolean nodata; + + rc = snprintf(cmd, FILENAME_MAX, "%s/%s", + STONITH_EXT_PLUGINDIR, sd->subplugin); + if (rc <= 0 || rc >= FILENAME_MAX) { + LOG(PIL_CRIT, "%s: external command too long.", __FUNCTION__); + return -1; + } + + if (stat(cmd, &buf) != 0) { + LOG(PIL_CRIT, "%s: stat(2) of %s failed: %s", + __FUNCTION__, cmd, strerror(errno)); + return -1; + } + + if (!S_ISREG(buf.st_mode) + || (!(buf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { + LOG(PIL_CRIT, "%s: %s found NOT to be executable.", + __FUNCTION__, cmd); + return -1; + } + + if (buf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_CRIT, "%s: %s found to be writable by group/others, " + "NOT executing for security purposes.", + __FUNCTION__, cmd); + return -1; + } + + strcat(cmd, " "); + strcat(cmd, op); + + /* We only have a global environment to use here. So we add our + * options to it, and then later remove them again. */ + if (sd->cmd_opts) { + g_hash_table_foreach(sd->cmd_opts, ext_add_to_env, NULL); + } + + /* external plugins need path to ha_log.sh */ + path = getenv("PATH"); + if (strncmp(GLUE_SHARED_DIR,path,strlen(GLUE_SHARED_DIR))) { + new_path_len = strlen(path)+strlen(GLUE_SHARED_DIR)+2; + new_path = (char *)g_malloc(new_path_len); + snprintf(new_path, new_path_len, "%s:%s", GLUE_SHARED_DIR, path); + setenv("PATH", new_path, 1); + g_free(new_path); + } + + /* set the logtag appropriately */ + logtag_len = strlen(PIL_PLUGIN_S)+strlen(sd->subplugin)+2; + logtag = (char *)g_malloc(logtag_len); + snprintf(logtag, logtag_len, "%s/%s", PIL_PLUGIN_S, sd->subplugin); + if (getenv(LOGTAG_VAR)) { + savevar = g_strdup(getenv(LOGTAG_VAR)); + } + setenv(LOGTAG_VAR, logtag, 1); + g_free(logtag); + + if (Debug) { + LOG(PIL_DEBUG, "%s: Calling '%s'", __FUNCTION__, cmd ); + } + file = popen(cmd, "r"); + if (NULL==file) { + LOG(PIL_CRIT, "%s: Calling '%s' failed", + __FUNCTION__, cmd); + rc = -1; + goto out; + } + + if (output) { + slen=0; + data = MALLOC(1); + data[slen] = EOS; + } + while (!feof(file)) { + nodata = TRUE; + if (output) { + read_len = fread(buff, 1, BUFF_LEN, file); + if (read_len > 0) { + data = REALLOC(data, slen+read_len+1); + if (data == NULL) { + break; + } + memcpy(data + slen, buff, read_len); + slen += read_len; + data[slen] = EOS; + nodata = FALSE; + } + } else { + if (fgets(buff, BUFF_LEN, file)) { + LOG(PIL_INFO, "%s: '%s' output: %s", __FUNCTION__, cmd, buff); + nodata = FALSE; + } + } + if (nodata) { + sleep(1); + } + } + if (output && !data) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + rc = -1; + goto out; + } + + status = pclose(file); + if (WIFEXITED(status)) { + rc = WEXITSTATUS(status); + if (rc != 0 && Debug) { + LOG(PIL_DEBUG, + "%s: Calling '%s' returned %d", __FUNCTION__, cmd, rc); + } + } else { + if (WIFSIGNALED(status)) { + LOG(PIL_CRIT, "%s: '%s' got signal %d", + __FUNCTION__, cmd, WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + LOG(PIL_INFO, "%s: '%s' stopped with signal %d", + __FUNCTION__, cmd, WSTOPSIG(status)); + } else { + LOG(PIL_CRIT, "%s: '%s' exited abnormally (core dumped?)", + __FUNCTION__, cmd); + } + rc = -1; + } + if (Debug && output && data) { + LOG(PIL_DEBUG, "%s: '%s' output: %s", __FUNCTION__, cmd, data); + } + +out: + if (savevar) { + setenv(LOGTAG_VAR, savevar, 1); + g_free(savevar); + } else { + unsetenv(LOGTAG_VAR); + } + if (sd->cmd_opts) { + g_hash_table_foreach(sd->cmd_opts, ext_del_from_env, NULL); + } + if (!rc) { + if (output) { + *output = data; + } + } else { + if (data) { + FREE(data); + } + if (output) { + *output = NULL; + } + } + return rc; +} diff --git a/lib/plugins/stonith/external/Makefile.am b/lib/plugins/stonith/external/Makefile.am new file mode 100644 index 0000000..42e0046 --- /dev/null +++ b/lib/plugins/stonith/external/Makefile.am @@ -0,0 +1,33 @@ +# Makefile.am for OCF RAs +# +# Author: Sun Jing Dong +# Copyright (C) 2004 IBM +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +EXTRA_DIST = drac5 dracmc-telnet ibmrsa-telnet ipmi rackpdu vmware vcenter xen0 \ + xen0-ha-dom0-stonith-helper kdumpcheck nut + +extdir = $(stonith_ext_plugindir) + +helperdir = $(stonith_plugindir) + +ext_SCRIPTS = drac5 dracmc-telnet ibmrsa ibmrsa-telnet ipmi riloe ssh vmware vcenter rackpdu xen0 hmchttp \ + xen0-ha kdumpcheck ippower9258 nut libvirt \ + hetzner + +helper_SCRIPTS = xen0-ha-dom0-stonith-helper diff --git a/lib/plugins/stonith/external/drac5.in b/lib/plugins/stonith/external/drac5.in new file mode 100644 index 0000000..218cbd3 --- /dev/null +++ b/lib/plugins/stonith/external/drac5.in @@ -0,0 +1,113 @@ +#!/bin/sh +# +# External STONITH module for DRAC5 adapters. +# +# Author: Jun Wang +# License: GNU General Public License (GPL) +# + +trap 'if [ -n "$outf" ]; then ha_log.sh err "`cat $outf`"; rm -f "$outf"; fi' 0 +outf=`mktemp` || { + ha_log.sh err "mktemp failed" + exit 1 +} + +sshlogin() { + if [ x = "x$ipaddr" -o x = "x$userid" ] + then + ha_log.sh err "ipaddr or userid missing; check configuration" + return 1 + fi + @SSH@ -q -x -n $userid@$ipaddr racadm serveraction "$1" >$outf 2>&1 +} + +drac_reset() { + sshlogin hardreset +} + +drac_on() { + sshlogin poweron +} + +drac_off() { + sshlogin poweroff +} + +drac_status() { + sshlogin powerstatus +} + +case $1 in +gethosts) + echo $hostname + ;; +on) + drac_poweron + ;; +off) + drac_poweroff + ;; +reset) + drac_reset + ;; +status) + drac_status + ;; +getconfignames) + for i in hostname ipaddr userid; do + echo $i + done + ;; +getinfo-devid) + echo "DRAC5 STONITH device" + ;; +getinfo-devname) + echo "DRAC5 STONITH device" + ;; +getinfo-devdescr) + echo "DRAC5 host reset/poweron/poweroff" + ;; +getinfo-devurl) + echo "http://www.dell.com" + ;; +getinfo-xml) + cat < + + + + +Hostname + + +The hostname of the host to be managed by this STONITH device + + + + + + +IP Address + + +The IP address of the STONITH device + + + + + + +Login + + +The username used for logging in to the STONITH device + + + + +EOF + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/dracmc-telnet b/lib/plugins/stonith/external/dracmc-telnet new file mode 100644 index 0000000..d993961 --- /dev/null +++ b/lib/plugins/stonith/external/dracmc-telnet @@ -0,0 +1,377 @@ +#!/usr/bin/env python +# vim: set filetype=python +####################################################################### +# +# dracmc-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) +# Connects to Dell Drac/MC Blade Enclosure via a Cyclades +# terminal server with telnet and switches power of named +# blade servers appropriatelly. +# +# Required parameters: +# nodename: The name of the server you want to touch on your network +# cyclades_ip: The IP address of the cyclades terminal server +# cyclades_port: The port for telnet to access on the cyclades (i.e. 7032) +# servername: The DRAC/MC server name of the blade (i.e. Server-7) +# username: The login user name for the DRAC/MC +# password: The login password for the DRAC/MC +# +# Author: Alex Tsariounov +# +# Based on ibmrsa-telnet external stonith plugin by Andreas Mock +# (andreas.mock@web.de), Copyright by Adreas Mock and released as part +# of HAv2. +# +# History: +# 2009-10-12 First release. +# +# Copyright (c) 2009 Novell, Inc. +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 or later of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +####################################################################### +import sys +import os +import time +import telnetlib +import random +import subprocess + +LOGINRETRIES = 10 + +class TimeoutException(Exception): + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + + def __str__(self): + return repr(self.value) + +class DracMC(telnetlib.Telnet): + def __init__(self, *args, **kwargs): + telnetlib.Telnet.__init__(self) + self._timeout = 4 + self._loggedin = 0 + self._history = [] + self._appl = os.path.basename(sys.argv[0]) + self._server = args[0] + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def write(self, buffer): + self._history.append(self._get_timestamp() + ': WRITE: ' + repr(buffer)) + telnetlib.Telnet.write(self, buffer) + + def read_until(self, what, timeout=2): + line = telnetlib.Telnet.read_until(self, what, timeout) + self._history.append(self._get_timestamp() + ': READ : ' + repr(line)) + if not line.endswith(what): + raise TimeoutException("Timeout while waiting for '%s'." % (what, )) + return line + + def login(self, user, passwd): + time.sleep(0.3) + try: + line = self.read_until('Login: ', self._timeout) + self.write(user) + self.write('\r') + line = self.read_until('Password: ', self._timeout) + self.write(passwd) + self.write('\r') + except: + self.write("\r") + line = self.read_until('Login: ', self._timeout) + self.write(user) + self.write('\r') + line = self.read_until('Password: ', self._timeout) + self.write(passwd) + self.write('\r') + try: + line = self.read_until('DRAC/MC:', self._timeout) + except: + self.write("\r") + line = self.read_until('DRAC/MC:', self._timeout) + + def hardreset(self): + self.write('serveraction -s %s hardreset\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def powercycle(self): + self.write('serveraction -s %s powercycle\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def on(self): + self.write('serveraction -s %s powerup\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def off(self): + self.write('serveraction -s %s powerdown\r' % self._server) + line = self.read_until('OK', 10) + line = self.read_until('DRAC/MC:', self._timeout) + + def exit(self): + self.write('exit\r') + + def get_history(self): + return "\n".join(self._history) + + +class DracMCStonithPlugin: + def __init__(self): + # define the external stonith plugin api + self._required_cmds = \ + 'reset gethosts status getconfignames getinfo-devid ' \ + 'getinfo-devname getinfo-devdescr getinfo-devurl ' \ + 'getinfo-xml' + self._optional_cmds = 'on off' + self._required_cmds_list = self._required_cmds.split() + self._optional_cmds_list = self._optional_cmds.split() + + # who am i + self._appl = os.path.basename(sys.argv[0]) + + # telnet connection object + self._connection = None + + # the list of configuration names + self._confignames = ['nodename', 'cyclades_ip', 'cyclades_port', + 'servername', 'username', 'password'] + + # catch the parameters provided by environment + self._parameters = {} + for name in self._confignames: + try: + self._parameters[name] = os.environ.get(name, '').split()[0] + except IndexError: + self._parameters[name] = '' + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def _echo_debug(self, *args): + subprocess.call("ha_log.sh debug '%s'" % ' '.join(args), shell=True) + + def echo(self, *args): + what = ''.join([str(x) for x in args]) + sys.stdout.write(what) + sys.stdout.write('\n') + sys.stdout.flush() + self._echo_debug("STDOUT:", what) + + def echo_log(self, level, *args): + subprocess.call("ha_log.sh %s '%s'" % (level,' '.join(args)), shell=True) + + def _get_connection(self): + if not self._connection: + c = DracMC(self._parameters['servername']) + self._echo_debug("Connecting to '%s:%s'" % + (self._parameters['cyclades_ip'], + self._parameters['cyclades_port'])) + tries = 0 + while tries < LOGINRETRIES: + try: + c.open(self._parameters['cyclades_ip'], + self._parameters['cyclades_port']) + c.login(self._parameters['username'], + self._parameters['password']) + except Exception, args: + if "Connection reset by peer" in str(args): + self._echo_debug("Someone is already logged in... retry=%s" % tries) + c.close() + time.sleep(random.uniform(1.0, 5.0)) + else: + raise + else: + break + tries += 1 + + if tries == LOGINRETRIES: + c.close() + raise Exception("Could not log in to %s:%s" % + (self._parameters['cyclades_ip'], + self._parameters['cyclades_port'])) + self._connection = c + + def _end_connection(self): + if self._connection: + self._connection.exit() + self._connection.close() + + def reset(self): + self._get_connection() + # self._connection.hardreset() + self._connection.powercycle() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Reset of node '%s' done" % + (self._parameters['nodename'],)) + return(0) + + def on(self): + self._get_connection() + self._connection.on() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' ON" % + (self._parameters['nodename'],)) + return(0) + + def off(self): + self._get_connection() + self._connection.off() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' OFF" % + (self._parameters['nodename'],)) + return(0) + + def gethosts(self): + self.echo(self._parameters['nodename']) + return(0) + + def status(self): + self._get_connection() + self._end_connection() + self._echo_debug(self._connection.get_history()) + return(0) + + def getconfignames(self): + for name in ['nodename', 'cyclades_ip', 'cyclades_port', 'servername', + 'username', 'password']: + self.echo(name) + return(0) + + def getinfo_devid(self): + self.echo("External Stonith Plugin for Dell DRAC/MC via Cyclades") + return(0) + + def getinfo_devname(self): + self.echo("External Stonith Plugin for Dell Drac/MC connecting " + "via Telnet to a Cyclades port") + return(0) + + def getinfo_devdescr(self): + self.echo("External stonith plugin for HAv2 which connects to " + "a Dell DRAC/MC connected via a Cyclades port with telnet. " + "Commands to turn on/off power and to reset server are sent " + "appropriately. " + "(c) 2009 by Novell, Inc. (alext@novell.com)") + return(0) + + def getinfo_devurl(self): + self.echo("http://support.dell.com/support/edocs/software/smdrac3/dracmc/1.3/en/index.htm") + + def getinfo_xml(self): + info = """ + + + nodename to shoot + + Name of the node to be stonithed. + + + + + hostname or ip address of cyclades + + Hostname or IP address of Cyclades connected to DRAC/MC. + + + + + telnet port to use on cyclades + + Port used with the Cyclades telnet interface which is connected to the DRAC/MC. + + + + + DRAC/MC name of blade to be stonithed + + Name of server blade to be stonithed on the DRAC/MC (example: Server-7) + + + + + username to login on the DRAC/MC + + Username to login to the DRAC/MC once connected via the Cyclades port. + + + + + password to login on the DRAC/MC + + Password to login to the DRAC/MC once connected via the Cyclades port. + + + + """ + self.echo(info) + return(0) + + def not_implemented(self, cmd): + self.echo_log("err", "Command '%s' not implemented." % (cmd,)) + return(1) + + def usage(self): + usage = "Call me with one of the allowed commands: %s, %s" % ( + ', '.join(self._required_cmds_list), + ', '.join(self._optional_cmds_list)) + return usage + + def process(self, argv): + self._echo_debug("========== Start =============") + if len(argv) < 1: + self.echo_log("err", 'At least one commandline argument required.') + return(1) + cmd = argv[0] + self._echo_debug("cmd:", cmd) + if cmd not in self._required_cmds_list and \ + cmd not in self._optional_cmds_list: + self.echo_log("err", "Command '%s' not supported." % (cmd,)) + return(1) + try: + cmd = cmd.lower().replace('-', '_') + func = getattr(self, cmd, self.not_implemented) + rc = func() + return(rc) + except Exception, args: + self.echo_log("err", 'Exception raised:', str(args)) + if self._connection: + self.echo_log("err", self._connection.get_history()) + self._connection.close() + return(1) + + +if __name__ == '__main__': + stonith = DracMCStonithPlugin() + rc = stonith.process(sys.argv[1:]) + sys.exit(rc) diff --git a/lib/plugins/stonith/external/hetzner b/lib/plugins/stonith/external/hetzner new file mode 100755 index 0000000..2b3e675 --- /dev/null +++ b/lib/plugins/stonith/external/hetzner @@ -0,0 +1,139 @@ +#!/bin/sh +# +# External STONITH module for Hetzner. +# +# Copyright (c) 2011 MMUL S.a.S. - Raoul Scarazzini +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +# Read parameters from config file, format is based upon the hetzner OCF resource agent +# developed by Kumina: http://blog.kumina.nl/2011/02/hetzner-failover-ip-ocf-script/ +conf_file="/etc/hetzner.cfg" + +case $1 in + get*) ;; # don't print errors if conf_file not present + *) + user=`sed -n 's/^user.*=\ *//p' $conf_file` + pass=`sed -n 's/^pass.*=\ *//p' $conf_file` + ;; +esac + +hetzner_server="https://robot-ws.your-server.de" + +check_http_response() { + # If the response is 200 then return 0 + if [ $1 = 200 ] + then + return 0 + else + # If the response is not 200 then display a description of the problem and return 1 + case $1 in + 400) ha_log.sh err "INVALID_INPUT - Invalid input parameters" + ;; + 404) ha_log.sh err "SERVER_NOT_FOUND - Server with ip $remote_ip not found" + ;; + 409) ha_log.sh err "RESET_MANUAL_ACTIVE - There is already a running manual reset" + ;; + 500) ha_log.sh err "RESET_FAILED - Resetting failed due to an internal error" + ;; + esac + return 1 + fi +} + +case $1 in +gethosts) + echo $hostname + exit 0 + ;; +on) + # Can't really be implemented because Hetzner's webservice cannot power on a system + ha_log.sh err "Power on is not available since Hetzner's webservice can't do this operation." + exit 1 + ;; +off) + # Can't really be implemented because Hetzner's webservice cannot power on a system + ha_log.sh err "Power off is not available since Hetzner's webservice can't do this operation." + exit 1 + ;; +reset) + # Launching the reset action via webservice + check_http_response $(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/reset/$remote_ip -d type=hw) + exit $? + ;; +status) + # Check if we can contact the webservice + check_http_response "$(curl --silent -o /dev/null -w '%{http_code}' -u $user:$pass $hetzner_server/server/$remote_ip)" + exit $? + ;; +getconfignames) + echo "hostname" + echo "remote_ip" + exit 0 + ;; +getinfo-devid) + echo "Hetzner STONITH device" + exit 0 + ;; +getinfo-devname) + echo "Hetzner STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "Hetzner host reset" + echo "Manages the remote webservice for reset a remote server." + exit 0 + ;; +getinfo-devurl) + echo "http://wiki.hetzner.de/index.php/Robot_Webservice_en" + exit 0 + ;; +getinfo-xml) + cat << HETZNERXML + + + + +Hostname + + +The name of the host to be managed by this STONITH device. + + + + + + +Remote IP + + +The address of the remote IP that manages this server. + + + +HETZNERXML + exit 0 + ;; +*) + ha_log.sh err "Don't know what to do for '$remote_ip'" + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/hmchttp b/lib/plugins/stonith/external/hmchttp new file mode 100644 index 0000000..9d111bc --- /dev/null +++ b/lib/plugins/stonith/external/hmchttp @@ -0,0 +1,218 @@ +#!/bin/sh +# External STONITH module for HMC web console +# +# Copyright (c) 2007 Xinwei Hu +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +#set -x +hostlist=`echo $hostlist | tr ',' ' '` + +trap '[ ! -e "$COOKIEFILE" ] || rm -f "$COOKIEFILE"' 0 +COOKIEFILE=`mktemp` || exit 1 + +: ${CURLBIN="/usr/bin/curl"} +: ${user=admin} +: ${password=admin} + +check_parameter() { + if [ ! -x $CURLBIN ] + then + ha_log.sh err "Curl can't be found in normal place. Set CURLBIN to override the default value" + exit 1 + fi + + if [ -z $hmc_ipaddr ] + then + ha_log.sh err "The address of HMC web console is not specified" + exit 1 + fi +} + +HMCUSERNAME=$user +HMCPASSWORD=$password + +HMC_LOGIN_COMMAND="$CURLBIN -3 -k -c $COOKIEFILE -d user=$HMCUSERNAME -d password=$HMCPASSWORD -d lang=0 -d submit=Log+in " +HMC_LOGOUT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d submit=Log+out " +HMC_TOC_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=2 " +HMC_POWERON_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 -d sp=255 -d is=0 -d om=4 -d id=1 -d ip=2 -d plt=1 -d pm=0 -d on=Save+settings+and+power+on " +HMC_POWERSTATE_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=60 " +HMC_POWEROFF_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=30 -d submit=Continue " +HMC_REBOOT_COMMAND="$CURLBIN -3 -k -b $COOKIEFILE -d form=74 -d submit=Continue " + +hmc_login() { + iamin=0 + while [ $iamin -eq 0 ]; do + $HMC_LOGIN_COMMAND https://$hmc_ipaddr/cgi-bin/cgi >/dev/null 2>&1 + $HMC_TOC_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Too many users" + iamin=$? + sleep 2 + done +} +hmc_logout() { + $HMC_LOGOUT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} + +hmc_reboot() { + check_parameter + $HMC_REBOOT_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} +hmc_poweron() { + r=1 + while [ 0 -ne $r ]; do + $HMC_POWERON_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null | grep -q "Operation completed successfully" + r=$? + done +} +hmc_poweroff() { + check_parameter + $HMC_POWEROFF_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null +} +hmc_powerstate() { + check_parameter + r=`$HMC_POWERSTATE_COMMAND https://$hmc_ipaddr/cgi-bin/cgi 2>/dev/null| grep "Current system power state:" | sed 's/
//g' | awk '{print $5}'` + echo $r +} + +hmc_poweroffon() { + check_parameter + hmc_poweroff + while [ 1 ]; do + r=`hmc_powerstate` + ha_log.sh debug "power state: $r" + if [ $r = "Off" ]; then + break + fi + sleep 5 + done + sleep 3 + hmc_poweron +} + +case $1 in +gethosts) + for h in $hostlist; do + echo $h + done + exit 0 + ;; +status) + if + ping -w1 -c1 "$hmc_ipaddr" 2>&1 + then + exit 0 + fi + exit 1 + ;; +getconfignames) + for f in hostlist hmc_ipaddr user password; do + echo $f + done + exit 0 + ;; +getinfo-devid) + echo "HMC web console STONITH device" + exit 0 + ;; +getinfo-devname) + echo "HMC web console STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "HMC web console based host power control" + echo "Use for i5, p5, pSeries and OpenPower systems that are managed via " + echo "web console through a direct connection to system's HMC port." + exit 0 + ;; +getinfo-devurl) + echo "http://www.ibm.com" + exit 0 + ;; +getinfo-xml) + cat << HMCXML + + + + +Hostlist + +The list of hosts that the STONITH device controls + + + + + +HMC IPAddr + +The IP address of the HMC web console + + + + + +User + +User name to log into HMC web console + + + + + +Password + +The password of user name to log into HMC web console + + + + +HMCXML + exit 0 + ;; +esac + +case $1 in +on|off|reset|powerstate|poweroffon) + hmc_login + case $1 in + on) + hmc_poweron $hmc_ipaddr + ;; + off) + hmc_poweroff $hmc_ipaddr + ;; + reset) +# hmc_reboot $hmc_ipaddr + hmc_poweroffon $hmc_ipaddr + ;; + powerstate) + hmc_powerstate + ;; + poweroffon) + hmc_poweroffon + ;; + esac + hmc_logout + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ibmrsa b/lib/plugins/stonith/external/ibmrsa new file mode 100644 index 0000000..7408465 --- /dev/null +++ b/lib/plugins/stonith/external/ibmrsa @@ -0,0 +1,157 @@ +#!/bin/sh +# +# Copyright (c) 2006 Dejan Muhamedagic , IBM Austria +# +# External STONITH module for IBM RSA adapters. +# External STONITH module for IBM BMC. +# This STONITH module depends on IBMmpcli. +# + +trap 'rm -f "$outf"' 0 +outf=`mktemp` || { + ha_log.sh err 'mktemp failed' + exit 1 +} + +chkmpcli() { + test -x /opt/IBMmpcli/bin/MPCLI.sh +} +mpcli() { + chkmpcli || { + ha_log.sh err "IBM mpcli not installed" + return 1 + } + if [ x = "x$ipaddr" -o x = "x$userid" -o x = "x$passwd" ] + then + ha_log.sh err "ipaddr, userid, or passwd missing; check configuration" + return 1 + fi + type=${type:-"ibm"} + + goodstg="SUCCESS" + failstg="FAILURE" + ( + echo "logonip -h $ipaddr -u $userid -p $passwd -t $type" + echo "outputfile $outf" + cat + ) | /opt/IBMmpcli/bin/MPCLI.sh | grep -w $goodstg >/dev/null 2>&1 + rc=$? + grep -w $failstg $outf >/dev/null + if [ $rc -eq 0 -a $? -eq 1 ]; then + return 0 + else + ha_log.sh err "MPCLI.sh failed: `cat $outf`" + return 1 + fi +} +ibmrsa_reboot() { + echo restart -now | mpcli +} +ibmrsa_poweron() { + echo poweron | mpcli +} +ibmrsa_poweroff() { + echo poweroff | mpcli +} +ibmrsa_status() { + echo | mpcli +} + +hostname=`echo ${hostname} | tr ',' ' '` + +case $1 in +gethosts) + echo $hostname + ;; +on) + ibmrsa_poweron + ;; +off) + ibmrsa_poweroff + ;; +reset) + ibmrsa_reboot + ;; +status) + ibmrsa_status + ;; +getconfignames) + for i in hostname ipaddr userid passwd type; do + echo $i + done + ;; +getinfo-devid) + echo "IBM MP STONITH device" + ;; +getinfo-devname) + echo "IBM MP STONITH device" + ;; +getinfo-devdescr) + echo "IBM MP host reboot/poweron/poweroff" + ;; +getinfo-devurl) + echo "http://www.ibm.com" + ;; +getinfo-xml) + cat < + + + + +Hostname + + +The hostname of the host to be managed by this STONITH device + + + + + + +IP Address + + +The IP address of the STONITH device + + + + + + +Login + + +The username used to login into the STONITH device + + + + + + +Password + + +The password used to login into the STONITH device + + + + + + +Management processor type + + +The type of the management processor. Possible values are +"ibm" (default, typically used for RSA) and "ipmi" +(for IPMI compliant processors such as BMC). + + + + +EOF + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ibmrsa-telnet b/lib/plugins/stonith/external/ibmrsa-telnet new file mode 100644 index 0000000..4d75d9a --- /dev/null +++ b/lib/plugins/stonith/external/ibmrsa-telnet @@ -0,0 +1,320 @@ +#!/usr/bin/python +# vim: set filetype=python +####################################################################### +# +# ibmrsa-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) +# Connects to IBM RSA Board via telnet and switches power +# of server appropriately. +# +# Author: Andreas Mock (andreas.mock@web.de) +# +# History: +# 2007-10-19 Fixed bad commandline handling in case of stonithing +# 2007-10-11 First release. +# +# Comment: Please send bug fixes and enhancements. +# I hope the functionality of communicating via telnet is encapsulated +# enough so that someone can use it for similar purposes. +# +# Description: IBM offers Remote Supervisor Adapters II for several +# servers. These RSA boards can be accessed in different ways. +# One of that is via telnet. Once logged in you can use 'help' to +# show all available commands. With 'power' you can reset, power on and +# off the controlled server. This command is used in combination +# with python's standard library 'telnetlib' to do it automatically. +# +# cib-snippet: Please see README.ibmrsa-telnet for examples. +# +# Copyright (c) 2007 Andreas Mock (andreas.mock@web.de) +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 or later of the GNU General Public +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +####################################################################### +import sys +import os +import time +import telnetlib +import subprocess + +class TimeoutException(Exception): + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + + def __str__(self): + return repr(self.value) + +class RSABoard(telnetlib.Telnet): + def __init__(self, *args, **kwargs): + telnetlib.Telnet.__init__(self, *args, **kwargs) + self._timeout = 10 + self._loggedin = 0 + self._history = [] + self._appl = os.path.basename(sys.argv[0]) + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def write(self, buffer, nolog = False): + self._history.append(self._get_timestamp() + ': WRITE: ' + + (nolog and '******' or repr(buffer))) + telnetlib.Telnet.write(self, buffer) + + def expect(self, what, timeout=20): + line = telnetlib.Telnet.expect(self, what, timeout) + self._history.append(self._get_timestamp() + ': READ : ' + repr(line)) + if not line: + raise TimeoutException("Timeout while waiting for '%s'." % (what, )) + return line + + def login(self, user, passwd): + time.sleep(1) + line = self.expect(['\nlogin : ', '\nusername: '], self._timeout) + self.write(user) + self.write('\r') + line = self.expect(['\nPassword: ', '\npassword: '], self._timeout) + self.write(passwd, nolog = True) + self.write('\r') + line = self.expect(['\nsystem>', '> '], self._timeout) + + def reset(self): + self.write('power cycle\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def on(self): + self.write('power on\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def off(self): + self.write('power off\r') + line = self.expect(['\nok'], self._timeout) + line = self.expect(['\nsystem>', '> '], self._timeout) + + def exit(self): + self.write('exit\r') + + def get_history(self): + return "\n".join(self._history) + + +class RSAStonithPlugin: + def __init__(self): + # define the external stonith plugin api + self._required_cmds = \ + 'reset gethosts status getconfignames getinfo-devid ' \ + 'getinfo-devname getinfo-devdescr getinfo-devurl ' \ + 'getinfo-xml' + self._optional_cmds = 'on off' + self._required_cmds_list = self._required_cmds.split() + self._optional_cmds_list = self._optional_cmds.split() + + # who am i + self._appl = os.path.basename(sys.argv[0]) + + # telnet connection object + self._connection = None + + # the list of configuration names + self._confignames = ['nodename', 'ip_address', 'username', 'password'] + + # catch the parameters provided by environment + self._parameters = {} + for name in self._confignames: + try: + self._parameters[name] = os.environ.get(name, '').split()[0] + except IndexError: + self._parameters[name] = '' + + def _get_timestamp(self): + ct = time.time() + msecs = (ct - long(ct)) * 1000 + return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(ct)), msecs) + + def _echo_debug(self, *args): + self.echo_log('debug', *args) + + def echo(self, *args): + what = ''.join([str(x) for x in args]) + sys.stdout.write(what) + sys.stdout.write('\n') + sys.stdout.flush() + self._echo_debug("STDOUT:", what) + + def echo_log(self, level, *args): + subprocess.call(('ha_log.sh', level) + args) + + def _get_connection(self): + if not self._connection: + c = RSABoard() + self._echo_debug("Connect to '%s'" % + (self._parameters['ip_address'],)) + c.open(self._parameters['ip_address']) + self._echo_debug("Connection established") + c.login(self._parameters['username'], + self._parameters['password']) + self._connection = c + + def _end_connection(self): + if self._connection: + self._connection.exit() + self._connection.close() + + def reset(self): + self._get_connection() + self._connection.reset() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Reset of node '%s' done" % + (self._parameters['nodename'],)) + return(0) + + def on(self): + self._get_connection() + self._connection.on() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' ON" % + (self._parameters['nodename'],)) + return(0) + + def off(self): + self._get_connection() + self._connection.off() + self._end_connection() + self._echo_debug(self._connection.get_history()) + self.echo_log("info", "Switched node '%s' OFF" % + (self._parameters['nodename'],)) + return(0) + + def gethosts(self): + self.echo(self._parameters['nodename']) + return(0) + + def status(self): + self._get_connection() + self._end_connection() + self._echo_debug(self._connection.get_history()) + return(0) + + def getconfignames(self): + for name in ['nodename', 'ip_address', 'username', 'password']: + self.echo(name) + return(0) + + def getinfo_devid(self): + self.echo("External Stonith Plugin for IBM RSA Boards") + return(0) + + def getinfo_devname(self): + self.echo("External Stonith Plugin for IBM RSA Boards connecting " + "via Telnet") + return(0) + + def getinfo_devdescr(self): + self.echo("External stonith plugin for HAv2 which connects to " + "a RSA board on IBM servers via telnet. Commands to " + "turn on/off power and to reset server are sent " + "appropriately. " + "(c) 2007 by Andreas Mock (andreas.mock@web.de)") + return(0) + + def getinfo_devurl(self): + self.echo("http://www.ibm.com/Search/?q=remote+supervisor+adapter") + + def getinfo_xml(self): + info = """ + + + nodename to shoot + + Name of the node which has to be stonithed in case. + + + + + hostname or ip address of RSA + + Hostname or ip address of RSA board used to reset node. + + + + + username to login on RSA board + + Username to login on RSA board. + + + + + password to login on RSA board + + Password to login on RSA board. + + + + """ + self.echo(info) + return(0) + + def not_implemented(self, cmd): + self.echo_log("err", "Command '%s' not implemented." % (cmd,)) + return(1) + + def usage(self): + usage = "Call me with one of the allowed commands: %s, %s" % ( + ', '.join(self._required_cmds_list), + ', '.join(self._optional_cmds_list)) + return usage + + def process(self, argv): + self._echo_debug("========== Start =============") + if len(argv) < 1: + self.echo_log("err", 'At least one commandline argument required.') + return(1) + cmd = argv[0] + self._echo_debug("cmd:", cmd) + if cmd not in self._required_cmds_list and \ + cmd not in self._optional_cmds_list: + self.echo_log("err", "Command '%s' not supported." % (cmd,)) + return(1) + try: + cmd = cmd.lower().replace('-', '_') + func = getattr(self, cmd, self.not_implemented) + rc = func() + return(rc) + except Exception, args: + self.echo_log("err", 'Exception raised:', str(args)) + if self._connection: + self.echo_log("err", self._connection.get_history()) + self._connection.close() + return(1) + + +if __name__ == '__main__': + stonith = RSAStonithPlugin() + rc = stonith.process(sys.argv[1:]) + sys.exit(rc) diff --git a/lib/plugins/stonith/external/ipmi b/lib/plugins/stonith/external/ipmi new file mode 100644 index 0000000..abadd5a --- /dev/null +++ b/lib/plugins/stonith/external/ipmi @@ -0,0 +1,276 @@ +#!/bin/sh +# +# External STONITH module using IPMI. +# This modules uses uses the ipmitool program available from +# http://ipmitool.sf.net/ for actual communication with the +# managed device. +# +# Copyright (c) 2007 Martin Bene +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# Initialization -- fix locale settings so we can parse output from +# binaries if we need it +LANG=C +LC_ALL=C + +RESET="power reset" +POWEROFF="power off" +POWERON="power on" +STATUS="power status" +IPMITOOL=${ipmitool:-"`which ipmitool 2>/dev/null`"} + +have_ipmi() { + test -x "${IPMITOOL}" +} + +# Wrapper function for ipmitool that sets the correct host IP address, +# username, and password, and invokes ipmitool with any arguments +# passed in +run_ipmitool() { + local ipmitool_opts privlvl="" + have_ipmi || { + ha_log.sh err "ipmitool not installed" + return 1 + } + if [ -z "${ipaddr}" -o -z "${userid}" -o -z "${passwd}" ]; then + ha_log.sh err "ipaddr, userid or passwd missing; check configuration" + return 1 + fi + + if [ -z "${interface}" ]; then + # default to "lan" interface + interface="lan" + fi + if [ -n "${priv}" ]; then + # default to "lan" interface + privlvl="-L $priv" + fi + + ipmitool_opts="-I ${interface} -H ${ipaddr} $privlvl" + + case "${passwd_method}" in + param|'') + passwd_method=param + M="-P" + ;; + env) + M="-E" + ;; + file) + M="-f" + ;; + *) + ha_log.sh err "invalid passwd_method: \"${passwd_method}\"" + return 1 + esac + + action="$*" + + if [ $passwd_method = env ] + then + IPMI_PASSWORD="${passwd}" ${IPMITOOL} $ipmitool_opts -U "${userid}" -E ${action} + else + ${IPMITOOL} $ipmitool_opts -U "${userid}" $M "${passwd}" ${action} + fi 2>&1 +} + +# Yet another convenience wrapper that invokes run_ipmitool, captures +# its output, logs the output, returns either 0 (on success) or 1 (on +# any error) +do_ipmi() { + if outp=`run_ipmitool $*`; then + ha_log.sh debug "ipmitool output: `echo $outp`" + return 0 + else + ha_log.sh err "error executing ipmitool: `echo $outp`" + return 1 + fi +} + +# Check if the managed node is powered on. To do so, issue the "power +# status" command. Should return either "Chassis Power is on" or +# "Chassis Power is off". +ipmi_is_power_on() { + local outp + outp=`run_ipmitool ${STATUS}` + case "${outp}" in + *on) + return 0 + ;; + *off) + return 1 + ;; + esac +} + + +case ${1} in +gethosts) + echo $hostname + exit 0 + ;; +on) + do_ipmi "${POWERON}" + exit + ;; +off) + do_ipmi "${POWEROFF}" + exit + ;; +reset) + if ipmi_is_power_on; then + do_ipmi "${RESET}" + else + do_ipmi "${POWERON}" + fi + exit + ;; +status) + # "status" reflects the status of the stonith _device_, not + # the managed node. Hence, only check if we can contact the + # IPMI device with "power status" command, don't pay attention + # to whether the node is in fact powered on or off. + do_ipmi "${STATUS}" + exit $? + ;; +getconfignames) + for i in hostname ipaddr userid passwd interface; do + echo $i + done + exit 0 + ;; +getinfo-devid) + echo "IPMI STONITH device" + exit 0 + ;; +getinfo-devname) + echo "IPMI STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ipmitool based power management. Apparently, the power off" + echo "method of ipmitool is intercepted by ACPI which then makes" + echo "a regular shutdown. If case of a split brain on a two-node" + echo "it may happen that no node survives. For two-node clusters" + echo "use only the reset method." + exit 0 + ;; +getinfo-devurl) + echo "http://ipmitool.sf.net/" + exit 0 + ;; +getinfo-xml) + cat << IPMIXML + + + + +Hostname + + +The name of the host to be managed by this STONITH device. + + + + + + +IP Address + + +The IP address of the STONITH device. + + + + + + +Login + + +The username used for logging in to the STONITH device. + + + + + + +Password + + +The password used for logging in to the STONITH device. + + + + + + +Method for passing passwd parameter + + +Method for passing the passwd parameter to ipmitool + param: pass as parameter (-P) + env: pass via environment (-E) + file: value of "passwd" is actually a file name, pass with (-f) + + + + + + +IPMI interface + + +IPMI interface to use, such as "lan" or "lanplus". + + + + + + +The privilege level of the user. + + +The privilege level of the user, for instance OPERATOR. If +unspecified the privilege level is ADMINISTRATOR. See +ipmitool(1) -L option for more information. + + + + + + +IPMI command(ipmitool) + + +Specify the full path to IPMI command. + + + + +IPMIXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/ippower9258.in b/lib/plugins/stonith/external/ippower9258.in new file mode 100755 index 0000000..6ae7e02 --- /dev/null +++ b/lib/plugins/stonith/external/ippower9258.in @@ -0,0 +1,316 @@ +#!/bin/sh +# +# External STONITH module using IP Power 9258 or compatible devices. +# +# Copyright (c) 2010 Helmut Weymann (Helmut (at) h-weymann (dot) de) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +# +# Basic commands & parameters independent from individual device + +DEVICE="IP Power 9258" +IPPowerOn="1" +IPPowerOff="0" +IPGetPower="Set.cmd?CMD=GetPower" +IPSetPower="Set.cmd?CMD=SetPower" +IPPort_name="P" +IPPort0=60 +HTTP_COMMAND="wget -q -O - --" +LOG_ERROR="ha_log.sh err" +LOG_WARNING="ha_log.sh warn" +LOG_INFO="ha_log.sh info" +LOG_DEBUG="ha_log.sh debug" +MY_COOKIES="cookies.txt" +MY_TEMPFILE="temp.htm" +PORT_STATUS="iocontrol.htm" +UNDEFINED_HOSTNAME="*not-defined*" + +# +# check MY_ROOT_PATH for IP Power 9258 and create it if necessary +MY_ROOT_PATH="@GLUE_STATE_DIR@/heartbeat/rsctmp/ippower9258" + +# +# script functions +# + +get_challenge() { + # + # device sends a challenge for md5 encryption of username, password and challenge + send_web_command - "http://$deviceip/" | grep Challenge | grep input | cut -d '"' -f 6 +} + +get_cookie_from_device(){ + # the form on the login page has these fields: + # Username, Password, Challenge, Response, ScreenWidth + # + challenge=`get_challenge` + response=`echo -n "$username$password$challenge" | md5sum | cut -b -32` + postdata="Username=$username&Password=&Challenge=&Response=$response&ScreenWidth=1024" + send_web_command " $MY_PATH/$MY_TEMPFILE --post-data=$postdata" "http://$deviceip/tgi/login.tgi" + if grep -qs "Invalid User name or Password" $MY_PATH/$MY_TEMPFILE + then + $LOG_ERROR "Login to device $deviceip failed." + $LOG_ERROR "Received Challenge = <<<$challenge>>>." + $LOG_ERROR "Sent postdata = <<<$postdata>>>." + exit 1 + fi +} + +get_data_from_device() { + # If successful all device info is available in MY_PATH + rm -f "$MY_PATH/$PORT_STATUS" + send_web_command "$MY_PATH/$PORT_STATUS" "http://$deviceip/$PORT_STATUS" + if grep -qs "Cookie Time Out" $MY_PATH/$PORT_STATUS + then + $LOG_ERROR "received no port data from $deviceip (Cookie Time Out)" + exit 1 + fi +} + +send_http_request() { + # ececution of http commands supported by the device + $HTTP_COMMAND "http://$username:$password@$deviceip/$1" +} + +send_web_command(){ + # ececution of web commands through the web-interface + WEB_COMMAND="wget -q --keep-session-cookies" + WEB_COMMAND="$WEB_COMMAND --load-cookies $MY_PATH/$MY_COOKIES" + WEB_COMMAND="$WEB_COMMAND --save-cookies $MY_PATH/$MY_COOKIES" + $WEB_COMMAND -O $1 -- $2 +} + +name2port() { + local name=$1 + local i=$IPPort0 + for h in $device_hostlist ; do + if [ $h = $name ]; then + echo $IPPort_name$i + return + fi + i=`expr $i + 1` + done + echo "invalid" +} + +set_port() { + # + # port status is always set. Even if requested status is current status. + # host status is not considered. + local host=$1 + local requested_status=$2 # 0 or 1 + local port=`name2port $host` + if [ "$port" = "invalid" ] + then + $LOG_ERROR "Host $host is not in hostlist ($hostlist) for $deviceip." + exit 1 + fi + ret=`send_http_request "$IPSetPower+$port=$requested_status" | cut -b 11` + if [ "$ret" != "$requested_status" ] + then + $LOG_ERROR "$DEVICE at $deviceip responds with wrong status $ret for host $host at port $port." + exit 1 + fi + return 0 +} + +build_device_hostlist() { + # + # hostnames are available from http://$deviceip/iocontrol.htm" + # check for number of ports + # + device_hostlist=$( + w3m -dump $MY_PATH/$PORT_STATUS | grep 'Power[1-8]' | + sed 's/[^[]*\[//;s/\].*//;s/ *//' | + while read h; do + [ -z "$h" ] && + echo $UNDEFINED_HOSTNAME || + echo $h + done + ) + if [ x = x"$device_hostlist" ]; then + $LOG_ERROR "cannot get hostlist for $deviceip" + exit 1 + fi + $LOG_DEBUG "Got new hostlist ($device_hostlist) from $deviceip" +} + +filter_device_hostlist() { + # check the given hostlist against the device hostlist + local host + for host in $device_hostlist; do + [ "$host" != "$UNDEFINED_HOSTNAME" ] && + echo $host + done +} + +check_hostlist() { + # check the given hostlist against the device hostlist + local cnt=`echo "$hostlist" | wc -w` + local cnt2=0 + local host + for host in $hostlist; do + if [ `name2port $host` != "invalid" ]; then + cnt2=$((cnt2+1)) + else + $LOG_ERROR "host $host not defined at $deviceip" + fi + done + [ $cnt -ne $cnt2 ] && + exit 1 +} + +get_http_status() { + pattern="P60=[01],P61=[01],P62=[01],P63=[01],P64=[01],P65=[01],P66=[01],P67=[01]" + ret=`send_http_request "$IPGetPower" | grep $pattern` + if [ "X$ret" = "X" ] + then + $LOG_ERROR "$DEVICE at $deviceip returns invalid or no string." + exit 1 + fi +} + +hostlist=`echo $hostlist | tr ',' ' '` +case $1 in +gethosts|on|off|reset|status) + # need environment from stonithd + # and device information from individual device + # + # default device username is admin + # IP Power 9258 does not allow user management. + # + if [ "X$username" = "X" ] + then + username="admin" + fi + + mkdir -p $MY_ROOT_PATH + tmp_path="$deviceip" # ensure a simple unique pathname + MY_PATH="$MY_ROOT_PATH/$tmp_path" + mkdir -p $MY_PATH + get_cookie_from_device + get_data_from_device + build_device_hostlist + if [ "X$hostlist" = "X" ]; then + hostlist="`filter_device_hostlist`" + else + check_hostlist + fi + ;; +*) + # the client is asking for meta-data + ;; +esac + +target=`echo $2 | sed 's/[.].*//'` +# the necessary actions for stonithd +case $1 in +gethosts) + echo $hostlist + ;; +on) + set_port $target $IPPowerOn + ;; +off) + set_port $target $IPPowerOff + ;; +reset) + set_port $target $IPPowerOff + sleep 5 + set_port $target $IPPowerOn + ;; +status) + # werify http command interface + get_http_status + ;; +getconfignames) + # return all the config names + for ipparam in deviceip username password hostlist + do + echo $ipparam + done + ;; +getinfo-devid) + echo "IP Power 9258" + ;; +getinfo-devname) + echo "IP Power 9258 power switch" + ;; +getinfo-devdescr) + echo "Power switch IP Power 9258 with 4 or 8 power outlets." + echo "WARNING: It is different from IP Power 9258 HP" + ;; +getinfo-devurl) + echo "http://www.aviosys.com/manual.htm" + ;; +getinfo-xml) + cat << IPPOWERXML + + + + +IP address or hostname of the device. + + +The IP Address or the hostname of the device. + + + + + + +Password + + +The password to log in with. + + + + + + +Hostlist + + +The list of hosts that the device controls. +If you leave this list empty, we will retrieve the hostnames from the device. + + + + + + +Account Name + + +The user to log in with. + + + + +IPPOWERXML + ;; +*) + $LOG_ERROR "Unexpected command $1 for $DEVICE at $deviceip." + exit 1; + ;; +esac diff --git a/lib/plugins/stonith/external/kdumpcheck.in b/lib/plugins/stonith/external/kdumpcheck.in new file mode 100644 index 0000000..7f3f752 --- /dev/null +++ b/lib/plugins/stonith/external/kdumpcheck.in @@ -0,0 +1,274 @@ +#!/bin/sh +# +# External STONITH module to check kdump. +# +# Copyright (c) 2008 NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n" +#Set default user name. +USERNAME="kdumpchecker" +#Initialize identity file-path options for ssh command +IDENTITY_OPTS="" + +#Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo ${hostlist} | tr ',' ' '` + +## +# Check the parameter hostlist is set or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_hostlist() { + if [ -z "${hostlist}" ]; then + ha_log.sh err "hostlist is empty" + exit 6 #ERR_CONFIGURED + fi +} + +## +# Set kdump check user name to USERNAME. +# always return 0. +## +get_username() { + kdump_conf="/etc/kdump.conf" + + if [ ! -f "${kdump_conf}" ]; then + ha_log.sh debug "${kdump_conf} doesn't exist" + return 0 + fi + + tmp="" + while read config_opt config_val; do + if [ "${config_opt}" = "kdump_check_user" ]; then + tmp="${config_val}" + fi + done < "${kdump_conf}" + if [ -n "${tmp}" ]; then + USERNAME="${tmp}" + fi + + ha_log.sh debug "kdump check user name is ${USERNAME}." +} + +## +# Check the specified or default identity file exists or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_identity_file() { + IDENTITY_OPTS="" + if [ -n "${identity_file}" ]; then + if [ ! -f "${identity_file}" ]; then + ha_log.sh err "${identity_file} doesn't exist." + exit 6 #ERR_CONFIGURED + fi + IDENTITY_OPTS="-i ${identity_file}" + else + flg_file_exists=0 + homedir=`eval echo "~${USERNAME}"` + for filename in "${homedir}/.ssh/id_rsa" \ + "${homedir}/.ssh/id_dsa" \ + "${homedir}/.ssh/identity" + do + if [ -f "${filename}" ]; then + flg_file_exists=1 + IDENTITY_OPTS="${IDENTITY_OPTS} -i ${filename}" + fi + done + if [ ${flg_file_exists} -eq 0 ]; then + ha_log.sh err "${USERNAME}'s identity file for ssh command" \ + " doesn't exist." + exit 6 #ERR_CONFIGURED + fi + fi +} + +## +# Check the user to check doing kdump exists or not. +# If not, exit with 6 (ERR_CONFIGURED). +## +check_user_existence() { + + # Get kdump check user name and check whether he exists or not. + grep -q "^${USERNAME}:" /etc/passwd > /dev/null 2>&1 + ret=$? + if [ ${ret} != 0 ]; then + ha_log.sh err "user ${USERNAME} doesn't exist." \ + "please confirm \"kdump_check_user\" setting in /etc/kdump.conf." \ + "(default user name is \"kdumpchecker\")" + exit 6 #ERR_CONFIGURED + fi +} + +## +# Check the target node is kdumping or not. +# arg1 : target node name. +# ret : 0 -> the target is kdumping. +# : 1 -> the target is _not_ kdumping. +# : else -> failed to check. +## +check_kdump() { + target_node="$1" + + # Get kdump check user name. + get_username + check_user_existence + exec_cmd="${SSH_COMMAND} -l ${USERNAME}" + + # Specify kdump check user's identity file for ssh command. + check_identity_file + exec_cmd="${exec_cmd} ${IDENTITY_OPTS}" + + # Now, check the target! + # In advance, Write the following setting at the head of + # kdump_check_user's public key in authorized_keys file on target node. + # command="test -s /proc/vmcore", \ + # no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty + ha_log.sh debug "execute the command [${exec_cmd} ${target_node}]." + ${exec_cmd} ${target_node} > /dev/null 2>&1 + ret=$? + ha_log.sh debug "the command's result is ${ret}." + + #ret -> 0 : vmcore file's size is not zero. the node is kdumping. + #ret -> 1 : the node is _not_ kdumping (vmcore didn't exist or + # its size is zero). It still needs to be STONITH'ed. + #ret -> 255 : ssh command is failed. + # else : Maybe command strings in authorized_keys is wrong... + return ${ret} +} + +### +# +# Main function. +# +### +case $1 in +gethosts) + check_hostlist + for hostname in ${hostlist} ; do + echo "${hostname}" + done + exit 0 + ;; +on) + # This plugin does only check whether a target node is kdumping or not. + exit 1 + ;; +reset|off) + check_hostlist + ret=1 + h_target=`echo $2 | tr A-Z a-z` + for hostname in ${hostlist} + do + hostname=`echo $hostname | tr A-Z a-z` + if [ "${hostname}" != "$h_target" ]; then + continue + fi + while [ 1 ] + do + check_kdump "$2" + ret=$? + if [ ${ret} -ne 255 ]; then + exit ${ret} + fi + #255 means ssh command itself is failed. + #For example, connection failure as if network doesn't start yet + #in 2nd kernel on the target node. + #So, retry to check after a little while. + sleep 1 + done + done + exit ${ret} + ;; +status) + check_hostlist + for hostname in ${hostlist} + do + if ping -w1 -c1 "${hostname}" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + get_username + check_user_existence + check_identity_file + exit 0 + ;; +getconfignames) + echo "hostlist identity_file" + exit 0 + ;; +getinfo-devid) + echo "kdump check STONITH device" + exit 0 + ;; +getinfo-devname) + echo "kdump check STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based kdump checker" + echo "To check whether a target node is dumping or not." + exit 0 + ;; +getinfo-devurl) + echo "kdump -> http://lse.sourceforge.net/kdump/" + echo "ssh -> http://openssh.org" + exit 0 + ;; +getinfo-xml) + cat << SSHXML + + + + +Hostlist + + +The list of hosts that the STONITH device controls + + + + + + +Identity file's full path for kdump check user + + +The full path of kdump check user's identity file for ssh command. +The identity in the specified file have to be restricted to execute +only the following command. +"test -s /proc/vmcore" +Default: kdump check user's default identity file path. +NOTE: You can specify kdump check user name in /etc/kdump.conf. + The parameter name is "kdump_check_user". + Default user is "kdumpchecker". + + + + +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/libvirt b/lib/plugins/stonith/external/libvirt new file mode 100644 index 0000000..494b048 --- /dev/null +++ b/lib/plugins/stonith/external/libvirt @@ -0,0 +1,298 @@ +#!/bin/sh +# +# External STONITH module for a libvirt managed hypervisor (kvm/Xen). +# Uses libvirt as a STONITH device to control guest. +# +# Copyright (c) 2010 Holger Teutsch +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# start a domain +libvirt_start() { + out=$($VIRSH -c $hypervisor_uri start $domain_id 2>&1) + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was started" + return 0 + fi + + $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 | + egrep -q '^State:.*(running|idle)|already active' + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id is already active" + return 0 + fi + + ha_log.sh err "Failed to start domain $domain_id" + ha_log.sh err "$out" + return 1 +} +# reboot a domain +# return +# 0: success +# 1: error +libvirt_reboot() { + local rc out + out=$($VIRSH -c $hypervisor_uri reboot $domain_id 2>&1) + rc=$? + if [ $rc -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was rebooted" + return 0 + fi + ha_log.sh err "Failed to reboot domain $domain_id (exit code: $rc)" + ha_log.sh err "$out" + return 1 +} + +# stop a domain +# return +# 0: success +# 1: error +# 2: was already stopped +libvirt_stop() { + out=$($VIRSH -c $hypervisor_uri destroy $domain_id 2>&1) + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id was stopped" + return 0 + fi + + $VIRSH -c $hypervisor_uri dominfo $domain_id 2>&1 | + egrep -q '^State:.*shut off|not found|not running' + if [ $? -eq 0 ] + then + ha_log.sh notice "Domain $domain_id is already stopped" + return 2 + fi + + ha_log.sh err "Failed to stop domain $domain_id" + ha_log.sh err "$out" + return 1 +} + +# get status of stonith device (*NOT* of the domain). +# If we can retrieve some info from the hypervisor +# the stonith device is OK. +libvirt_status() { + out=$($VIRSH -c $hypervisor_uri version 2>&1) + if [ $? -eq 0 ] + then + return 0 + fi + + ha_log.sh err "Failed to get status for $hypervisor_uri" + ha_log.sh err "$out" + return 1 +} + +# check config and set variables +# does not return on error +libvirt_check_config() { + VIRSH=`which virsh 2>/dev/null` + + if [ ! -x "$VIRSH" ] + then + ha_log.sh err "virsh not installed" + exit 1 + fi + + if [ -z "$hostlist" -o -z "$hypervisor_uri" ] + then + ha_log.sh err "hostlist or hypervisor_uri missing; check configuration" + exit 1 + fi + + case "$reset_method" in + power_cycle|reboot) : ;; + *) + ha_log.sh err "unrecognized reset_method: $reset_method" + exit 1 + ;; + esac +} + +# set variable domain_id for the host specified as arg +libvirt_set_domain_id () +{ + for h in $hostlist + do + case $h in + $1:*) + domain_id=`expr $h : '.*:\(.*\)'` + return + ;; + + $1) + domain_id=$1 + return + esac + done + + ha_log.sh err "Should never happen: Called for host $1 but $1 is not in $hostlist." + exit 1 +} + +libvirt_info() { +cat << LVIRTXML + + + + +List of hostname[:domain_id].. + + +List of controlled hosts: hostname[:domain_id].. +The optional domain_id defaults to the hostname. + + + + + + +Hypervisor URI + + +URI for connection to the hypervisor. +driver[+transport]://[username@][hostlist][:port]/[path][?extraparameters] +e.g. +qemu+ssh://my_kvm_server.mydomain.my/system (uses ssh for root) +xen://my_kvm_server.mydomain.my/ (uses TLS for client) + +virsh must be installed (e.g. libvir-client package) and access control must +be configured for your selected URI. + + + + + + +How to reset a guest. + + +A guest reset may be done by a sequence of off and on commands +(power_cycle) or by the reboot command. Which method works +depend on the hypervisor and guest configuration management. + + + +LVIRTXML +exit 0 +} + +############# +# Main code # +############# + +# don't fool yourself when testing with stonith(8) +# and transport ssh +unset SSH_AUTH_SOCK + +# support , as a separator as well +hostlist=`echo $hostlist| sed -e 's/,/ /g'` + +reset_method=${reset_method:-"power_cycle"} + +case $1 in + gethosts) + hostnames=`echo $hostlist|sed -e 's/:[^ ]*//g'` + for h in $hostnames + do + echo $h + done + exit 0 + ;; + + on) + libvirt_check_config + libvirt_set_domain_id $2 + + libvirt_start + exit $? + ;; + + off) + libvirt_check_config + libvirt_set_domain_id $2 + + libvirt_stop + [ $? = 1 ] && exit 1 + exit 0 + ;; + + reset) + libvirt_check_config + libvirt_set_domain_id $2 + + if [ "$reset_method" = "power_cycle" ]; then + libvirt_stop + [ $? = 1 ] && exit 1 + sleep 2 + libvirt_start + else + libvirt_reboot + fi + exit $? + ;; + + status) + libvirt_check_config + libvirt_status + exit $? + ;; + + getconfignames) + echo "hostlist hypervisor_uri reboot_method" + exit 0 + ;; + + getinfo-devid) + echo "libvirt STONITH device" + exit 0 + ;; + + getinfo-devname) + echo "libvirt STONITH external device" + exit 0 + ;; + + getinfo-devdescr) + echo "libvirt-based host reset for Xen/KVM guest domain through hypervisor" + exit 0 + ;; + + getinfo-devurl) + echo "http://libvirt.org/uri.html http://linux-ha.org/wiki" + exit 0 + ;; + + getinfo-xml) + libvirt_info + echo 0; + ;; + + *) + exit 1 + ;; +esac + +# vi:et:ts=4:sw=4 diff --git a/lib/plugins/stonith/external/nut b/lib/plugins/stonith/external/nut new file mode 100644 index 0000000..9e51bb8 --- /dev/null +++ b/lib/plugins/stonith/external/nut @@ -0,0 +1,302 @@ +#!/bin/sh + +# External STONITH module that uses the NUT daemon to control an external UPS. +# See the comments below, and the various NUT man pages, for how this +# script works. It should work unchanged with most modern "smart" APC UPSes in +# a Redhat/Fedora/RHEL-style distribution with the nut package installed. + +# Author: William Seligman +# License: GPLv2 + +# As you're designing your UPS and STONITH set-up, it may help to consider that +# there can be potentially three computers involved: +# 1) the machine running this STONITH module; +# 2) the machine being controlled by this STONITH module ($hostname); +# 3) the machine that can send commands to the UPS. + +# On my cluster, all the UPSes have SNMP smartcards, so every host can communicate +# with every UPS; in other words, machines (1) and (3) are the same. If your UPSes +# are controlled via serial or USB connections, then you might have a +# situation in which $hostname is plugged into a UPS, which has a serial connection +# to some master "power-control" computer, and can potentially be STONITHed +# by any other machine in your cluster. + +# In general, you'll probably need the nut daemon running on both the hosts (1) and +# (3) in the above list. The NUT daemon will also have to run on (2) if you want the +# reset command to gracefully reboot $hostname. + +# The NUT command default locations. In the RHEL-type nut packages, these binaries +# are in /usr/bin. +RHELUPSCMD="/usr/bin/upscmd" +RHELUPSC="/usr/bin/upsc" + +# Defaults for APC smart UPSes: + +# Reset = reboot $hostname; this will be a graceful reboot if the host +# is running NUT and monitoring $ups. +APCRESET="shutdown.return" + +# Poweroff = turn off $hostname immediately by cutting the power on $ups. +# For a graceful shutdown, use shutdown.stayoff instead of load.off, +# but it might take a few minutes to shutdown in this way. +APCPOWEROFF="load.off" + +# Poweron = turn on the power to $ups, which will presumably turn on $hostname. +# (Did you set $hostname's BIOS to boot up on AC power restore, as opposed to +# "last state"?) +APCPOWERON="load.on" + +# Status = returns a short string with the $ups status; OL = on-line, OFF = off-line, etc. +APCSTATUSVAR="ups.status" + + +# Stick in the defaults, if needed. +if [ -z "${poweron}" ]; then + poweron=${APCPOWERON} +fi +if [ -z "${poweroff}" ]; then + poweroff=${APCPOWEROFF} +fi +if [ -z "${reset}" ]; then + reset=${APCRESET} +fi +if [ -z "${statusvar}" ]; then + statusvar=${APCSTATUSVAR} +fi +if [ -z "${upscmd}" ]; then + upscmd=${RHELUPSCMD} +fi +if [ -z "${upsc}" ]; then + upsc=${RHELUPSC} +fi + + +# Define the command to fetch the UPS status. +STATUSCMD="${upsc} ${ups} ${statusvar}" + +usage() { + echo "Usage: $0 {on|off|reset|status|gethosts|getconfignames|getinfo-devid|getinfo-devname|getinfo-devdescr|getinfo-devurl|getinfo-xml}" +} + +# Can we find the NUT binary? +have_nut() { + test -x "${upscmd}" +} +have_upsc() { + test -x "${upsc}" +} + +do_nut() { + have_nut || { + echo "Can't find NUT upscmd command" + return 1 + } + if [ -z "${username}" -o -z "${password}" -o -z "${ups}" ]; then + echo "username, password or ups name missing; check configuration" + return 1 + fi + # Execute the command given in argument 1. + ${upscmd} -u ${username} -p ${password} ${ups} ${1} || { + echo "error executing nut command" + return 1 + } +} + +case ${1} in +gethosts) + echo ${hostname} + exit 0 + ;; +on) + result=1 + do_nut "${poweron}" + result=$? + exit ${result} + ;; +off) + result=1 + do_nut "${poweroff}" + result=$? + exit ${result} + ;; +reset) + result=1 + do_nut "${reset}" + result=$? + exit $result + ;; +status) + have_upsc || { + echo "Can't find NUT upsc command" + exit 1 + } + ${STATUSCMD} + exit $? + ;; +getconfignames) + echo "hostname ups username password poweron poweroff reset statusvar upscmd upsc" + exit 0 + ;; +getinfo-devid) + echo "NUT STONITH device" + exit 0 + ;; +getinfo-devname) + echo "NUT STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "A STONITH device based on NUT (Network UPS Tools)." + echo " " + echo "For this STONITH script to work, the following conditions have" + echo "to be met:" + echo " " + echo "- NUT has to be installed on both the host running this script" + echo " and the host that controls the UPS (on RHEL systems, NUT is" + echo " in packages nut and nut-client) and the nut daemon services" + echo " (normally called the ups or upsd service) must be running" + echo " on both systems." + echo " " + echo "- The UPS name has to be defined in ups.conf on the host" + echo " that controls the UPS." + echo " " + echo "- The username/password to access the UPS must be defined in" + echo " upsd.users on the host that controls the UPS, with the instcmds" + echo " for poweron, poweroff, and reset allowed." + echo " " + echo "- The host that is running this script must be allowed access" + echo " via upsd.conf and upsd.users on the host the controls the UPS." + echo " " + echo "On RHEL systems, the files listed above are in /etc/ups." + echo " " + echo "The defaults will probably work with APC UPS devices. It might" + echo "work on others; 'upscmd -l (ups)' and 'upsc (ups)' will list" + echo "the commands and variables, and you can change the values" + echo "for poweron, poweroff, reset, and statusvar to suit your UPS." + echo "Change upscmd and upsc if your NUT binaries are not in /usr/bin." + exit 0 + ;; +getinfo-devurl) + echo "http://www.networkupstools.org/" + exit 0 + ;; +getinfo-xml) +cat << nutXML + + + + +Hostname + +The name of the host to be managed by this STONITH device. +The nut daemon must be running on the host controllng the +UPS _and_ on the host running this script; this script does +not start/stop the daemons for you. + + + + + +UPS name + +The name of the UPS as defined in ups.conf on the host +controlling the UPS. The format for this option is +upsname[@controlhost[:port]]. The default controlhost is +"localhost". + + + + + +Username + +The username used for accessing the UPS. This is defined in +upsd.conf on the host controlling the UPS. + + + + + +Password + +The password used for logging in to the UPS for the host +controlling the UPS, as defined in upsd.conf on that host. + + + + + +UPS Power On command + +The NUT hardware command to turn on the UPS. The default +should work for most "smart" APC UPSes. For a list of +commands that your UPS can support, type 'upscmd -l (ups)' +on the command line. + + + + +UPS Power Off command + +The NUT hardware command to turn off the UPS. On most APC +"smart" UPSes, the command shutdown.stayoff will result +in a graceful shutdown, provided the host is running the +nut daemon, but this might take a few minutes; load.off +will cut the power immediately. For a list of commands that +your UPS can support, type 'upscmd -l (ups)' on the command +line. + + + + + +UPS Reset command + +The NUT hardware command to reset the host. On most APC +"smart" UPSes, the command shutdown.return will result +in a graceful shutdown, with power restored after perhaps +a short interval. For a list of commands that your UPS can + support, type 'upscmd -l (ups)' on the command line. + + + + + +UPS Status variable + +The NUT variable that returns the status of the UPS. On APC +UPSes, the value of ups.status will be "OL" if the UPS is +"on-line." For a list of variables that your UPS supports, +type 'upsc (ups)' on the command line. + + + + + +upscmd binary location + +The full path to the NUT binary command 'upscmd'. On RHEL +systems with the nut RPM installed, this location is +/usr/bin/upscmd. + + + + + +upsc binary location + +The full path to the NUT binary command 'upsc'. On RHEL +systems with the nut RPM installed, this location is +/usr/bin/upsc. + + + + +nutXML +exit 0 +;; +*) + usage + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/rackpdu b/lib/plugins/stonith/external/rackpdu new file mode 100644 index 0000000..7d0e20b --- /dev/null +++ b/lib/plugins/stonith/external/rackpdu @@ -0,0 +1,280 @@ +#!/bin/sh +# +# External STONITH module for APC Switched Rack PDU +# +# Copyright (c) 2008 Sergey Maznichenko +# Version 1.2 +# +# See http://www.it-consultant.su/rackpdu +# for additional information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SWITCH_ON="1" +SWITCH_OFF="2" +SWITCH_RESET="3" + +DEFAULT_NAMES_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2" +DEFAULT_COMMAND_OID=".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4" + +if [ -z "$oid" ]; then + oid=$DEFAULT_COMMAND_OID +fi + +if [ -z "$names_oid" ]; then + names_oid=$DEFAULT_NAMES_OID +fi + +if [ -z "$outlet_config" ]; then + outlet_config="none" +fi + +GetOutletNumber() { + local nodename=$1 + + if [ "$outlet_config" != "none" ]; then + # Get outlet number from file + + if [ -f "$outlet_config" ]; then + local outlet_num=`grep $nodename $outlet_config | tr -d ' ' | cut -f2 -d'='` + if [ -z "$outlet_num" ]; then + ha_log.sh err "Outlet number not found for node $nodename. Check configuration file $outlet_config" + return 0 + fi + return $outlet_num + else + ha_log.sh err "File $outlet_config not found." + return 0 + fi + else + # Get outlet number from device + + local outlet_num=1 + local snmp_result + snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result" + return 0 + fi + + local names + names=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '` + for name in $names; do + if [ "$name" != "$nodename" ]; then + local outlet_num=`expr $outlet_num + 1` + continue + fi + + return $outlet_num + done + + ha_log.sh err "Outlet number not found for node $nodename. Result: $snmp_result" + return 0 + fi +} + +SendCommand() { + + local host=$1 + local command=$2 + + GetOutletNumber $host + local outlet=$? + + if [ $outlet -gt 0 ]; then + local set_result + set_result=`snmpset -v1 -c $community $pduip $oid.$outlet i $command 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command failed. Result: $set_result" + return 1 + fi + if echo "$set_result" | grep -qs "Timeout"; then + ha_log.sh err "Write SNMP to $pduip value $oid.$outlet=$command timed out. Result: $set_result" + return 1 + fi + return 0 + else + return 1 + fi +} + +hostlist=`echo $hostlist | tr ',' ' '` +incommand=$1 +innode=$2 + +case $incommand in +gethosts) + if [ "$hostlist" = "AUTO" ]; then + snmp_result=`snmpwalk -v1 -c $community $pduip $names_oid 2>&1` + if [ $? -ne 0 ]; then + ha_log.sh err "snmpwalk $community $pduip $names_oid failed. Result: $snmp_result" + exit 1 + fi + if echo "$snmp_result" | grep -qs "Timeout"; then + ha_log.sh err "snmpwalk $community $pduip $names_oid timed out. Result: $snmp_result" + exit 1 + else + hostlist=`echo "$snmp_result" | cut -f2 -d'"' | tr ' ' '_' | tr '\012' ' '` + fi + fi + + for h in $hostlist ; do + echo $h + done + + exit 0 + ;; +on) + if + SendCommand $innode $SWITCH_ON + then + exit 0 + else + exit 1 + fi + ;; +off) + if + SendCommand $innode $SWITCH_OFF + then + exit 0 + else + exit 1 + fi + ;; +reset) + if + SendCommand $innode $SWITCH_RESET + then + exit 0 + else + exit 1 + fi + ;; +status) + if [ -z "$pduip" ]; then + exit 1 + fi + + if ping -w1 -c1 $pduip >/dev/null 2>&1; then + exit 0 + else + exit 1 + fi + ;; +getconfignames) + echo "hostlist pduip community" + exit 0 + ;; +getinfo-devid) + echo "rackpdu STONITH device" + exit 0 + ;; +getinfo-devname) + echo "rackpdu STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "APC Switched Rack PDU" + exit 0 + ;; +getinfo-devurl) + echo "http://www.apcc.com/products/family/index.cfm?id=30" + exit 0 + ;; +getinfo-xml) + cat << PDUXML + + + + Hostlist + +The list of hosts that the STONITH device controls (comma or space separated). +If you set value of this parameter to AUTO, list of hosts will be get from Rack PDU device. + + + + + + Name or IP address of Rack PDU device. + Name or IP address of Rack PDU device. + + + + + Name of write community. + Name of write community. + + + + + + The OID without the outlet number. + + +The SNMP OID for the PDU. minus the outlet number. +Try .1.3.6.1.4.1.318.1.1.12.3.3.1.1.4 (default value) +or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/ +Varies on different APC hardware and firmware. +Warning! No dot at the end of OID + + + + + + The OID for getting names of outlets. + +The SNMP OID for getting names of outlets. +It is required to recognize outlet number by nodename. +Try ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.2" (default value) +or use mib from ftp://ftp.apcc.com/apc/public/software/pnetmib/mib/ +Names of nodes must be equal names of outlets, in other way use outlet_config parameter. +If you set 'names_oid' parameter then parameter outlet_config must not be use. +Varies on different APC hardware and firmware. +Warning! No dot at the end of OID + + + + + + Configuration file. Other way to recognize outlet number by nodename. + +Configuration file. Other way to recognize outlet number by nodename. +Configuration file which contains +node_name=outlet_number +strings. + +Example: +server1=1 +server2=2 + +If you use outlet_config parameter then names_oid parameter can have any value and it is not uses. + + + + +PDUXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/riloe b/lib/plugins/stonith/external/riloe new file mode 100644 index 0000000..ce98847 --- /dev/null +++ b/lib/plugins/stonith/external/riloe @@ -0,0 +1,530 @@ +#!/usr/bin/env python +# +# Stonith module for RILOE Stonith device +# +# Copyright (c) 2004 Alain St-Denis +# +# Modified by Alan Robertson for STONITH external compatibility. +# +# Extended and merged by Tijl Van den broeck +# with ilo-v2 script from Guy Coates +# +# Cleanup by Andrew Beekhof +# +# Rewritten by Dejan Muhamedagic +# Now, the plugin actually reads replies from iLO. +# +# Extended by Jochen Roeder +# to enable access via proxies +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +import sys +import os +import socket +import subprocess +import xml.dom.minidom +import httplib +import time +import re + +def log_msg(level,msg): + subprocess.call("ha_log.sh %s '%s'" % (level,msg), shell=True) +def my_err(msg): + log_msg("err", msg) +def my_warn(msg): + log_msg("warn", msg) +def my_debug(msg): + log_msg("debug", msg) +def fatal(msg): + my_err(msg) + sys.exit(1) + +argv = sys.argv + +try: + cmd = argv[1] +except IndexError: + my_err("Not enough arguments") + sys.exit(1) + +legacy_RI_HOST = os.environ.get('RI_HOST', '') +legacy_RI_HOSTRI = os.environ.get('RI_HOSTRI', '') +legacy_RI_LOGIN = os.environ.get('RI_LOGIN', 'Administrator') +legacy_RI_PASSWORD = os.environ.get('RI_PASSWORD', '') + +reset_ok = os.environ.get('ilo_can_reset', '0') +ilo_protocol = os.environ.get('ilo_protocol', '1.2') +power_method = os.environ.get('ilo_powerdown_method', 'power') + +realhost = os.environ.get('hostlist', legacy_RI_HOST) +rihost = os.environ.get('ilo_hostname', legacy_RI_HOSTRI) +ilouser = os.environ.get('ilo_user', legacy_RI_LOGIN) +ilopass = os.environ.get('ilo_password', legacy_RI_PASSWORD) +iloproxyhost = os.environ.get('ilo_proxyhost', '') +try: + iloproxyport = int(os.environ.get('ilo_proxyport', 3128)) +except ValueError: + my_err("ilo_proxyport is not a number") + sys.exit(1) + +xmlinfo = ''' + + + ilo target hostname + + Contains the hostname that the ilo controls + + + + + ilo device hostname + + The hostname of the ilo device + + + + + ilo user + + The user for connecting to the ilo device + + + + + password + + The password for the ilo device user + + + + + Device can reset + + Does the ILO device support RESET commands (hint: older ones cannot) + + + + + ILO Protocol + + Protocol version supported by the ILO device. + Known supported versions: 1.2, 2.0 + + + + + Power down method + + The method to powerdown the host in question. + * button - Emulate holding down the power button + * power - Emulate turning off the machines power + + NB: A button request takes around 20 seconds. The power method + about half a minute. + + + + + Proxy hostname + + proxy hostname if required to access ILO board + + + + + Proxy port + + proxy port if required to access ILO board + parameter will be ignored if proxy hostname is not set + + + +''' + +info = { + 'getinfo-devid': 'iLO2', + 'getinfo-devname': 'ilo2 ' + rihost, + 'getinfo-devdescr': 'HP/COMPAQ iLO2 STONITH device', + 'getinfo-devurl': 'http://www.hp.com/', + 'gethosts': realhost, + 'getinfo-xml': xmlinfo +} + +if cmd in info: + print info[cmd] + sys.exit(0) + +if cmd == 'getconfignames': + for arg in [ "hostlist", "ilo_hostname", "ilo_user", "ilo_password", "ilo_can_reset", "ilo_protocol", "ilo_powerdown_method", "ilo_proxyhost", "ilo_proxyport"]: + print arg + sys.exit(0) + +if not rihost: + fatal("ILO device hostname not specified") + +if not realhost: + fatal("Host controlled by this ILO device not specified") + +if not power_method in ("power","button"): + my_err('unknown power method %s, setting to "power"') + power_method = "power" + +# XML elements +E_RIBCL = "RIBCL" +E_LOGIN = "LOGIN" +E_SERVER_INFO = "SERVER_INFO" + +# power mgmt methods +E_RESET = "RESET_SERVER" # error if powered off +E_COLD_BOOT = "COLD_BOOT_SERVER" # error if powered off +E_WARM_BOOT = "WARM_BOOT_SERVER" # error if powered off +E_PRESS_BUTTON = "PRESS_PWR_BTN" +E_HOLD_BUTTON = "HOLD_PWR_BTN" + +# get/set status elements +E_SET_POWER = "SET_HOST_POWER" +E_GET_PSTATUS = "GET_HOST_POWER_STATUS" + +# whatever this means, but we have to use it to get good XML +E_LOCFG = "LOCFG" +LOCFG_VER = '2.21' + +# attributes +A_VERSION = "VERSION" # ilo_protocol +A_USER = "USER_LOGIN" +A_PWD = "PASSWORD" +A_MODE = "MODE" # info mode (read or write) +A_POWER_SW = "HOST_POWER" # "Y" or "N" +A_POWER_STATE = "HOST_POWER" # "ON" or "OFF" + +def new_power_req(tag, name = None, value = None): + ''' + Create a new RIBCL request (as XML). + ''' + my_debug("creating power request: %s,%s,%s"%(tag,name,value)) + doc = xml.dom.minidom.Document() + locfg = doc.createElement(E_LOCFG) + locfg.setAttribute(A_VERSION,LOCFG_VER) + ribcl = doc.createElement(E_RIBCL) + ribcl.setAttribute(A_VERSION,ilo_protocol) + login = doc.createElement(E_LOGIN) + login.setAttribute(A_USER,ilouser) + login.setAttribute(A_PWD,ilopass) + serv_info = doc.createElement(E_SERVER_INFO) + # read or write, it doesn't really matter, i.e. even if we + # say "write" that doesn't mean we can't read + serv_info.setAttribute(A_MODE,"write") + doc.appendChild(locfg) + locfg.appendChild(ribcl) + ribcl.appendChild(login) + login.appendChild(serv_info) + el_node = doc.createElement(tag) + if name: + el_node.setAttribute(name,value) + serv_info.appendChild(el_node) + s = doc.toprettyxml() + doc.unlink() + # work around an iLO bug: last line containing "" + # produces a syntax error + lines = s.split('\n') + return '\n'.join(lines[:-2]) + +E_RESPONSE = "RESPONSE" +E_HOST_POWER = "GET_HOST_POWER" +A_STATUS = "STATUS" +# documentation mentions both; better safe than sorry +A_MSG = "MSG" +A_MSG2 = "MESSAGE" + +def is_element(xmlnode): + return xmlnode.nodeType == xmlnode.ELEMENT_NODE + +def read_resp(node): + ''' + Check if the RESPONSE XML is OK. + ''' + msg = "" + str_status = "" + for attr in node.attributes.keys(): + if attr == A_STATUS: + str_status = node.getAttribute(attr) + elif attr == A_MSG: + msg = node.getAttribute(attr) + elif attr == A_MSG2: + msg = node.getAttribute(attr) + else: + my_warn("unexpected attribute %s in %s" % (attr,E_RESPONSE)) + if not str_status: + my_err("no status in response") + return -1 + try: + status = int(str_status,16) + except ValueError: + my_err("unexpected status %s in response" % str_status) + return -1 + if status != 0: + my_err("%s (rc: %s)"%(msg,str_status)) + return -1 + return 0 + +def read_power(node): + ''' + Read the power from the XML node. Set the global power + variable correspondingly. + ''' + global power + for attr in node.attributes.keys(): + if attr == A_POWER_STATE: + power_state = node.getAttribute(attr).upper() + else: + my_warn("unexpected attribute %s in %s" % (attr,node.tagName)) + if not power_state: + my_err("no %s attribute in %s" % (A_POWER_STATE,node.tagName)) + return -1 + if power_state not in ("ON","OFF"): + my_err("unexpected value for %s: %s" % (A_POWER_STATE,power_state)) + return -1 + power = (power_state == "ON") + my_debug("Host has power: %s"%power) + return 0 + +el_parsers = { + E_RESPONSE:read_resp, + E_HOST_POWER:read_power +} +def proc_resp(doc): + ''' + Process one iLO reply. Real work is done in el_parsers. + ''' + ribcl = doc.childNodes[0] + if not is_element(ribcl) or ribcl.tagName != E_RIBCL: + my_err("unexpected top element in response") + return -1 + for child in ribcl.childNodes: + if not is_element(child): + continue + if child.tagName in el_parsers: + rc = el_parsers[child.tagName](child) + if rc != 0: + return -1 + else: + my_warn("unexpected element in response: %s" % child.toxml()) + return 0 + +def open_ilo(host): + # open https connection + try: + if iloproxyhost != "" and iloproxyport != 0: + proxy=socket.socket(socket.AF_INET,socket.SOCK_STREAM) + proxy.connect((iloproxyhost, iloproxyport)) + proxy_connect='CONNECT %s:%s HTTP/1.1\r\n'%(host,443) + user_agent='User-Agent: python\r\n' + proxy_pieces=proxy_connect+user_agent+'\r\n' + proxy.sendall(proxy_pieces) + response=proxy.recv(8192) + status=response.split()[1] + if status!=str(200): + fatal("Error status=: %s" %(response)) + import ssl + sock = ssl.wrap_socket(proxy) + h=httplib.HTTPConnection('localhost') + h.sock=sock + return h + else: + return httplib.HTTPSConnection(host) + except socket.gaierror, msg: + fatal("%s: %s" %(msg,host)) + except socket.sslerror, msg: + fatal("%s for %s" %(msg,host)) + except socket.error, msg: + fatal("%s while talking to %s" %(msg,host)) + except ImportError, msg: + fatal("ssl support missing (%s)" %msg) + +def send_request(req,proc_f): + ''' + 1. After every request, the iLO closes the connection. + 2. For every request, there are multiple replies. Each reply + is an XML document. Most of replies are just a kind of + (verbose) XML "OK". + ''' + t_begin = time.time() + c = open_ilo(rihost) + try: + c.send(req+'\r\n') + except socket.error, msg: + fatal("%s, while talking to %s" %(msg,rihost)) + t_end = time.time() + my_debug("request sent in %0.2f s" % ((t_end-t_begin))) + + t_begin = time.time() + result = [] + while True: + try: + reply = c.sock.recv(1024) + if not reply: + break + result.append(reply) + except socket.error, msg: + if msg[0] == 6: # connection closed + break + my_err("%s, while talking to %s" %(msg,rihost)) + return -1 + c.close() + t_end = time.time() + + if not result: + fatal("no response from %s within %0.2f s"%(rihost,(t_end-t_begin))) + for reply in result: + # work around the iLO bug, i.e. element RIBCL closed twice + if re.search("", reply): + reply = re.sub("<(RIBCL.*)/>", r"<\1>", reply) + try: + doc = xml.dom.minidom.parseString(reply) + except xml.parsers.expat.ExpatError,msg: + fatal("malformed response: %s\n%s"%(msg,reply)) + rc = proc_f(doc) + doc.unlink() + if rc != 0: + break + my_debug("iLO processed request (rc=%d) in %0.2f s" % (rc,(t_end-t_begin))) + return rc + +def manage_power(cmd): + ''' + Before trying to send a request we have to check the power + state. + ''' + rc = 0 + req = '' + # it won't do to turn it on if it's already on! + if cmd == "on" and not power: + req = new_power_req(E_SET_POWER,A_POWER_SW,"Y") + # also to turn it off if it's already off + elif cmd == "off" and power: + req = new_power_req(E_SET_POWER,A_POWER_SW,"N") + elif cmd == "cold_boot" and power: + req = new_power_req(E_COLD_BOOT) + elif cmd == "warm_boot" and power: + req = new_power_req(E_WARM_BOOT) + elif cmd == "reset": + if power: + req = new_power_req(E_RESET) + # reset doesn't work if the host's off + else: + req = new_power_req(E_SET_POWER,A_POWER_SW,"Y") + if req: + rc = send_request(req,proc_resp) + return rc +def power_on(): + ''' + Update the power variable without checking the power state. + The iLO is slow at times to report the actual power state, so + we assume that it changed if the request succeeded. + ''' + rc = manage_power("on") + if rc == 0: + global power + power = True + return rc +def power_off(): + rc = manage_power("off") + if rc == 0: + global power + power = False + return rc +def cold_boot(): + rc = manage_power("cold_boot") + return rc +def warm_boot(): + rc = manage_power("warm_boot") + return rc +def reset(): + rc = manage_power("reset") + if rc == 0: + global power + power = True + return rc +def hold_button(): + ''' + Hold the power button. Got this error message when tried + without the TOGGLE attribute: + Command without TOGGLE="Yes" attribute is ignored + when host power is off. (rc: 0x0054) + Didn't find any documentation about TOGGLE. + ''' + if power: + req = new_power_req(E_HOLD_BUTTON) + else: + req = new_power_req(E_HOLD_BUTTON,"TOGGLE","Yes") + rc = send_request(req,proc_resp) + return rc +def read_power_state(): + req = new_power_req(E_GET_PSTATUS) + rc = send_request(req,proc_resp) + return rc + +def reset_power(): + ''' + Three methods to reset: + - hold power button + - reset (only if host has power and user said that reset is ok) + - power off/on + ''' + do_power_on = False + if power_method == 'button': + rc = hold_button() + elif reset_ok != '0': + if power: + return reset() + else: + return power_on() + else: + do_power_on = True + rc = power_off() + if rc == 0: + rc = read_power_state() + if do_power_on: + while rc == 0 and power: # wait for the power state to go off + time.sleep(5) + rc = read_power_state() + if rc == 0 and do_power_on and not power: + rc = power_on() + return rc + +# track state of host power +power = -1 + +todo = { +'reset':reset_power, +'on':power_on, +'off':power_off, +'cold':cold_boot, +'warm':warm_boot, +'status':lambda: 0 # just return 0, we already read the state +} + +rc = read_power_state() +if rc == 0: + if cmd in todo: + rc = todo[cmd]() + else: + fatal('Invalid command: %s' % cmd) +if rc != 0: + fatal("request failed") +sys.exit(rc) + +# vi:ts=4:sw=4:et: diff --git a/lib/plugins/stonith/external/ssh.in b/lib/plugins/stonith/external/ssh.in new file mode 100644 index 0000000..2a8eb73 --- /dev/null +++ b/lib/plugins/stonith/external/ssh.in @@ -0,0 +1,176 @@ +#!/bin/sh +# +# External STONITH module for ssh. +# +# Copyright (c) 2004 SUSE LINUX AG - Lars Marowsky-Bree +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +SSH_COMMAND="@SSH@ -q -x -o PasswordAuthentication=no -o StrictHostKeyChecking=no -n -l root" +#SSH_COMMAND="@SSH@ -q -x -n -l root" + +REBOOT_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1" + +# Warning: If you select this poweroff command, it'll physically +# power-off the machine, and quite a number of systems won't be remotely +# revivable. +# TODO: Probably should touch a file on the server instead to just +# prevent heartbeat et al from being started after the reboot. +# POWEROFF_COMMAND="echo 'sleep 2; /sbin/poweroff -nf' | SHELL=/bin/sh at now >/dev/null 2>&1" +POWEROFF_COMMAND="echo 'sleep 2; @REBOOT@ @REBOOT_OPTIONS@' | SHELL=/bin/sh at now >/dev/null 2>&1" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +is_host_up() { + for j in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + do + if + ping -w1 -c1 "$1" >/dev/null 2>&1 + then + sleep 1 + else + return 1 + fi + done + return 0 +} + + +case $1 in +gethosts) + for h in $hostlist ; do + echo $h + done + exit 0 + ;; +on) + # Can't really be implemented because ssh cannot power on a system + # when it is powered off. + exit 1 + ;; +off) + # Shouldn't really be implemented because if ssh cannot power on a + # system, it shouldn't be allowed to power it off. + exit 1 + ;; +reset) + h_target=`echo $2 | tr A-Z a-z` + for h in $hostlist + do + h=`echo $h | tr A-Z a-z` + [ "$h" != "$h_target" ] && + continue + if + case ${livedangerously} in + [Yy]*) is_host_up $h;; + *) true;; + esac + then + $SSH_COMMAND "$2" "$REBOOT_COMMAND" + # Good thing this is only for testing... + if + is_host_up $h + then + exit 1 + else + exit 0 + fi + else + # well... Let's call it successful, after all this is only for testing... + exit 0 + fi + done + exit 1 + ;; +status) + if + [ -z "$hostlist" ] + then + exit 1 + fi + for h in $hostlist + do + if + ping -w1 -c1 "$h" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + exit 0 + ;; +getconfignames) + echo "hostlist" + exit 0 + ;; +getinfo-devid) + echo "ssh STONITH device" + exit 0 + ;; +getinfo-devname) + echo "ssh STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based host reset" + echo "Fine for testing, but not suitable for production!" + echo "Only reboot action supported, no poweroff, and, surprisingly enough, no poweron." + exit 0 + ;; +getinfo-devurl) + echo "http://openssh.org" + exit 0 + ;; +getinfo-xml) + cat << SSHXML + + + + +Hostlist + + +The list of hosts that the STONITH device controls + + + + + + +Live Dangerously!! + + +Set to "yes" if you want to risk your system's integrity. +Of course, since this plugin isn't for production, using it +in production at all is a bad idea. On the other hand, +setting this parameter to yes makes it an even worse idea. +Viva la Vida Loca! + + + + +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/vcenter b/lib/plugins/stonith/external/vcenter new file mode 100755 index 0000000..71a6302 --- /dev/null +++ b/lib/plugins/stonith/external/vcenter @@ -0,0 +1,280 @@ +#!/usr/bin/env perl +# +# External STONITH module for VMWare vCenter/ESX +# +# Author: Nhan Ngo Dinh +# License: GNU General Public License (GPL) +# + +require 5.010; + +use strict; +use warnings; + +sub dielog { + my $msg = "["; + $msg .= "$ARGV[0]" if defined($ARGV[0]); + $msg .= " $ARGV[1]" if defined($ARGV[1]); + $msg .= "]"; + ( $_ ) = @_; + $msg .= " $_"; + system("ha_log.sh", "err", "$msg"); + die(); +} + +# Define command groups +my @configCommands = qw{getconfignames getinfo-devid getinfo-devname getinfo-devdescr getinfo-devurl getinfo-xml}; +my @actionCommands = qw{reset on off}; +my @netCommands = (@actionCommands, qw{status gethosts listvms}); + +# Process command line arguments +my $command = $ARGV[0] || dielog("No command specified\n"); + +# Command belongs to the group of commands that do not require any connection to VMware vCenter +if ($command ~~ @configCommands) { + if ($command eq "getconfignames") { + print "VI_SERVER\nVI_PORTNUMBER\nVI_PROTOCOL\nVI_SERVICEPATH\nVI_CREDSTORE\nHOSTLIST\nRESETPOWERON\n"; + } + elsif ($command eq "getinfo-devid") { + print "VMware vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devname") { + print "VMware vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devdescr") { + print "VMWare vCenter STONITH device\n"; + } + elsif ($command eq "getinfo-devurl") { + print "http://www.vmware.com/\n"; + } + elsif ($command eq "getinfo-xml") { + print q{ + + +List of hosts and virtual machines (required) + +The list of hosts that the VMware vCenter STONITH device controls. +Syntax is: + hostname1[=VirtualMachineName1];hostname2[=VirtualMachineName2] + +NOTE: omit =VirtualMachineName if hostname and virtual machine names are identical + +Example: + cluster1=VMCL1;cluster2=VMCL2 + + + + +VMware vCenter address + +The VMware vCenter address + + + + +VMware vCenter protocol + +The VMware vCenter protocol + + + + +VMware vCenter port number + +The VMware vCenter port number + + + + +VMware vCenter service path + +The VMware vCenter services path + + + + +VMware vCenter credentials store file + +VMware vCenter credentials store file + + + + +PowerOnVM on reset + +Enable/disable a PowerOnVM on reset when the target virtual machine is off +Allowed values: 0, 1 + + + + +Enable or disable SSL hostname verification + +To disable SSL hostname verification set this option to 0. +To enable hostname verification, set this option to 1. +This option is actually part of the LWP Perl library. +See LWP(3pm) for more information. + + +} . "\n"; + } + else { dielog("Invalid command specified: $command\n"); } +} + +# Command belongs to the group of commands that require connecting to VMware vCenter +elsif ($command ~~ @netCommands) { + + eval { require VMware::VIRuntime; } + or dielog("Missing perl module VMware::VIRuntime. Download and install 'VMware Infrastructure (VI) Perl Toolkit', available at http://www.vmware.com/support/developer/viperltoolkit/ \n"); + + # A valid VI_CREDSTORE is required to avoid interactive prompt + ( exists $ENV{'VI_CREDSTORE'} ) || dielog("VI_CREDSTORE not specified\n"); + + # HOSTLIST is mandatory + exists $ENV{'HOSTLIST'} || dielog("HOSTLIST not specified\n"); + + # Parse HOSTLIST to %host_to_vm and %vm_to_host + my @hostlist = split(';', $ENV{'HOSTLIST'}); + my %host_to_vm = (); + my %vm_to_host = (); + foreach my $host (@hostlist) { + my @config = split(/=/, $host); + my $key = $config[0]; my $value = $config[1]; + if (!defined($value)) { $value = $config[0]; } + $host_to_vm{$key} = $value; + $vm_to_host{(lc $value)} = $key; + } + + eval { + # VI API: reads options from the environment variables into appropriate data structures for validation. + Opts::parse(); + # VI API: ensures that input values from environment variable are complete, consistent and valid. + Opts::validate(); + # VI API: establishes a session with the VirtualCenter Management Server or ESX Server Web service + Util::connect(); + }; + if ($@) { + # This is just a placeholder for any error handling procedure + dielog($@); + } + + # Command belongs to the group of commands that performs actions on Virtual Machines + if ($command ~~ @actionCommands) { + + my $targetHost = $ARGV[1] || dielog("No target specified\n"); + + # Require that specified target host exists in the specified HOSTLIST + if (exists $host_to_vm{$targetHost}) { + + my $vm; + my $esx; + eval { + # VI API: searches the inventory tree for a VirtualMachine managed entity whose name matches + # the name of the virtual machine assigned to the target host in HOSTLIST + $vm = Vim::find_entity_view(view_type => "VirtualMachine", filter => { name => qr/^\Q$host_to_vm{$targetHost}\E/i }); + if (!defined $vm) { + dielog("Machine $targetHost was not found"); + } + + # VI API: retrieves the properties of the managed object reference runtime.host of the VirtualMachine + # managed entity obtained by the previous command + # NOTE: This is essentially a workaround to vSphere Perl SDK + # to allow pointing to the right HostSystem. This is probably + # done by changing the current HostSystem in the Web Service + # session context. WARNING: Do not use the same session for any + # other concurrent operation. + $esx = Vim::get_view(mo_ref => $vm->{"runtime"}{"host"})->name; + }; + if ($@) { + if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); } + dielog($@); + } + + my $powerState = $vm->get_property('runtime.powerState')->val; + if ($powerState eq "suspended") { + # This implementation assumes that suspending a cluster node can cause + # severe failures on shared resources, thus any failover operation should + # be blocked. + dielog("Machine $esx:$vm->{'name'} is in a suspended state\n"); + } + + eval { + if ($command eq "reset") { + if ($powerState eq "poweredOn") { + $vm->ResetVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been reset"); + } else { + system("ha_log.sh", "warn", "Tried to ResetVM $esx:$vm->{'name'} that was $powerState"); + # Start a virtual machine on reset only if explicitly allowed by RESETPOWERON + if ($powerState eq "poweredOff" && (! exists $ENV{'RESETPOWERON'} || $ENV{'RESETPOWERON'} ne 0)) { + $vm->PowerOnVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on"); + } else { + dielog("Could not complete $esx:$vm->{'name'} power cycle"); + } + } + } + elsif ($command eq "off") { + if ($powerState eq "poweredOn") { + $vm->PowerOffVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered off"); + } else { + system("ha_log.sh", "warn", "Tried to PowerOffVM $esx:$vm->{'name'} that was $powerState"); + + } + } + elsif ($command eq "on") { + if ($powerState eq "poweredOff") { + $vm->PowerOnVM(); + system("ha_log.sh", "info", "Machine $esx:$vm->{'name'} has been powered on"); + } else { + system("ha_log.sh", "warn", "Tried to PowerOnVM $esx:$vm->{'name'} that was $powerState"); + } + } + else { dielog("Invalid command specified: $command\n"); } + }; + if ($@) { + if (ref($@) eq "SoapFault") { dielog("$@->detail\n"); } + dielog($@); + } + + } else { dielog("Invalid target specified\n"); } + } else { + # Command belongs to the group of commands that lookup the status of VMware vCenter and/or virtual machines + if ($command eq "status") { + # we already connect to the vcenter, no need to do + # anything else in status + ; + } + elsif ($command eq "gethosts") { + foreach my $key (keys(%host_to_vm)) { + print "$key \n"; + } + } + elsif ($command eq "listvms") { + eval { + # VI API: Searches the inventory tree for all VirtualMachine managed objects + my $vms = Vim::find_entity_views(view_type => "VirtualMachine"); + if (defined $vms) { + printf(STDERR "%-50s %-20s\n", "VM Name", "Power state"); + print STDERR "-" x 70 . "\n"; + foreach my $vm (@$vms) { + my $powerState = $vm->get_property('runtime.powerState')->val; + printf("%-50s %-20s\n", $vm->{name}, $powerState); + } + } + }; + } + else { dielog("Invalid command specified: $command\n"); } + } + eval { + Util::disconnect(); + }; + if ($@) { + # This is just a placeholder for any error handling procedure + dielog($@); + } +} +else { dielog("Invalid command specified: $command\n"); } + +exit(0); diff --git a/lib/plugins/stonith/external/vmware b/lib/plugins/stonith/external/vmware new file mode 100644 index 0000000..55966ba --- /dev/null +++ b/lib/plugins/stonith/external/vmware @@ -0,0 +1,216 @@ +#!/usr/bin/perl +# External STONITH module for VMWare Server Guests +# +# Copyright (c) 2004 SUSE LINUX AG - Andrew Beekhof +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +sub supply_default +{ + my $name = $_[0]; + my $value = $_[1]; + + if ( defined $ENV{$name} ) { + #print "Set: $name=$ENV{$name}\n"; + } else { + $ENV{$name} = $value; + #print "Default: $name=$ENV{$name}\n"; + } +} + +sub vmware_command +{ + my $config = $_[0]; + my $action = $_[1]; + my @lines; + + my $device = $ENV{'device_host'}; + + if ( $device =~ /localhost/ ) { + @lines = readpipe "vmware-cmd $config $action"; + + } else { + @lines = readpipe "ssh $device \"vmware-cmd \\\"$config\\\" $action\""; + } + + #print @lines; + return @lines; +} + +sub is_host_active +{ + my $config = config_for_host($_[0]); + my @lines = vmware_command($config, "getstate"); + foreach $line (@lines) { + if ( $line =~ /getstate.* = on/ ) { + return 1; + } + } + return 0; +} + +sub supported_hosts +{ + my $line; + my @lines; + + if ( defined $ENV{'host_map'} ) { + @lines = split(/;/, $ENV{'host_map'}); + foreach $line (@lines){ + @config = split(/=/, $line); + $host = $config[0]; + if ( is_host_active($host) == 1 ) { + print "$host\n"; + } + } + + } else { + @lines = vmware_command("-l"); + foreach $line (@lines){ + my @elements = split(/\//, $line); + $host = $elements[$#elements-1]; + if ( is_host_active($host) == 1 ) { + print "$host\n"; + } + } + } +} + +sub config_for_host +{ + my $line; + my @lines; + my $target = $_[0]; + + if ( defined $ENV{'host_map'} ) { + @lines = split(/;/, $ENV{'host_map'}); + foreach $line (@lines){ + if ( $line =~ /^$target=/ ) { + @config = split(/=/, $line); + return $config[1]; + } + } + + } else { + @lines = vmware_command("-l"); + + foreach $line (@lines){ + if ( $line =~ /\/$target\// ) { + chop($line); + return $line; + } + } + } +} + +$command = $ARGV[0]; +if ( defined $ARGV[1] ) { + $targetHost = $ARGV[1]; +} + +supply_default("device_host", "localhost"); + +if ( $command =~ /^gethosts$/ ) { + supported_hosts; + +} elsif ( $command =~ /^getconfignames$/ ) { + print "device_host\n"; + +} elsif ( $command =~ /^getinfo-devid$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devname$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devdescr$/ ) { + print "VMware Server STONITH device\n"; +} elsif ( $command =~ /^getinfo-devurl$/ ) { + print "http://www.vmware.com/"; + +} elsif ( $command =~ /^on$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "start hard"); + print @lines; + +} elsif ( $command =~ /^off$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "stop hard"); + print @lines; + +} elsif ( $command =~ /^reset$/ ) { + $config = config_for_host($targetHost); + my @lines = vmware_command($config, "reset hard"); + print @lines; + +} elsif ( $command =~ /^status$/ ) { + my $rc = 7; + my $device = $ENV{'device_host'}; + if ( $device =~ /localhost/ ) { + $rc = 0; + # TODO: Check for the vmware process + print "Local version: always running\n"; + + } else { + print "Remote version: running ping\n"; + @lines = readpipe "ping -c1 $device"; + print @lines; + + foreach $line ( @lines ) { + if ( $line =~ /0% packet loss/ ) { + $rc = 0; + last; + } + } + } + exit($rc); + +} elsif ( $command =~ /^getinfo-xml$/ ) { + $metadata = < + + + +Host Map + + +A mapping of hostnames to config paths supported by this device. +Eg. host1=/config/path/1;host2=/config/path/2;host3=/config/path/3; + + + + + +Device Host + + +The machine _hosting_ the virtual machines + + + +END + +print $metadata; + +} else { + print "Command $command: not supported\n"; + exit(3); # Not implemented +} + + +exit(0); diff --git a/lib/plugins/stonith/external/xen0 b/lib/plugins/stonith/external/xen0 new file mode 100644 index 0000000..ef1ee40 --- /dev/null +++ b/lib/plugins/stonith/external/xen0 @@ -0,0 +1,253 @@ +#!/bin/sh +# +# External STONITH module for Xen Dom0 through ssh. +# +# Description: Uses Xen Dom0 Domain as a STONITH device +# to control DomUs. +# +# +# Author: Serge Dubrouski (sergeyfd@gmail.com) +# Inspired by Lars Marowsky-Bree's external/ssh agent. +# +# Copyright 2007 Serge Dubrouski +# License: GNU General Public License (GPL) +# + +STOP_COMMAND="xm destroy" +START_COMMAND="xm create" +DUMP_COMMAND="xm dump-core" +DEFAULT_XEN_DIR="/etc/xen" +SSH_COMMAND="/usr/bin/ssh -q -x -n" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +CheckIfDead() { + for j in 1 2 3 4 5 + do + if ! ping -w1 -c1 "$1" >/dev/null 2>&1 + then + return 0 + fi + sleep 1 + done + + return 1 +} + +CheckHostList() { + if [ "x" = "x$hostlist" ] + then + ha_log.sh err "hostlist isn't set" + exit 1 + fi +} + +CheckDom0() { + if [ "x" = "x$dom0" ] + then + ha_log.sh err "dom0 isn't set" + exit 1 + fi +} + +RunCommand() { + CheckHostList + CheckDom0 + + for h in $hostlist + do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + if [ "x" = "x$node" ] + then + ha_log.sh err "Syntax error in host list" + exit 1 + fi + + if [ "x" = "x$cfg" ] + then + cfg="${DEFAULT_XEN_DIR}/${node}.cfg" + fi + + if [ "$node" != "$1" ] + then + continue + fi + + case $2 in + stop) + kill_node=`$SSH_COMMAND $dom0 "grep ^[[:space:]]*name $cfg" | cut -f 2 -d '=' | sed -e 's,",,g'` + if [ "x" = "x$kill_node" ] + then + ha_log.sh err "Couldn't find a node name to stop" + exit 1 + fi + + if [ "x$run_dump" != "x" ] + then + #Need to run core dump + if [ "x$dump_dir" != "x" ] + then + #Dump with the specified core file + TIMESTAMP=`date +%Y-%m%d-%H%M.%S` + DOMAINNAME=`printf "%s" $kill_node` + COREFILE=$dump_dir/$TIMESTAMP-$DOMAINNAME.core + $SSH_COMMAND $dom0 "(mkdir -p $dump_dir; $DUMP_COMMAND $kill_node $COREFILE) >/dev/null 2>&1" + else + $SSH_COMMAND $dom0 "$DUMP_COMMAND $kill_node >/dev/null 2>&1" + fi + fi + $SSH_COMMAND $dom0 "(sleep 2; $STOP_COMMAND $kill_node) >/dev/null 2>&1 &" + break;; + start) + $SSH_COMMAND $dom0 "(sleep 2; $START_COMMAND $cfg) >/dev/null 2>&1 &" + break;; + esac + exit 0 + done +} + + +# Main code + +case $1 in +gethosts) + CheckHostList + + for h in $hostlist ; do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + echo $node + done + exit 0 + ;; +on) + RunCommand $2 start + exit $? + ;; +off) + if RunCommand $2 stop + then + if CheckIfDead $2 + then + exit 0 + fi + fi + + exit 1 + ;; +reset) + RunCommand $2 stop + + if CheckIfDead $2 + then + RunCommand $2 start + exit 0 + fi + + exit 1 + ;; +status) + CheckHostList + + for h in $hostlist + do + CIFS=$IFS + IFS=: + read node cfg << -! +$h +-! + IFS=$CIFS + + echo $node + if ping -w1 -c1 "$node" 2>&1 | grep "unknown host" + then + exit 1 + fi + done + exit 0 + ;; +getconfignames) + echo "hostlist dom0" + exit 0 + ;; +getinfo-devid) + echo "xen0 STONITH device" + exit 0 + ;; +getinfo-devname) + echo "xen0 STONITH external device" + exit 0 + ;; +getinfo-devdescr) + echo "ssh-based host reset for Xen DomU trough Dom0" + echo "Fine for testing, but not really suitable for production!" + exit 0 + ;; +getinfo-devurl) + echo "http://openssh.org http://www.xensource.com/ http://linux-ha.org/wiki" + exit 0 + ;; +getinfo-xml) + cat << SSHXML + + + + +Hostlist + + +The list of controlled nodes in a format node[:config_file]. +For example: "node1:/opt/xen/node1.cfg node2" +If config file isn't set it defaults to /etc/xen/{node_name}.cfg + + + + + +Dom0 + + +Name of the Dom0 Xen node. Root user shall be able to ssh to that node. + + + + + +Run dump-core + + +If set plugin will call "xm dump-core" before killing DomU + + + + + +Run dump-core with the specified directory + + +This parameter can indicate the dump destination. +Should be set as a full path format, ex.) "/var/log/dump" +The above example would dump the core, like; +/var/log/dump/2009-0316-1403.37-domU.core + + + +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper new file mode 100755 index 0000000..b313f8b --- /dev/null +++ b/lib/plugins/stonith/external/xen0-ha-dom0-stonith-helper @@ -0,0 +1,72 @@ +#!/bin/bash +# Author: Lars Marowsky-Bree +# +# Copyright 2008 Lars Marowsky-Bree +# License: GNU General Public License (GPL) + +# This is not an external/stonith plugin by itself, but instead a helper +# script which gets installed in Dom0. + +# TODO: +# - Error handling +# - How to handle if the DomU resource doesn't exist? +# - Does this truly work with split-brain? +# - Is the handling of non-existent resources adequate? +# ... +# Basically: more testing. This is proof-of-concept and works, but deserves +# validation. + +CMD="$1" +DOMU="$2" +TIMEOUT="$3" + +# Make sure the timeout is an integer: +if [ "0$TIMEOUT" -eq 0 ]; then + TIMEOUT=300 +fi + +SetTargetRole() { + local new_role="$1" + crm_resource -r $DOMU --meta -p target_role -v $new_role + + local timeout="$TIMEOUT" + + # We only need to wait for "stopped". + if [ "$new_role" != "stopped" ]; then + return 0 + fi + + while [ $timeout -gt 0 ]; do + local rc + crm_resource -W -r $DOMU 2>&1 | grep -q "is NOT running" + rc=$? + if [ $rc -eq 0 ]; then + return 0 + fi + timeout=$[timeout-1]; + sleep 1 + done + return 1 +} + + +case $CMD in +on) SetTargetRole started + exit $? + ;; +off) SetTargetRole stopped + exit $? + ;; +reset) SetTargetRole stopped || exit 1 + SetTargetRole started + exit $? + ;; +status) exit 0 + ;; +*) ha_log.sh err "Called with unknown command: $CMD" + exit 1 + ;; +esac + +exit 1 + diff --git a/lib/plugins/stonith/external/xen0-ha.in b/lib/plugins/stonith/external/xen0-ha.in new file mode 100755 index 0000000..cb42cbc --- /dev/null +++ b/lib/plugins/stonith/external/xen0-ha.in @@ -0,0 +1,96 @@ +#!/bin/bash +# +# This STONITH script integrates a cluster running within DomUs +# with the CRM/Pacemaker cluster running in Dom0. +# +# Author: Lars Marowsky-Bree +# Copyright: 2008 Lars Marowsky-Bree +# License: GNU General Public License (GPL) +# + +SSH_COMMAND="@SSH@ -q -x -n" +HVM_HELPER="@stonith_plugindir@/xen0-ha-dom0-stonith-helper" + +# Rewrite the hostlist to accept "," as a delimeter for hostnames too. +hostlist=`echo $hostlist | tr ',' ' '` + +# Runs a command on the host, waiting for it to return +RunHVMCommand() { + $SSH_COMMAND $dom0_cluster_ip "$HVM_HELPER $1 $2 $stop_timeout" +} + +# Main code +case $1 in +gethosts) + echo $hostlist + exit 0 + ;; +on|off|reset|status) + RunHVMCommand $1 $2 + exit $? + ;; +getconfignames) + echo "hostlist dom0_cluster_ip timeout" + exit 0 + ;; +getinfo-devid) + echo "xen0-ha DomU/Dom0 device" + exit 0 + ;; +getinfo-devname) + echo "xen0-ha DomU/Dom0 external device" + exit 0 + ;; +getinfo-devdescr) + echo "Allows STONITH to control DomUs managed by a CRM/Pacemaker Dom0." + echo "Requires Xen + CRM/Pacemaker at both layers." + echo "Proof-of-concept code!" + exit 0 + ;; +getinfo-devurl) + echo "http://linux-ha.org/wiki/DomUClusters" + exit 0 + ;; +getinfo-xml) + cat << SSHXML + + + + +Hostlist + + +The list of controlled DomUs, separated by whitespace. +These must be configured as Xen RA resources with a name with a matching +id. +For example: "xen-1 xen-2 xen-3" + + + + + +Dom0 cluster ip + + +The cluster IP address associated with Dom0. +Root user must be able to ssh to that node. + + + + + +Stop timeout + + +The timeout, in seconds, for which to wait for Dom0 to report that the +DomU has been stopped, before aborting with a failure. + + + +SSHXML + exit 0 + ;; +*) + exit 1 + ;; +esac diff --git a/lib/plugins/stonith/ibmhmc.c b/lib/plugins/stonith/ibmhmc.c new file mode 100644 index 0000000..d33fea9 --- /dev/null +++ b/lib/plugins/stonith/ibmhmc.c @@ -0,0 +1,1261 @@ +/* + * Stonith module for IBM Hardware Management Console (HMC) + * + * Author: Huang Zhen + * Support for HMC V4+ added by Dave Blaschke + * + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * + * This code has been tested in following environment: + * + * Hardware Management Console (HMC): Release 3, Version 2.4 + * - Both FullSystemPartition and LPAR Partition: + * - p630 7028-6C4 two LPAR partitions + * - p650 7038-6M2 one LPAR partition and FullSystemPartition + * + * Hardware Management Console (HMC): Version 4, Release 2.1 + * - OP720 1000-6CA three LPAR partitions + * + * Note: Only SSH access to the HMC devices are supported. + * + * This command would make a nice status command: + * + * lshmc -r -F ssh + * + * The following V3 command will get the list of systems we control and their + * mode: + * + * lssyscfg -r sys -F name:mode --all + * + * 0 indicates full system partition + * 255 indicates the system is partitioned + * + * The following V4 command will get the list of systems we control: + * + * lssyscfg -r sys -F name + * + * The following V3 command will get the list of partitions for a given managed + * system running partitioned: + * + * lssyscfg -m managed-system -r lpar -F name --all + * + * Note that we should probably only consider partitions whose boot mode + * is normal (1). (that's my guess, anyway...) + * + * The following V4 command will get the list of partitions for a given managed + * system running partitioned: + * + * lssyscfg -m managed-system -r lpar -F name + * + * The following V3 commands provide the reset/on/off actions: + * + * FULL SYSTEM: + * on: chsysstate -m %1 -r sys -o on -n %1 -c full + * off: chsysstate -m %1 -r sys -o off -n %1 -c full -b norm + * reset:chsysstate -m %1 -r sys -o reset -n %1 -c full -b norm + * + * Partitioned SYSTEM: + * on: chsysstate -m %1 -r lpar -o on -n %2 + * off: reset_partition -m %1 -p %2 -t hard + * reset:do off action above, followed by on action... + * + * where %1 is managed-system, %2 is-lpar name + * + * The following V4 commands provide the reset/on/off actions: + * + * on: chsysstate -m %1 -r lpar -o on -n %2 -f %3 + * off: chsysstate -m %1 -r lpar -o shutdown -n %2 --immed + * reset:chsysstate -m %1 -r lpar -o shutdown -n %2 --immed --restart + * + * where %1 is managed-system, %2 is lpar-name, %3 is profile-name + * + * Of course, to do all this, we need to track which partition name goes with + * which managed system's name, and which systems on the HMC are partitioned + * and which ones aren't... + */ + +#include + +#define DEVICE "IBM HMC" + +#include "stonith_plugin_common.h" + +#ifndef SSH_CMD +# define SSH_CMD "ssh" +#endif +#ifndef HMCROOT +# define HMCROOT "hscroot" +#endif + +#define PIL_PLUGIN ibmhmc +#define PIL_PLUGIN_S "ibmhmc" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +#define MAX_HOST_NAME_LEN (256*4) +#define MAX_CMD_LEN 2048 +#define FULLSYSTEMPARTITION "FullSystemPartition" +#define MAX_POWERON_RETRY 10 +#define MAX_HMC_NAME_LEN 256 + +#define ST_MANSYSPAT "managedsyspat" +#define NOPASS "nopass" + +#define STATE_UNKNOWN -1 +#define STATE_OFF 0 +#define STATE_ON 1 +#define STATE_INVALID 2 + +#define HMCURL "http://publib-b.boulder.ibm.com/redbooks.nsf/RedbookAbstracts"\ + "/SG247038.html" + +static StonithPlugin * ibmhmc_new(const char *); +static void ibmhmc_destroy(StonithPlugin *); +static const char * ibmhmc_getinfo(StonithPlugin * s, int InfoType); +static const char * const * ibmhmc_get_confignames(StonithPlugin* p); +static int ibmhmc_status(StonithPlugin * ); +static int ibmhmc_reset_req(StonithPlugin * s,int request,const char* host); +static char ** ibmhmc_hostlist(StonithPlugin *); +static int ibmhmc_set_config(StonithPlugin *, StonithNVpair*); + +static struct stonith_ops ibmhmcOps = { + ibmhmc_new, /* Create new STONITH object */ + ibmhmc_destroy, /* Destroy STONITH object */ + ibmhmc_getinfo, /* Return STONITH info string */ + ibmhmc_get_confignames, /* Return configuration parameters */ + ibmhmc_set_config, /* Set configuration */ + ibmhmc_status, /* Return STONITH device status */ + ibmhmc_reset_req, /* Request a reset */ + ibmhmc_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) + +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &ibmhmcOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + char * idinfo; + char * hmc; + GList* hostlist; + int hmcver; + char * password; + char ** mansyspats; +}; + +static const char * pluginid = "HMCDevice-Stonith"; +static const char * NOTpluginID = "IBM HMC device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_MANSYSPAT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MANSYSPAT \ + XML_PARM_SHORTDESC_END + +#define XML_MANSYSPAT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "White-space delimited list of patterns used to match managed system names; if last character is '*', all names that begin with the pattern are matched" \ + XML_PARM_LONGDESC_END + +#define XML_MANSYSPAT_PARM \ + XML_PARAMETER_BEGIN(ST_MANSYSPAT, "string", "0", "0") \ + XML_MANSYSPAT_SHORTDESC \ + XML_MANSYSPAT_LONGDESC \ + XML_PARAMETER_END + +#define XML_OPTPASSWD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "Password for " HMCROOT " if passwordless ssh access to HMC has NOT been setup (to do so, it is necessary to create a public/private key pair with empty passphrase - see \"Configure the OpenSSH Client\" in the redbook at " HMCURL " for more details)" \ + XML_PARM_LONGDESC_END + +#define XML_OPTPASSWD_PARM \ + XML_PARAMETER_BEGIN(ST_PASSWD, "string", "0", "0") \ + XML_PASSWD_SHORTDESC \ + XML_OPTPASSWD_LONGDESC \ + XML_PARAMETER_END + +static const char *ibmhmcXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_MANSYSPAT_PARM + XML_OPTPASSWD_PARM + XML_PARAMETERS_END; + +static int get_hmc_hostlist(struct pluginDevice* dev); +static void free_hmc_hostlist(struct pluginDevice* dev); +static int get_hmc_mansyspats(struct pluginDevice* dev, const char* mansyspats); +static void free_hmc_mansyspats(struct pluginDevice* dev); +static char* do_shell_cmd(const char* cmd, int* status, const char* password); +static int check_hmc_status(struct pluginDevice* dev); +static int get_num_tokens(char *str); +static gboolean pattern_match(char **patterns, char *string); +/* static char* do_shell_cmd_fake(const char* cmd, int* status); */ + +static int +ibmhmc_status(StonithPlugin *s) +{ + struct pluginDevice* dev = NULL; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + dev = (struct pluginDevice*) s; + + return check_hmc_status(dev); +} + + +/* + * Return the list of hosts configured for this HMC device + */ + +static char ** +ibmhmc_hostlist(StonithPlugin *s) +{ + int j; + struct pluginDevice* dev; + int numnames = 0; + char** ret = NULL; + GList* node = NULL; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + dev = (struct pluginDevice*) s; + + /* refresh the hostlist */ + free_hmc_hostlist(dev); + if (S_OK != get_hmc_hostlist(dev)){ + LOG(PIL_CRIT, "unable to obtain list of managed systems in %s" + , __FUNCTION__); + return NULL; + } + + numnames = g_list_length(dev->hostlist); + if (numnames < 0) { + LOG(PIL_CRIT, "unconfigured stonith object in %s" + , __FUNCTION__); + return(NULL); + } + + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + + memset(ret, 0, (numnames+1)*sizeof(char*)); + for (node = g_list_first(dev->hostlist), j = 0 + ; NULL != node + ; j++, node = g_list_next(node)) { + char* host = strchr((char*)node->data, '/'); + ret[j] = STRDUP(++host); + if (ret[j] == NULL) { + LOG(PIL_CRIT, "out of memory"); + stonith_free_hostlist(ret); + return NULL; + } + strdown(ret[j]); + } + return ret; +} + + +static const char * const * +ibmhmc_get_confignames(StonithPlugin* p) +{ + static const char * names[] = {ST_IPADDR, NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + + +/* + * Reset the given host, and obey the request type. + * We should reset without power cycle for the non-partitioned case + */ + +static int +ibmhmc_reset_req(StonithPlugin * s, int request, const char * host) +{ + GList* node = NULL; + struct pluginDevice* dev = NULL; + char off_cmd[MAX_CMD_LEN]; + char on_cmd[MAX_CMD_LEN]; + char reset_cmd[MAX_CMD_LEN]; + gchar** names = NULL; + int i; + int is_lpar = FALSE; + int status; + char* pch; + char* output = NULL; + char state_cmd[MAX_CMD_LEN]; + int state = STATE_UNKNOWN; + + status = 0; + if(Debug){ + LOG(PIL_DEBUG, "%s: called, host=%s\n", __FUNCTION__, host); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (NULL == host) { + LOG(PIL_CRIT, "invalid argument to %s", __FUNCTION__); + return(S_OOPS); + } + + dev = (struct pluginDevice*) s; + + for (node = g_list_first(dev->hostlist) + ; NULL != node + ; node = g_list_next(node)) { + if(Debug){ + LOG(PIL_DEBUG, "%s: node->data=%s\n" + , __FUNCTION__, (char*)node->data); + } + + if ((pch = strchr((char*)node->data, '/')) != NULL + && 0 == strcasecmp(++pch, host)) { + break; + } + } + + if (!node) { + LOG(PIL_CRIT + , "Host %s is not configured in this STONITH module. " + "Please check your configuration information.", host); + return (S_OOPS); + } + + names = g_strsplit((char*)node->data, "/", 2); + /* names[0] will be the name of managed system */ + /* names[1] will be the name of the lpar partition */ + if(Debug){ + LOG(PIL_DEBUG, "%s: names[0]=%s, names[1]=%s\n" + , __FUNCTION__, names[0], names[1]); + } + + if (dev->hmcver < 4) { + if (0 == strcasecmp(names[1], FULLSYSTEMPARTITION)) { + is_lpar = FALSE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o off -n %s -c full" + , dev->hmc, dev->hmc, names[0]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o on -n %s -c full -b norm" + , dev->hmc, names[0], names[0]); + + snprintf(reset_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r sys -m %s -o reset -n %s -c full -b norm" + , dev->hmc, names[0], names[0]); + + *state_cmd = 0; + }else{ + is_lpar = TRUE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s reset_partition" + " -m %s -p %s -t hard" + , dev->hmc, names[0], names[1]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -r lpar -m %s -o on -n %s" + , dev->hmc, names[0], names[1]); + + *reset_cmd = 0; + + snprintf(state_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -r lpar -m %s -F state -n %s" + , dev->hmc, names[0], names[1]); + } + }else{ + is_lpar = TRUE; + + snprintf(off_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o shutdown -n \"%s\" --immed" + , dev->hmc, names[0], names[1]); + + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -m %s -r lpar -F \"default_profile\"" + " --filter \"lpar_names=%s\"" + , dev->hmc, names[0], names[1]); + + output = do_shell_cmd(on_cmd, &status, dev->password); + if (output == NULL) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return (S_OOPS); + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + snprintf(on_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o on -n %s -f %s" + , dev->hmc, names[0], names[1], output); + FREE(output); + output = NULL; + + snprintf(reset_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s chsysstate" + " -m %s -r lpar -o shutdown -n %s --immed --restart" + , dev->hmc, names[0], names[1]); + + snprintf(state_cmd, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lssyscfg" + " -m %s -r lpar -F state --filter \"lpar_names=%s\"" + , dev->hmc, names[0], names[1]); + } + g_strfreev(names); + + if(Debug){ + LOG(PIL_DEBUG, "%s: off_cmd=%s, on_cmd=%s," + "reset_cmd=%s, state_cmd=%s\n" + , __FUNCTION__, off_cmd, on_cmd, reset_cmd, state_cmd); + } + + output = do_shell_cmd(state_cmd, &status, dev->password); + if (output == NULL) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return S_OOPS; + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + if (strcmp(output, "Running") == 0 + || strcmp(output, "Starting") == 0 + || strcmp(output, "Open Firmware") == 0) { + state = STATE_ON; + }else if (strcmp(output, "Shutting Down") == 0 + || strcmp(output, "Not Activated") == 0 + || strcmp(output, "Ready") == 0) { + state = STATE_OFF; + }else if (strcmp(output, "Not Available") == 0 + || strcmp(output, "Error") == 0) { + state = STATE_INVALID; + } + FREE(output); + output = NULL; + + if (state == STATE_INVALID) { + LOG(PIL_CRIT, "host %s in invalid state", host); + return S_OOPS; + } + + switch (request) { + case ST_POWERON: + if (state == STATE_ON) { + LOG(PIL_INFO, "host %s already on", host); + return S_OK; + } + + output = do_shell_cmd(on_cmd, &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", on_cmd); + return S_OOPS; + } + break; + case ST_POWEROFF: + if (state == STATE_OFF) { + LOG(PIL_INFO, "host %s already off", host); + return S_OK; + } + + output = do_shell_cmd(off_cmd, &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", off_cmd); + return S_OOPS; + } + break; + case ST_GENERIC_RESET: + if (dev->hmcver < 4) { + if (is_lpar) { + if (state == STATE_ON) { + output = do_shell_cmd(off_cmd + , &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s " + "failed", off_cmd); + return S_OOPS; + } + } + for (i = 0; i < MAX_POWERON_RETRY; i++) { + char *output2; + output2 = do_shell_cmd(on_cmd + , &status, dev->password); + if (output2 != NULL) { + FREE(output2); + } + if (0 != status) { + sleep(1); + }else{ + break; + } + } + if (MAX_POWERON_RETRY == i) { + LOG(PIL_CRIT, "command %s failed" + , on_cmd); + return S_OOPS; + } + }else{ + output = do_shell_cmd(reset_cmd + , &status, dev->password); + if (0 != status) { + LOG(PIL_CRIT, "command %s failed" , reset_cmd); + return S_OOPS; + } + break; + } + }else{ + if (state == STATE_ON) { + output = do_shell_cmd(reset_cmd + , &status, dev->password); + }else{ + output = do_shell_cmd(on_cmd + , &status, dev->password); + } + if (0 != status) { + LOG(PIL_CRIT, "command %s failed", reset_cmd); + return S_OOPS; + } + } + break; + default: + return S_INVAL; + } + + if (output != NULL) { + FREE(output); + } + + LOG(PIL_INFO, "Host %s %s %d.", host, __FUNCTION__, request); + + return S_OK; +} + + +/* + * Parse the information in the given configuration file, + * and stash it away... + */ + +static int +ibmhmc_set_config(StonithPlugin * s, StonithNVpair* list) +{ + struct pluginDevice* dev = NULL; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {NULL, NULL} + }; + int rc; + char get_hmcver[MAX_CMD_LEN]; + char firstchar; + int firstnum; + char* output = NULL; + int status; + const char *mansyspats; + int len; + + ERRIFWRONGDEV(s,S_OOPS); + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + dev = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + if(Debug){ + LOG(PIL_DEBUG, "%s: ipaddr=%s\n", __FUNCTION__ + , namestocopy[0].s_value); + } + + if (get_num_tokens(namestocopy[0].s_value) == 1) { + /* name=value pairs on command line, look for managedsyspat */ + mansyspats = OurImports->GetValue(list, ST_MANSYSPAT); + if (mansyspats != NULL) { + if (get_hmc_mansyspats(dev, mansyspats) != S_OK) { + FREE(namestocopy[0].s_value); + return S_OOPS; + } + } + /* look for password */ + dev->password = STRDUP(OurImports->GetValue(list, ST_PASSWD)); + dev->hmc = namestocopy[0].s_value; + }else{ + /* -p or -F option with args "ipaddr [managedsyspat]..." */ + char *pch = namestocopy[0].s_value; + + /* skip over ipaddr and null-terminate */ + pch += strcspn(pch, WHITESPACE); + *pch = EOS; + + /* skip over white-space up to next token */ + pch++; + pch += strspn(pch, WHITESPACE); + if (get_hmc_mansyspats(dev, pch) != S_OK) { + FREE(namestocopy[0].s_value); + return S_OOPS; + } + + dev->hmc = STRDUP(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + } + + /* check whether the HMC has ssh command enabled */ + if (check_hmc_status(dev) != S_OK) { + LOG(PIL_CRIT, "HMC %s does not have remote " + "command execution using the ssh facility enabled", dev->hmc); + return S_BADCONFIG; + } + + /* get the HMC's version info */ + snprintf(get_hmcver, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lshmc -v | grep RM", dev->hmc); + if (Debug) { + LOG(PIL_DEBUG, "%s: get_hmcver=%s", __FUNCTION__, get_hmcver); + } + + output = do_shell_cmd(get_hmcver, &status, dev->password); + if (Debug) { + LOG(PIL_DEBUG, "%s: output=%s\n", __FUNCTION__ + , output ? output : "(nil)"); + } + if (output == NULL) { + return S_BADCONFIG; + } + + /* parse the HMC's version info (i.e. "*RM V4R2.1" or "*RM R3V2.6") */ + if ((sscanf(output, "*RM %c%1d", &firstchar, &firstnum) == 2) + && ((firstchar == 'V') || (firstchar == 'R'))) { + dev->hmcver = firstnum; + if(Debug){ + LOG(PIL_DEBUG, "%s: HMC %s version is %d" + , __FUNCTION__, dev->hmc, dev->hmcver); + } + }else{ + LOG(PIL_CRIT, "%s: unable to determine HMC %s version" + , __FUNCTION__, dev->hmc); + FREE(output); + return S_BADCONFIG; + } + + len = strlen(output+4) + sizeof(DEVICE) + 1; + if (dev->idinfo != NULL) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + dev->idinfo = MALLOC(len * sizeof(char)); + if (dev->idinfo == NULL) { + LOG(PIL_CRIT, "out of memory"); + FREE(output); + return S_OOPS; + } + snprintf(dev->idinfo, len, "%s %s", DEVICE, output+4); + FREE(output); + + if (S_OK != get_hmc_hostlist(dev)){ + LOG(PIL_CRIT, "unable to obtain list of managed systems in %s" + , __FUNCTION__); + return S_BADCONFIG; + } + + return S_OK; +} + + +static const char* +ibmhmc_getinfo(StonithPlugin* s, int reqtype) +{ + struct pluginDevice* dev; + const char* ret; + + ERRIFWRONGDEV(s,NULL); + + dev = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = dev->idinfo; + break; + + case ST_DEVICENAME: + ret = dev->hmc; + break; + + case ST_DEVICEDESCR: + ret = "IBM Hardware Management Console (HMC)\n" + "Use for IBM i5, p5, pSeries and OpenPower systems " + "managed by HMC\n" + " Optional parameter name " ST_MANSYSPAT " is " + "white-space delimited list of\n" + "patterns used to match managed system names; if last " + "character is '*',\n" + "all names that begin with the pattern are matched\n" + " Optional parameter name " ST_PASSWD " is password " + "for " HMCROOT " if passwordless\n" + "ssh access to HMC has NOT been setup (to do so, it " + "is necessary to create\n" + "a public/private key pair with empty passphrase - " + "see \"Configure the\n" + "OpenSSH client\" in the redbook for more details)"; + break; + + case ST_DEVICEURL: + ret = HMCURL; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = ibmhmcXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + + +/* + * HMC Stonith destructor... + */ + +static void +ibmhmc_destroy(StonithPlugin *s) +{ + struct pluginDevice* dev; + + if(Debug){ + LOG(PIL_DEBUG, "%s : called\n", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + dev = (struct pluginDevice *)s; + + dev->pluginid = NOTpluginID; + if (dev->hmc) { + FREE(dev->hmc); + dev->hmc = NULL; + } + if (dev->password) { + FREE(dev->password); + dev->password = NULL; + } + if (dev->idinfo) { + FREE(dev->idinfo); + dev->idinfo = NULL; + } + free_hmc_hostlist(dev); + free_hmc_mansyspats(dev); + + FREE(dev); +} + + +static StonithPlugin * +ibmhmc_new(const char *subplugin) +{ + struct pluginDevice* dev = ST_MALLOCT(struct pluginDevice); + + if(Debug){ + LOG(PIL_DEBUG, "%s: called\n", __FUNCTION__); + } + + if (dev == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return(NULL); + } + + memset(dev, 0, sizeof(*dev)); + + dev->pluginid = pluginid; + dev->hmc = NULL; + dev->password = NULL; + dev->hostlist = NULL; + dev->mansyspats = NULL; + dev->hmcver = -1; + REPLSTR(dev->idinfo, DEVICE); + if (dev->idinfo == NULL) { + FREE(dev); + return(NULL); + } + dev->sp.s_ops = &ibmhmcOps; + + if(Debug){ + LOG(PIL_DEBUG, "%s: returning successfully\n", __FUNCTION__); + } + + return((void *)dev); +} + +static int +get_hmc_hostlist(struct pluginDevice* dev) +{ + int i, j, status; + char* output = NULL; + char get_syslist[MAX_CMD_LEN]; + char host[MAX_HOST_NAME_LEN]; + gchar** syslist = NULL; + gchar** name_mode = NULL; + char get_lpar[MAX_CMD_LEN]; + gchar** lparlist = NULL; + char* pch; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, dev->hmc=%s\n", __FUNCTION__ + , dev->hmc); + } + + if (dev->hmc == NULL || *dev->hmc == 0){ + return S_BADCONFIG; + } + + /* get the managed system's names of the hmc */ + if (dev->hmcver < 4) { + snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD " -l " HMCROOT + " %s lssyscfg -r sys -F name:mode --all", dev->hmc); + }else{ + snprintf(get_syslist, MAX_CMD_LEN, SSH_CMD + " -l " HMCROOT " %s lssyscfg -r sys -F name", dev->hmc); + } + if(Debug){ + LOG(PIL_DEBUG, "%s: get_syslist=%s", __FUNCTION__, get_syslist); + } + + output = do_shell_cmd(get_syslist, &status, dev->password); + if (output == NULL) { + return S_BADCONFIG; + } + syslist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each managed system */ + for (i = 0; syslist[i] != NULL && syslist[i][0] != 0; i++) { + if (dev->hmcver < 4) { + name_mode = g_strsplit(syslist[i], ":", 2); + if(Debug){ + LOG(PIL_DEBUG, "%s: name_mode0=%s, name_mode1=%s\n" + , __FUNCTION__, name_mode[0], name_mode[1]); + } + + if (dev->mansyspats != NULL + && !pattern_match(dev->mansyspats, name_mode[0])) { + continue; + } + + /* if it is in fullsystempartition */ + if (NULL != name_mode[1] + && 0 == strncmp(name_mode[1], "0", 1)) { + /* add the FullSystemPartition */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/FullSystemPartition", name_mode[0]); + dev->hostlist = g_list_append(dev->hostlist + , STRDUP(host)); + }else if (NULL != name_mode[1] + && 0 == strncmp(name_mode[1], "255", 3)){ + /* get its lpars */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r lpar -F name --all" + , dev->hmc, name_mode[0]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar + , &status, dev->password); + if (output == NULL) { + g_strfreev(name_mode); + g_strfreev(syslist); + return S_BADCONFIG; + } + lparlist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each lpar */ + for (j = 0 + ; NULL != lparlist[j] && 0 != lparlist[j][0] + ; j++) { + /* skip the full system partition */ + if (0 == strncmp(lparlist[j] + , FULLSYSTEMPARTITION + , strlen(FULLSYSTEMPARTITION))) { + continue; + } + /* add the lpar */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/%s", name_mode[0] + , lparlist[j]); + dev->hostlist = + g_list_append(dev->hostlist + , STRDUP(host)); + } + g_strfreev(lparlist); + } + g_strfreev(name_mode); + }else{ + if (dev->mansyspats != NULL + && !pattern_match(dev->mansyspats, syslist[i])) { + continue; + } + + /* get its state */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r sys -F state" + , dev->hmc, syslist[i]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar, &status, dev->password); + if (output == NULL) { + g_strfreev(syslist); + return S_BADCONFIG; + } + if ((pch = strchr(output, '\n')) != NULL) { + *pch = 0; + } + if (!strcmp(output, "No Connection")){ + FREE(output); + continue; + } + FREE(output); + + /* get its lpars */ + snprintf(get_lpar, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT + " %s lssyscfg -m %s -r lpar -F name" + , dev->hmc, syslist[i]); + if(Debug){ + LOG(PIL_DEBUG, "%s: get_lpar=%s\n" + , __FUNCTION__, get_lpar); + } + + output = do_shell_cmd(get_lpar, &status, dev->password); + if (output == NULL) { + g_strfreev(syslist); + return S_BADCONFIG; + } + lparlist = g_strsplit(output, "\n", 0); + FREE(output); + + /* for each lpar */ + for (j = 0 + ; NULL != lparlist[j] && 0 != lparlist[j][0] + ; j++) { + /* add the lpar */ + snprintf(host, MAX_HOST_NAME_LEN + , "%s/%s", syslist[i],lparlist[j]); + dev->hostlist = g_list_append(dev->hostlist + , STRDUP(host)); + } + g_strfreev(lparlist); + } + } + g_strfreev(syslist); + + return S_OK; +} + +static void +free_hmc_hostlist(struct pluginDevice* dev) +{ + if (dev->hostlist) { + GList* node; + while (NULL != (node=g_list_first(dev->hostlist))) { + dev->hostlist = g_list_remove_link(dev->hostlist, node); + FREE(node->data); + g_list_free(node); + } + dev->hostlist = NULL; + } +} + +static int +get_hmc_mansyspats(struct pluginDevice * dev, const char *mansyspats) +{ + char *patscopy; + int numpats; + int i; + char *tmp; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, mansyspats=%s\n" + , __FUNCTION__, mansyspats); + } + + patscopy = STRDUP(mansyspats); + if (patscopy == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return S_OOPS; + } + + numpats = get_num_tokens(patscopy); + if (numpats > 0) { + dev->mansyspats = MALLOC((numpats+1)*sizeof(char *)); + if (dev->mansyspats == NULL) { + LOG(PIL_CRIT, "%s: out of memory" + , __FUNCTION__); + FREE(patscopy); + return S_OOPS; + } + + memset(dev->mansyspats, 0, (numpats+1)*sizeof(char *)); + + /* White-space split the output here */ + i = 0; + tmp = strtok(patscopy, WHITESPACE); + while (tmp != NULL) { + dev->mansyspats[i] = STRDUP(tmp); + if (dev->mansyspats[i] == NULL) { + LOG(PIL_CRIT, "%s: out of memory" + , __FUNCTION__); + free_hmc_mansyspats(dev); + dev->mansyspats = NULL; + FREE(patscopy); + return S_OOPS; + } + + if(Debug){ + LOG(PIL_DEBUG, "%s: adding pattern %s\n" + , __FUNCTION__, dev->mansyspats[i]); + } + + /* no patterns necessary if all specified */ + if (strcmp(dev->mansyspats[i], "*") == 0) { + stonith_free_hostlist(dev->mansyspats); + dev->mansyspats = NULL; + break; + } + + i++; + tmp = strtok(NULL, WHITESPACE); + } + } + FREE(patscopy); + return S_OK; +} + +static void +free_hmc_mansyspats(struct pluginDevice* dev) +{ + if (dev->mansyspats) { + stonith_free_hostlist(dev->mansyspats); + dev->mansyspats = NULL; + } +} + +static char* +do_shell_cmd(const char* cmd, int* status, const char* password) +{ + const int BUFF_LEN=4096; + int read_len = 0; + char buff[BUFF_LEN]; + char cmd_password[MAX_CMD_LEN]; + char* data = NULL; + GString* g_str_tmp = NULL; + + FILE* file; + if (NULL == password) { + file = popen(cmd, "r"); + } else { + snprintf(cmd_password, MAX_CMD_LEN + ,"umask 077;" + "if [ ! -d " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc ];" + "then mkdir " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc 2>/dev/null;" + "fi;" + "export ibmhmc_tmp=`mktemp -p " HA_VARRUNDIR "/heartbeat/rsctmp/ibmhmc/`;" + "echo \"echo '%s'\">$ibmhmc_tmp;" + "chmod +x $ibmhmc_tmp;" + "unset SSH_AGENT_SOCK SSH_AGENT_PID;" + "SSH_ASKPASS=$ibmhmc_tmp DISPLAY=ibmhmc_foo setsid %s;" + "rm $ibmhmc_tmp -f;" + "unset ibmhmc_tmp" + ,password, cmd); + file = popen(cmd_password, "r"); + } + if (NULL == file) { + return NULL; + } + + g_str_tmp = g_string_new(""); + while(!feof(file)) { + memset(buff, 0, BUFF_LEN); + read_len = fread(buff, 1, BUFF_LEN, file); + if (0 < read_len) { + g_string_append(g_str_tmp, buff); + }else{ + sleep(1); + } + } + data = (char*)MALLOC(g_str_tmp->len+1); + if (data != NULL) { + data[0] = data[g_str_tmp->len] = 0; + strncpy(data, g_str_tmp->str, g_str_tmp->len); + } + g_string_free(g_str_tmp, TRUE); + *status = pclose(file); + return data; +} + +static int +check_hmc_status(struct pluginDevice* dev) +{ + int status; + char check_status[MAX_CMD_LEN]; + char* output = NULL; + int rc = S_OK; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, hmc=%s\n", __FUNCTION__, dev->hmc); + } + + snprintf(check_status, MAX_CMD_LEN + , SSH_CMD " -l " HMCROOT " %s lshmc -r -F ssh", dev->hmc); + if(Debug){ + LOG(PIL_DEBUG, "%s: check_status %s\n", __FUNCTION__ + , check_status); + } + + output = do_shell_cmd(check_status, &status, dev->password); + if (Debug) { + LOG(PIL_DEBUG, "%s: status=%d, output=%s\n", __FUNCTION__ + , status, output ? output : "(nil)"); + } + + if (NULL == output || strncmp(output, "enable", 6) != 0) { + rc = S_BADCONFIG; + } + if (NULL != output) { + FREE(output); + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static gboolean +pattern_match(char **patterns, char *string) +{ + char **pattern; + + if(Debug){ + LOG(PIL_DEBUG, "%s: called, string=%s\n", __FUNCTION__, string); + } + + for (pattern = patterns; *pattern; pattern++) { + int patlen = strlen(*pattern); + + if (pattern[0][patlen-1] == '*') { + /* prefix match */ + if (strncmp(string, *pattern, patlen-1) == 0) { + return TRUE; + } + }else{ + /* exact match */ + if (strcmp(string, *pattern) == 0) { + return TRUE; + } + } + } + + return FALSE; +} + +/* +static char* +do_shell_cmd_fake(const char* cmd, int* status) +{ + printf("%s()\n", __FUNCTION__); + printf("cmd:%s\n", cmd); + *status=0; + return NULL; +} +*/ diff --git a/lib/plugins/stonith/ipmi_os_handler.c b/lib/plugins/stonith/ipmi_os_handler.c new file mode 100644 index 0000000..bdb6d6e --- /dev/null +++ b/lib/plugins/stonith/ipmi_os_handler.c @@ -0,0 +1,257 @@ +/* + * This program is largely based on the ipmicmd.c program that's part of OpenIPMI package. + * + * Copyright Intel Corp. + * Yixiong.Zou@intel.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include + +#include + +extern selector_t *os_sel; + +#if 0 +static void check_no_locks(os_handler_t *handler); +#define CHECK_NO_LOCKS(handler) check_no_locks(handler) +#else +#define CHECK_NO_LOCKS(handler) do {} while(0) +#endif + +struct os_hnd_fd_id_s +{ + int fd; + void *cb_data; + os_data_ready_t data_ready; + os_handler_t *handler; +}; + +static void +fd_handler(int fd, void *data) +{ + + os_hnd_fd_id_t *fd_data = (os_hnd_fd_id_t *) data; + + CHECK_NO_LOCKS(fd_data->handler); + fd_data->data_ready(fd, fd_data->cb_data, fd_data); + CHECK_NO_LOCKS(fd_data->handler); +} + +static int +add_fd(os_handler_t *handler, + int fd, + os_data_ready_t data_ready, + void *cb_data, + os_hnd_fd_id_t **id) +{ + os_hnd_fd_id_t *fd_data; + + fd_data = ipmi_mem_alloc(sizeof(*fd_data)); + if (!fd_data) + return ENOMEM; + + fd_data->fd = fd; + fd_data->cb_data = cb_data; + fd_data->data_ready = data_ready; + fd_data->handler = handler; + sel_set_fd_handlers(os_sel, fd, fd_data, fd_handler, NULL, NULL, NULL); + sel_set_fd_read_handler(os_sel, fd, SEL_FD_HANDLER_ENABLED); + sel_set_fd_write_handler(os_sel, fd, SEL_FD_HANDLER_DISABLED); + sel_set_fd_except_handler(os_sel, fd, SEL_FD_HANDLER_DISABLED); + + *id = fd_data; + return 0; +} + +static int +remove_fd(os_handler_t *handler, os_hnd_fd_id_t *fd_data) +{ + sel_clear_fd_handlers(os_sel, fd_data->fd); + sel_set_fd_read_handler(os_sel, fd_data->fd, SEL_FD_HANDLER_DISABLED); + ipmi_mem_free(fd_data); + return 0; +} + +struct os_hnd_timer_id_s +{ + void *cb_data; + os_timed_out_t timed_out; + sel_timer_t *timer; + int running; + os_handler_t *handler; +}; + +static void +timer_handler(selector_t *sel, + sel_timer_t *timer, + void *data) +{ + os_hnd_timer_id_t *timer_data = (os_hnd_timer_id_t *) data; + void *cb_data; + os_timed_out_t timed_out; + + CHECK_NO_LOCKS(timer_data->handler); + timed_out = timer_data->timed_out; + cb_data = timer_data->cb_data; + timer_data->running = 0; + timed_out(cb_data, timer_data); + CHECK_NO_LOCKS(timer_data->handler); +} + +static int +start_timer(os_handler_t *handler, + os_hnd_timer_id_t *id, + struct timeval *timeout, + os_timed_out_t timed_out, + void *cb_data) +{ + struct timeval now; + + if (id->running) + return EBUSY; + + id->running = 1; + id->cb_data = cb_data; + id->timed_out = timed_out; + + gettimeofday(&now, NULL); + now.tv_sec += timeout->tv_sec; + now.tv_usec += timeout->tv_usec; + while (now.tv_usec >= 1000000) { + now.tv_usec -= 1000000; + now.tv_sec += 1; + } + + return sel_start_timer(id->timer, &now); +} + +static int +stop_timer(os_handler_t *handler, os_hnd_timer_id_t *timer_data) +{ + return sel_stop_timer(timer_data->timer); +} + +static int +alloc_timer(os_handler_t *handler, + os_hnd_timer_id_t **id) +{ + os_hnd_timer_id_t *timer_data; + int rv; + + timer_data = ipmi_mem_alloc(sizeof(*timer_data)); + if (!timer_data) + return ENOMEM; + + timer_data->running = 0; + timer_data->timed_out = NULL; + timer_data->handler = handler; + + rv = sel_alloc_timer(os_sel, timer_handler, timer_data, + &(timer_data->timer)); + if (rv) { + ipmi_mem_free(timer_data); + return rv; + } + + *id = timer_data; + return 0; +} + +static int +free_timer(os_handler_t *handler, os_hnd_timer_id_t *timer_data) +{ + sel_free_timer(timer_data->timer); + ipmi_mem_free(timer_data); + return 0; +} + +static int +get_random(os_handler_t *handler, void *data, unsigned int len) +{ + int fd = open("/dev/urandom", O_RDONLY); + int rv; + + if (fd == -1) + return errno; + + rv = read(fd, data, len); + + close(fd); + return rv; +} + +static void +sui_log(os_handler_t *handler, + enum ipmi_log_type_e log_type, + char *format, + ...) +{ + return; +} + +static void +sui_vlog(os_handler_t *handler, + enum ipmi_log_type_e log_type, + char *format, + va_list ap) +{ + return; +} + + +os_handler_t ipmi_os_cb_handlers = +{ + .add_fd_to_wait_for = add_fd, + .remove_fd_to_wait_for = remove_fd, + + .start_timer = start_timer, + .stop_timer = stop_timer, + .alloc_timer = alloc_timer, + .free_timer = free_timer, + + .create_lock = NULL, + .destroy_lock = NULL, + .is_locked = NULL, + .lock = NULL, + .unlock = NULL, + .create_rwlock = NULL, + .destroy_rwlock = NULL, + .read_lock = NULL, + .write_lock = NULL, + .read_unlock = NULL, + .write_unlock = NULL, + .is_readlocked = NULL, + .is_writelocked = NULL, + + .get_random = get_random, + + .log = sui_log, + .vlog = sui_vlog +}; + + diff --git a/lib/plugins/stonith/ipmilan.c b/lib/plugins/stonith/ipmilan.c new file mode 100644 index 0000000..1efdfee --- /dev/null +++ b/lib/plugins/stonith/ipmilan.c @@ -0,0 +1,587 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou + * + * Mangled by Sun Jiang Dong , IBM, 2005. + * And passed the compiling with OpenIPMI-1.4.8. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + + +/* + * See README.ipmi for information regarding this plugin. + * + */ + +#define DEVICE "IPMI Over LAN" + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN ipmilan +#define PIL_PLUGIN_S "ipmilan" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +#include +#include + +#include "ipmilan.h" + +static StonithPlugin * ipmilan_new(const char *); +static void ipmilan_destroy(StonithPlugin *); +static const char * const * ipmilan_get_confignames(StonithPlugin *); +static int ipmilan_set_config(StonithPlugin *, StonithNVpair *); +static const char * ipmilan_getinfo(StonithPlugin * s, int InfoType); +static int ipmilan_status(StonithPlugin * ); +static int ipmilan_reset_req(StonithPlugin * s, int request, const char * host); +static char ** ipmilan_hostlist(StonithPlugin *); + +static struct stonith_ops ipmilanOps ={ + ipmilan_new, /* Create new STONITH object */ + ipmilan_destroy, /* Destroy STONITH object */ + ipmilan_getinfo, /* Return STONITH info string */ + ipmilan_get_confignames,/* Get configuration parameter names */ + ipmilan_set_config, /* Set configuration */ + ipmilan_status, /* Return STONITH device status */ + ipmilan_reset_req, /* Request a reset */ + ipmilan_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug); +const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &ipmilanOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * ipmilan STONITH device. + * + * ipmilanHostInfo is a double linked list. Where the prev of the head always + * points to the tail. This is a little wierd. But it saves me from looping + * around to find the tail when destroying the list. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + int hostcount; + struct ipmilanHostInfo * hostlist; +}; + +static const char * pluginid = "IPMI-LANDevice-Stonith"; +static const char * NOTpluginid = "IPMI-LAN device has been destroyed"; + +#define ST_HOSTNAME "hostname" +#define ST_PORT "port" +#define ST_AUTH "auth" +#define ST_PRIV "priv" +#define ST_RESET_METHOD "reset_method" + +#include "stonith_config_xml.h" + +#define XML_HOSTNAME_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_HOSTNAME \ + XML_PARM_SHORTDESC_END + +#define XML_HOSTNAME_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hostname of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_HOSTNAME_PARM \ + XML_PARAMETER_BEGIN(ST_HOSTNAME, "string", "1", "1") \ + XML_HOSTNAME_SHORTDESC \ + XML_HOSTNAME_LONGDESC \ + XML_PARAMETER_END + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number to where the IPMI message is sent" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +#define XML_AUTH_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_AUTH \ + XML_PARM_SHORTDESC_END + +#define XML_AUTH_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The authorization type of the IPMI session (\"none\", \"straight\", \"md2\", or \"md5\")" \ + XML_PARM_LONGDESC_END + +#define XML_AUTH_PARM \ + XML_PARAMETER_BEGIN(ST_AUTH, "string", "1", "0") \ + XML_AUTH_SHORTDESC \ + XML_AUTH_LONGDESC \ + XML_PARAMETER_END + +#define XML_PRIV_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PRIV \ + XML_PARM_SHORTDESC_END + +#define XML_PRIV_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The privilege level of the user (\"operator\" or \"admin\")" \ + XML_PARM_LONGDESC_END + +#define XML_PRIV_PARM \ + XML_PARAMETER_BEGIN(ST_PRIV, "string", "1", "0") \ + XML_PRIV_SHORTDESC \ + XML_PRIV_LONGDESC \ + XML_PARAMETER_END + +#define XML_RESET_METHOD_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_RESET_METHOD \ + XML_PARM_SHORTDESC_END + +#define XML_RESET_METHOD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "How to reset the host (\"power_cycle\" or \"hard_reset\")" \ + XML_PARM_LONGDESC_END + +#define XML_RESET_METHOD_PARM \ + XML_PARAMETER_BEGIN(ST_RESET_METHOD, "string", "0", "0") \ + XML_RESET_METHOD_SHORTDESC \ + XML_RESET_METHOD_LONGDESC \ + XML_PARAMETER_END + +static const char *ipmilanXML = + XML_PARAMETERS_BEGIN + XML_HOSTNAME_PARM + XML_IPADDR_PARM + XML_PORT_PARM + XML_AUTH_PARM + XML_PRIV_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/* + * Check the status of the IPMI Lan STONITH device. + * + * NOTE: not sure what we should do here since each host is configured + * seperately. + * + * Two options: + * 1) always return S_OK. + * 2) using IPMI ping to confirm the status for every host that's + * configured. + * + * For now I choose the option 1 hoping that I can get by. Maybe we should + * change it to option 2 later. + */ + +static int +ipmilan_status(StonithPlugin *s) +{ + struct pluginDevice * nd; + struct ipmilanHostInfo * node; + int ret, rv; + int i; + + ERRIFWRONGDEV(s,S_OOPS); + + ret = S_OK; + + nd = (struct pluginDevice *)s; + for( i=0, node = nd->hostlist; + i < nd->hostcount; i++, node = node->next ) { + rv = do_ipmi_cmd(node, ST_IPMI_STATUS); + if (rv) { + LOG(PIL_INFO, "Host %s ipmilan status failure." + , node->hostname); + ret = S_ACCESS; + } else { + LOG(PIL_INFO, "Host %s ipmilan status OK." + , node->hostname); + } + + } + + return ret; +} + +/* + * This function returns the list of hosts that's configured. + * + * The detailed configuration is disabled because the STONITH command can be + * run by anyone so there is a security risk if that to be exposed. + */ + +static char * +get_config_string(struct pluginDevice * nd, int index) +{ + struct ipmilanHostInfo * host; + int i; + + if (index >= nd->hostcount || index < 0) { + return (NULL); + } + + host = nd->hostlist; + for (i = 0; i < index; i++) { + host = host->next; + } + + return STRDUP(host->hostname); +} + + +/* + * Return the list of hosts configured for this ipmilan device + * + */ + +static char ** +ipmilan_hostlist(StonithPlugin *s) +{ + int numnames = 0; + char ** ret = NULL; + struct pluginDevice* nd; + int j; + + ERRIFWRONGDEV(s,NULL); + + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in ipmi_hostlist"); + return(NULL); + } + numnames = nd->hostcount; + + ret = (char **)MALLOC((numnames + 1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return (ret); + } + + memset(ret, 0, (numnames + 1)*sizeof(char*)); + + for (j = 0; j < numnames; ++j) { + ret[j] = get_config_string(nd, j); + if (!ret[j]) { + stonith_free_hostlist(ret); + ret = NULL; + break; + } + strdown(ret[j]); + } + + return(ret); +} + +/* + * Parse the config information, and stash it away... + * + * The buffer for each string is MAX_IPMI_STRING_LEN bytes long. + * Right now it is set to 64. Hope this is enough. + * + */ + +#define MAX_IPMI_STRING_LEN 64 + +/* + * Reset the given host on this StonithPlugin device. + */ +static int +ipmilan_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + struct pluginDevice * nd; + struct ipmilanHostInfo * node; + int i; + + ERRIFWRONGDEV(s,S_OOPS); + + nd = (struct pluginDevice *)s; + for( i=0, node = nd->hostlist; + i < nd->hostcount; i++, node = node->next ) { + if (strcasecmp(node->hostname, host) == 0) { + break; + } + } + + if (i >= nd->hostcount) { + LOG(PIL_CRIT, "Host %s is not configured in this STONITH " + " module. Please check your configuration file.", host); + return (S_OOPS); + } + + rc = do_ipmi_cmd(node, request); + if (!rc) { + LOG(PIL_INFO, "Host %s ipmilan-reset.", host); + } else { + LOG(PIL_INFO, "Host %s ipmilan-reset error. Error = %d." + , host, rc); + } + return rc; +} + +/* + * Get configuration parameter names + */ +static const char * const * +ipmilan_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = + { ST_HOSTNAME, ST_IPADDR, ST_PORT, ST_AUTH, + ST_PRIV, ST_LOGIN, ST_PASSWD, ST_RESET_METHOD, NULL}; + return ret; +} + +/* + * Set the configuration parameters + */ +static int +ipmilan_set_config(StonithPlugin* s, StonithNVpair * list) +{ + struct pluginDevice* nd; + int rc; + struct ipmilanHostInfo * tmp; + const char *reset_opt; + + StonithNamesToGet namestocopy [] = + { {ST_HOSTNAME, NULL} + , {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_AUTH, NULL} + , {ST_PRIV, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice *)s; + + ERRIFWRONGDEV(s, S_OOPS); + if (nd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + tmp = ST_MALLOCT(struct ipmilanHostInfo); + tmp->hostname = namestocopy[0].s_value; + tmp->ipaddr = namestocopy[1].s_value; + tmp->portnumber = atoi(namestocopy[2].s_value); + FREE(namestocopy[2].s_value); + if (namestocopy[3].s_value == NULL) { + LOG(PIL_CRIT, "ipmilan auth type is NULL. See " + "README.ipmilan for allowed values"); + return S_OOPS; + } else if (strcmp(namestocopy[3].s_value, "none") == 0) { + tmp->authtype = 0; + } else if (strcmp(namestocopy[3].s_value, "md2") == 0) { + tmp->authtype = 1; + } else if (strcmp(namestocopy[3].s_value, "md5") == 0) { + tmp->authtype = 2; + } else if (strcmp(namestocopy[3].s_value, "key") == 0 || + strcmp(namestocopy[3].s_value, "password") == 0 || + strcmp(namestocopy[3].s_value, "straight") == 0) { + tmp->authtype = 4; + } else { + LOG(PIL_CRIT, "ipmilan auth type '%s' invalid. See " + "README.ipmilan for allowed values", namestocopy[3].s_value); + return S_OOPS; + } + FREE(namestocopy[3].s_value); + if (namestocopy[4].s_value == NULL) { + LOG(PIL_CRIT, "ipmilan priv value is NULL. See " + "README.ipmilan for allowed values"); + return S_OOPS; + } else if (strcmp(namestocopy[4].s_value, "operator") == 0) { + tmp->privilege = 3; + } else if (strcmp(namestocopy[4].s_value, "admin") == 0) { + tmp->privilege = 4; + } else { + LOG(PIL_CRIT, "ipmilan priv value '%s' invalid. See " + "README.ipmilan for allowed values", namestocopy[4].s_value); + return(S_OOPS); + } + FREE(namestocopy[4].s_value); + tmp->username = namestocopy[5].s_value; + tmp->password = namestocopy[6].s_value; + reset_opt = OurImports->GetValue(list, ST_RESET_METHOD); + if (!reset_opt || !strcmp(reset_opt, "power_cycle")) { + tmp->reset_method = 0; + } else if (!strcmp(reset_opt, "hard_reset")) { + tmp->reset_method = 1; + } else { + LOG(PIL_CRIT, "ipmilan reset_method '%s' invalid", reset_opt); + return S_OOPS; + } + + if (nd->hostlist == NULL ) { + nd->hostlist = tmp; + nd->hostlist->prev = tmp; + nd->hostlist->next = tmp; + } else { + tmp->prev = nd->hostlist->prev; + tmp->next = nd->hostlist; + nd->hostlist->prev->next = tmp; + nd->hostlist->prev = tmp; + } + nd->hostcount++; + + return(S_OK); +} + +static const char * +ipmilan_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice * nd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + + case ST_DEVICENAME: + ret = nd->hostlist ? nd->hostlist->hostname : NULL; + break; + + case ST_DEVICEDESCR: + ret = "IPMI LAN STONITH device\n"; + break; + + case ST_DEVICEURL: + ret = "http://www.intel.com/design/servers/ipmi/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = ipmilanXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * ipmilan StonithPlugin destructor... + * + * The hostlist is a link list. So have to iterate through. + */ +static void +ipmilan_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + struct ipmilanHostInfo * host; + int i; + + VOIDERRIFWRONGDEV(s); + + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginid; + + if (nd->hostlist) { + host = nd->hostlist->prev; + for (i = 0; i < nd->hostcount; i++) { + struct ipmilanHostInfo * host_prev = host->prev; + + FREE(host->hostname); + FREE(host->ipaddr); + FREE(host->username); + FREE(host->password); + + FREE(host); + host = host_prev; + } + } + + nd->hostcount = -1; + FREE(nd); + ipmi_leave(); +} + +/* Create a new ipmilan StonithPlugin device. Too bad this function can't be static */ +static StonithPlugin * +ipmilan_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + LOG(PIL_WARN, "The ipmilan stonith plugin is deprecated! Please use external/ipmi."); + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = 0; + nd->idinfo = DEVICE; + nd->sp.s_ops = &ipmilanOps; + return(&(nd->sp)); +} diff --git a/lib/plugins/stonith/ipmilan.h b/lib/plugins/stonith/ipmilan.h new file mode 100644 index 0000000..fb548f0 --- /dev/null +++ b/lib/plugins/stonith/ipmilan.h @@ -0,0 +1,41 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define ST_IPMI_STATUS 4 +#include + +struct ipmilanHostInfo { + char * hostname; + char * ipaddr; + int portnumber; + int authtype; + int privilege; + char * username; + char * password; + int reset_method; + + struct ipmilanHostInfo * prev; + struct ipmilanHostInfo * next; +}; + +int do_ipmi_cmd(struct ipmilanHostInfo * host, int request); +void ipmi_leave(void); diff --git a/lib/plugins/stonith/ipmilan_command.c b/lib/plugins/stonith/ipmilan_command.c new file mode 100644 index 0000000..a3de493 --- /dev/null +++ b/lib/plugins/stonith/ipmilan_command.c @@ -0,0 +1,399 @@ +/* + * This program is largely based on the ipmicmd.c program that's part of OpenIPMI package. + * + * Copyright Intel Corp. + * Yixiong.Zou@intel.com + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include + +#include /* malloc() */ +#include /* getopt() */ +#include /* strerror() */ +#include /* gethostbyname() */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipmilan.h" +#include +#include + +#include +extern const PILPluginImports* PluginImports; + +/* #define DUMP_MSG 0 */ +#define OPERATION_TIME_OUT 10 + +os_handler_t *os_hnd=NULL; +selector_t *os_sel; +static ipmi_con_t *con; +extern os_handler_t ipmi_os_cb_handlers; +static int reset_method; + +static int request_done = 0; +static int op_done = 0; + +typedef enum ipmi_status { + /* + IPMI_CONNECTION_FAILURE, + IPMI_SEND_FAILURE, + IPMI_BAD_REQUEST, + IPMI_REQUEST_FAILED, + IPMI_TIME_OUT, + */ + IPMI_RUNNING = 99, +} ipmi_status_t; + +static ipmi_status_t gstatus; + +typedef enum chassis_control_request { + POWER_DOWN = 0X00, + POWER_UP = 0X01, + POWER_CYCLE = 0X02, + HARD_RESET = 0X03, + PULSE_DIAGNOSTIC_INTERRUPT = 0X04, + SOFT_SHUTDOWN = 0X05 +} chassis_control_request_t; + +void dump_msg_data(ipmi_msg_t *msg, ipmi_addr_t *addr, const char *type); +int rsp_handler(ipmi_con_t *ipmi, ipmi_msgi_t *rspi); + +void send_ipmi_cmd(ipmi_con_t *con, int request); + +void timed_out(selector_t *sel, sel_timer_t *timer, void *data); + +void +timed_out(selector_t *sel, sel_timer_t *timer, void *data) +{ + PILCallLog(PluginImports->log,PIL_CRIT, "IPMI operation timed out... :(\n"); + gstatus = S_TIMEOUT; +} + +void +dump_msg_data(ipmi_msg_t *msg, ipmi_addr_t *addr, const char *type) +{ + ipmi_system_interface_addr_t *smi_addr = NULL; + int i; + ipmi_ipmb_addr_t *ipmb_addr = NULL; + + if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) { + smi_addr = (struct ipmi_system_interface_addr *) addr; + + fprintf(stderr, "%2.2x %2.2x %2.2x %2.2x ", + addr->channel, + msg->netfn, + smi_addr->lun, + msg->cmd); + } else if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE) + || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)) { + ipmb_addr = (struct ipmi_ipmb_addr *) addr; + + fprintf(stderr, "%2.2x %2.2x %2.2x %2.2x ", + addr->channel, + msg->netfn, + ipmb_addr->lun, + msg->cmd); + } + + for (i = 0; i < msg->data_len; i++) { + if (((i%16) == 0) && (i != 0)) { + printf("\n "); + } + fprintf(stderr, "%2.2x ", msg->data[i]); + } + fprintf(stderr, "\n"); +} + +/* + * This function gets called after the response comes back + * from the IPMI device. + * + * Some IPMI device does not return success, 0x00, to the + * remote node when the power-reset was issued. + * + * The host who sent the ipmi cmd might get a 0xc3, + * a timeout instead. This creates problems for + * STONITH operation, where status is critical. :( + * + * Right now I am only checking 0xc3 as the return. + * If your IPMI device returns some wired code after + * reset, you might want to add it in this code block. + * + */ + +int +rsp_handler(ipmi_con_t *ipmi, ipmi_msgi_t *rspi) +{ + int rv; + long request; + + /*dump_msg_data(&rspi->msg, &rspi->addr, "response");*/ + request = (long) rspi->data1; + + op_done = 1; + if( !rspi || !(rspi->msg.data) ) { + PILCallLog(PluginImports->log,PIL_CRIT, "No data received\n"); + gstatus = S_RESETFAIL; + return IPMI_MSG_ITEM_NOT_USED; + } + rv = rspi->msg.data[0]; + /* some IPMI device might not issue 0x00, success, for reset command. + instead, a 0xc3, timeout, is returned. */ + if (rv == 0x00) { + gstatus = S_OK; + } else if((rv == 0xc3 || rv == 0xff) && request == ST_GENERIC_RESET) { + PILCallLog(PluginImports->log,PIL_WARN , + "IPMI reset request failed: %x, but we assume that it succeeded\n", rv); + gstatus = S_OK; + } else { + PILCallLog(PluginImports->log,PIL_INFO + , "IPMI request %ld failed: %x\n", request, rv); + gstatus = S_RESETFAIL; + } + return IPMI_MSG_ITEM_NOT_USED; +} + +void +send_ipmi_cmd(ipmi_con_t *con, int request) +{ + ipmi_addr_t addr; + unsigned int addr_len; + ipmi_msg_t msg; + struct ipmi_system_interface_addr *si; + int rv; + ipmi_msgi_t *rspi; + /* chassis control command request is only 1 byte long */ + unsigned char cc_data = POWER_CYCLE; + + si = (void *) &addr; + si->lun = 0x00; + si->channel = IPMI_BMC_CHANNEL; + si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + addr_len = sizeof(*si); + + msg.netfn = IPMI_CHASSIS_NETFN; + msg.cmd = IPMI_CHASSIS_CONTROL_CMD; + msg.data = &cc_data; + msg.data_len = 1; + + switch (request) { + case ST_POWERON: + cc_data = POWER_UP; + break; + + case ST_POWEROFF: + cc_data = POWER_DOWN; + break; + + case ST_GENERIC_RESET: + cc_data = (reset_method ? POWER_CYCLE : HARD_RESET); + break; + + case ST_IPMI_STATUS: + msg.netfn = IPMI_APP_NETFN; + msg.cmd = IPMI_GET_DEVICE_ID_CMD; + msg.data_len = 0; + break; + + default: + gstatus = S_INVAL; + return; + } + + gstatus = S_ACCESS; + rspi = calloc(1, sizeof(ipmi_msgi_t)); + if (NULL == rspi) { + PILCallLog(PluginImports->log,PIL_CRIT, "Error sending IPMI command: Out of memory\n"); + } else { + rspi->data1 = (void *) (long) request; + rv = con->send_command(con, &addr, addr_len, &msg, rsp_handler, rspi); + if (rv == -1) { + PILCallLog(PluginImports->log,PIL_CRIT, "Error sending IPMI command: %x\n", rv); + } else { + request_done = 1; + } + } + + return; +} + +static void +con_changed_handler(ipmi_con_t *ipmi, int err, unsigned int port_num, + int still_connected, void *cb_data) +{ + int * request; + + if (err) { + PILCallLog(PluginImports->log,PIL_CRIT, "Unable to setup connection: %x\n", err); + return; + } + + if( !request_done ) { + request = (int *) cb_data; + send_ipmi_cmd(ipmi, *request); + } +} + +static int +setup_ipmi_conn(struct ipmilanHostInfo * host, int *request) +{ + int rv; + + struct hostent *ent; + struct in_addr lan_addr[2]; + int lan_port[2]; + int num_addr = 1; + int authtype = 0; + int privilege = 0; + char username[17]; + char password[17]; + + /*DEBUG_MSG_ENABLE();*/ + + os_hnd = ipmi_posix_get_os_handler(); + if (!os_hnd) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_smi_setup_con: Unable to allocate os handler"); + return 1; + } + + rv = sel_alloc_selector(os_hnd, &os_sel); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "Could not allocate selector\n"); + return rv; + } + + ipmi_posix_os_handler_set_sel(os_hnd, os_sel); + + rv = ipmi_init(os_hnd); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_init erro: %d ", rv); + return rv; + } + + ent = gethostbyname(host->ipaddr); + if (!ent) { + PILCallLog(PluginImports->log,PIL_CRIT, "gethostbyname failed: %s\n", strerror(h_errno)); + return 1; + } + + memcpy(&lan_addr[0], ent->h_addr_list[0], ent->h_length); + lan_port[0] = host->portnumber; + lan_port[1] = 0; + + authtype = host->authtype; + privilege = host->privilege; + + memcpy(username, host->username, sizeof(username)); + memcpy(password, host->password, sizeof(password)); + + reset_method = host->reset_method; + + rv = ipmi_lan_setup_con(lan_addr, lan_port, num_addr, + authtype, privilege, + username, strlen(username), + password, strlen(password), + os_hnd, os_sel, + &con); + + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "ipmi_lan_setup_con: %s\n", strerror(rv)); + return S_ACCESS; + } + +#if OPENIPMI_VERSION_MAJOR < 2 + con->set_con_change_handler(con, con_changed_handler, request); +#else + con->add_con_change_handler(con, con_changed_handler, request); +#endif + + gstatus = IPMI_RUNNING; + + rv = con->start_con(con); + if (rv) { + PILCallLog(PluginImports->log,PIL_CRIT, "Could not start IPMI connection: %x\n", rv); + gstatus = S_BADCONFIG; + return rv; + } + return S_OK; +} + +void +ipmi_leave() +{ + if( con && con->close_connection ) { + con->close_connection(con); + con = NULL; + } + if( os_sel ) { + sel_free_selector(os_sel); + os_sel = NULL; + } +} + +int +do_ipmi_cmd(struct ipmilanHostInfo * host, int request) +{ + int rv; + sel_timer_t * timer; + struct timeval timeout; + + request_done = 0; + op_done = 0; + + if( !os_hnd ) { + rv = setup_ipmi_conn(host, &request); + if( rv ) { + return rv; + } + } else { + send_ipmi_cmd(con, request); + } + + gettimeofday(&timeout, NULL); + timeout.tv_sec += OPERATION_TIME_OUT; + timeout.tv_usec += 0; + + sel_alloc_timer(os_sel, timed_out, NULL, &timer); + sel_start_timer(timer, &timeout); + + while (!op_done) { + rv = sel_select(os_sel, NULL, 0, NULL, NULL); + if (rv == -1) { + break; + } + } + + sel_free_timer(timer); + return gstatus; +} + +#if OPENIPMI_VERSION_MAJOR < 2 +void +posix_vlog(char *format, enum ipmi_log_type_e log_type, va_list ap) +{ +} +#endif diff --git a/lib/plugins/stonith/ipmilan_test.c b/lib/plugins/stonith/ipmilan_test.c new file mode 100644 index 0000000..47859a0 --- /dev/null +++ b/lib/plugins/stonith/ipmilan_test.c @@ -0,0 +1,63 @@ +/* + * Stonith module for ipmi lan Stonith device + * + * Copyright (c) 2003 Intel Corp. + * Yixiong Zou + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * A quick test program to verify that IPMI host is setup correctly. + * + * You will need to modify the values in user, pass, ip, and port. + */ + +#include +#include +#include "ipmilan.h" +#include + +int main(int argc, char * argv[]) +{ + struct ipmilanHostInfo host; + int request = 2; + int rv; + + char user[] = "joe"; + char pass[] = "blow"; + char ip[] = "192.168.1.7"; + + host.hostname = NULL; + host.portnumber = 999; + host.authtype = IPMI_AUTHTYPE_NONE; + host.privilege = IPMI_PRIVILEGE_ADMIN; + + host.ipaddr = ip; + memcpy(host.username, user, sizeof(user)); + memcpy(host.password, pass, sizeof(pass)); + /* + memset(host.username, 0, sizeof(host.username)); + memset(host.password, 0, sizeof(host.password)); + */ + + rv = do_ipmi_cmd(&host, request); + if (rv) + printf("rv = %d, operation failed. \n", rv); + else + printf("operation succeeded. \n"); + return rv; +} diff --git a/lib/plugins/stonith/meatware.c b/lib/plugins/stonith/meatware.c new file mode 100644 index 0000000..029ba35 --- /dev/null +++ b/lib/plugins/stonith/meatware.c @@ -0,0 +1,351 @@ +/* + * Stonith module for Human Operator Stonith device + * + * Copyright (c) 2001 Gregor Binder + * + * This module is largely based on the "NULL Stonith device", written + * by Alan Robertson , using code by David C. Teigland + * originally appeared in the GFS stomith + * meatware agent. + * + * Mangled by Zhaokai , IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#define DEVICE "Meatware STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN meatware +#define PIL_PLUGIN_S "meatware" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +static StonithPlugin * meatware_new(const char *); +static void meatware_destroy(StonithPlugin *); +static int meatware_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * meatware_get_confignames(StonithPlugin *); +static const char * meatware_getinfo(StonithPlugin * s, int InfoType); +static int meatware_status(StonithPlugin * ); +static int meatware_reset_req(StonithPlugin * s, int request, const char * host); +static char ** meatware_hostlist(StonithPlugin *); + +static struct stonith_ops meatwareOps ={ + meatware_new, /* Create new STONITH object */ + meatware_destroy, /* Destroy STONITH object */ + meatware_getinfo, /* Return STONITH info string */ + meatware_get_confignames,/* Return STONITH info string */ + meatware_set_config, /* Get configuration from NVpairs */ + meatware_status, /* Return STONITH device status */ + meatware_reset_req, /* Request a reset */ + meatware_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &meatwareOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Meatware STONITH device. + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "MeatwareDevice-Stonith"; +static const char * NOTpluginID = "Meatware device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *meatwareXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +meatware_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this Meat device + */ + +static char ** +meatware_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd; + + ERRIFWRONGDEV(s,NULL); + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in Meatware_list_hosts"); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)nd->hostlist); +} + +/* + * Parse the config information, and stash it away... + */ + +static int +Meat_parse_config_info(struct pluginDevice* nd, const char * info) +{ + LOG(PIL_INFO , "parse config info info=%s",info); + if (nd->hostcount >= 0) { + return(S_OOPS); + } + + nd->hostlist = OurImports->StringToHostList(info); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount]; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return(S_OK); +} + + +/* + * Indicate that host must be power cycled manually. + */ +static int +meatware_reset_req(StonithPlugin * s, int request, const char * host) +{ + int fd, rc; + const char * meatpipe_pr = HA_VARRUNDIR "/meatware"; /* if you intend to + change this, modify + meatclient.c as well */ + + char line[256], meatpipe[256]; + char resp_addr[50], resp_mw[50], resp_result[50]; + + + ERRIFWRONGDEV(s,S_OOPS); + + snprintf(meatpipe, 256, "%s.%s", meatpipe_pr, host); + umask(0); + unlink(meatpipe); + + rc = mkfifo(meatpipe, (S_IRUSR | S_IWUSR)); + + if (rc < 0) { + LOG(PIL_CRIT, "cannot create FIFO for Meatware_reset_host"); + return S_OOPS; + } + + LOG(PIL_CRIT, "OPERATOR INTERVENTION REQUIRED to reset %s.", host); + LOG(PIL_CRIT, "Run \"meatclient -c %s\" AFTER power-cycling the " + "machine.", host); + + fd = open(meatpipe, O_RDONLY); + + if (fd < 0) { + LOG(PIL_CRIT, "cannot open FIFO for Meatware_reset_host"); + return S_OOPS; + } + + alarm(600); + memset(line, 0, 256); + rc = read(fd, line, 256); + alarm(0); + + if (rc < 0) { + LOG(PIL_CRIT, "read error on FIFO for Meatware_reset_host"); + return S_OOPS; + } + + memset(resp_mw, 0, 50); + memset(resp_result, 0, 50); + memset(resp_addr, 0, 50); + + if (sscanf(line, "%s %s %s", resp_mw, resp_result, resp_addr) < 3) { + LOG(PIL_CRIT, "Format error - failed to Meatware-reset node %s", + host); + return S_RESETFAIL; + } + + strdown(resp_addr); + + if (strncmp(resp_mw, "meatware", 8) || + strncmp(resp_result, "reply", 5) || + strncasecmp(resp_addr, host, strlen(resp_addr))) { + LOG(PIL_CRIT, "failed to Meatware-reset node %s", host); + return S_RESETFAIL; + }else{ + LOG(PIL_INFO, "node Meatware-reset: %s", host); + unlink(meatpipe); + return S_OK; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +meatware_set_config(StonithPlugin* s, StonithNVpair *list) +{ + + struct pluginDevice* nd; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + rc = Meat_parse_config_info(nd, namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return STONITH config vars + */ +static const char * const * +meatware_get_confignames(StonithPlugin* p) +{ + static const char * MeatwareParams[] = {ST_HOSTLIST, NULL }; + return MeatwareParams; +} + +/* + * Return STONITH info string + */ +static const char * +meatware_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + case ST_DEVICENAME: + ret = "Your Name Here"; + break; + case ST_DEVICEDESCR: + ret = "Human (meatware) intervention STONITH device.\n" + "This STONITH agent prompts a human to reset a machine.\n" + "The human tells it when the reset was completed."; + break; + case ST_CONF_XML: /* XML metadata */ + ret = meatwareXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Meat Stonith destructor... + */ +static void +meatware_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(nd); +} + +/* Create a new Meatware Stonith device. */ + +static StonithPlugin * +meatware_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = -1; + nd->idinfo = DEVICE; + nd->sp.s_ops = &meatwareOps; + + return &(nd->sp); +} diff --git a/lib/plugins/stonith/null.c b/lib/plugins/stonith/null.c new file mode 100644 index 0000000..0d0cf04 --- /dev/null +++ b/lib/plugins/stonith/null.c @@ -0,0 +1,260 @@ +/* + * Stonith module for NULL Stonith device + * + * Copyright (c) 2000 Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#define DEVICE "NULL STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN null +#define PIL_PLUGIN_S "null" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static StonithPlugin* null_new(const char *); +static void null_destroy(StonithPlugin *); +static int null_set_config(StonithPlugin* +, StonithNVpair*); +static const char * const * null_get_confignames(StonithPlugin*); +static const char * null_getinfo(StonithPlugin * s, int InfoType); +static int null_status(StonithPlugin * ); +static int null_reset_req(StonithPlugin * s +, int request, const char * host); +static char ** null_hostlist(StonithPlugin *); + +static struct stonith_ops nullOps ={ + null_new, /* Create new STONITH object */ + null_destroy, /* Destroy STONITH object */ + null_getinfo, /* Return STONITH info string */ + null_get_confignames, /* Return list of config params */ + null_set_config, /* configure fron NV pairs */ + null_status, /* Return STONITH device status */ + null_reset_req, /* Request a reset */ + null_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &nullOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Null STONITH device. We are very agreeable, but don't do much :-) + */ + + +static const char * pluginid = "nullDevice-Stonith"; +static const char * NOTpluginID = "Null device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *nullXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +null_status(StonithPlugin *s) +{ + + ERRIFWRONGDEV(s, S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this NULL device + */ + +static char ** +null_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd = (struct pluginDevice*)s; + + ERRIFWRONGDEV(s, NULL); + return OurImports->CopyHostList((const char * const *)nd->hostlist); +} + + +/* + * Pretend to reset the given host on this Stonith device. + * (we don't even error check the "request" type) + */ +static int +null_reset_req(StonithPlugin * s, int request, const char * host) +{ + + ERRIFWRONGDEV(s,S_OOPS); + + /* Real devices need to pay attention to the "request" */ + /* (but we don't care ;-)) */ + + LOG(PIL_INFO, "Host null-reset: %s", host); + return S_OK; +} + + +static const char * const * +null_get_confignames(StonithPlugin* p) +{ + static const char * NullParams[] = {ST_HOSTLIST, NULL }; + return NullParams; +} + +/* + * Parse the config information in the given string, + * and stash it away... + */ +static int +null_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* nd = (struct pluginDevice*) s; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + ERRIFWRONGDEV(s, S_OOPS); + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + nd->hostlist = OurImports->StringToHostList(namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount] + ; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return nd->hostcount ? S_OK : S_BADCONFIG; +} + +static const char * +null_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd = (struct pluginDevice*) s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + + case ST_DEVICENAME: + ret = "(nil)"; + break; + + case ST_DEVICEDESCR: + ret = "Dummy (do-nothing) STONITH device\n" + "FOR TESTING ONLY!"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = nullXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * NULL Stonith destructor... + */ +static void +null_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTpluginID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(s); +} + +/* Create a new Null Stonith device. + * Too bad this function can't be static + */ +static StonithPlugin * +null_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->idinfo = DEVICE; + nd->sp.s_ops = &nullOps; + return (StonithPlugin *)nd; +} diff --git a/lib/plugins/stonith/nw_rpc100s.c b/lib/plugins/stonith/nw_rpc100s.c new file mode 100644 index 0000000..5ba0827 --- /dev/null +++ b/lib/plugins/stonith/nw_rpc100s.c @@ -0,0 +1,779 @@ +/* + * Stonith module for Night/Ware RPC100S + * + * Original code from baytech.c by + * Copyright (c) 2000 Alan Robertson + * + * Modifications for NW RPC100S + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers + * + * Mangled by Zhaokai , IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#define DEVICE "NW RPC100S Power Switch" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN nw_rpc100s +#define PIL_PLUGIN_S "nw_rpc100s" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define MAX_CFGLINE 256 +#include + +static StonithPlugin * nw_rpc100s_new(const char *); +static void nw_rpc100s_destroy(StonithPlugin *); +static int nw_rpc100s_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * nw_rpc100s_get_confignames(StonithPlugin *); +static const char * nw_rpc100s_getinfo(StonithPlugin * s, int InfoType); +static int nw_rpc100s_status(StonithPlugin * ); +static int nw_rpc100s_reset_req(StonithPlugin * s, int request, const char * host); +static char ** nw_rpc100s_hostlist(StonithPlugin *); + +static struct stonith_ops nw_rpc100sOps ={ + nw_rpc100s_new, /* Create new STONITH object */ + nw_rpc100s_destroy, /* Destroy STONITH object */ + nw_rpc100s_getinfo, /* Return STONITH info string */ + nw_rpc100s_get_confignames,/* Return STONITH info string */ + nw_rpc100s_set_config, /* Get configuration from NVpairs */ + nw_rpc100s_status, /* Return STONITH device status */ + nw_rpc100s_reset_req, /* Request a reset */ + nw_rpc100s_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_signal.h" + +#define DOESNT_USE_STONITHKILLCOMM +#define DOESNT_USE_STONITHSCANLINE +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &nw_rpc100sOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + The Nightware RPS-100S is manufactured by: + + Micro Energetics Corp + +1 703 250-3000 + http://www.nightware.com/ + + Thank you to David Hicks of Micro Energetics Corp. for providing + a demo unit to write this software. + + This switch has a very simple protocol, + You issue a command and it gives a response. + Sample commands are conveniently documented on a sticker on the + bottom of the device. + + The switch accepts a single command of the form + + //0,yyy,zzz[/m][/h] + + Where yyy is the wait time before activiting the relay. + zzz is the relay time. + + The default is that the relay is in a default state of ON, which + means that usually yyy is the number of seconds to wait + before shutting off the power and zzz is the number of seconds the + power remains off. There is a dip switch to change the default + state to 'OFF'. Don't set this switch. It will screw up this code. + + An asterisk can be used for zzz to specify an infinite switch time. + The /m /and /h command options will convert the specified wait and + switch times to either minutewes or hours. + + A response is either + OK + or + Invalid Entry + + + As far as THIS software is concerned, we have to implement 4 commands: + + status --> //0,0,BOGUS; # Not a real command, this is just a + # probe to see if switch is alive + open(on) --> //0,0,0; # turn power to default state (on) + close(off) --> //0,0,*; # leave power off indefinitely + reboot --> //0,0,10; # immediately turn power off for 10 seconds. + + and expect the response 'OK' to confirm that the unit is operational. +*/ + + + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + + int fd; /* FD open to the serial port */ + + char * device; /* Serial device name to use to communicate + to this RPS10 + */ + + char * node; /* Name of the node that this is controlling */ + +}; + +/* This string is used to identify this type of object in the config file */ +static const char * pluginid = "NW_RPC100S"; +static const char * NOTrpcid = "NW RPC100S device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *nw_rpc100sXML = + XML_PARAMETERS_BEGIN + XML_TTYDEV_PARM + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +/* + * Different expect strings that we get from the NW_RPC100S + * Remote Power Controllers... + */ + +static struct Etoken NWtokOK[] = { {"OK", 0, 0}, {NULL,0,0}}; +static struct Etoken NWtokInvalidEntry[] = { {"Invalid Entry", 0, 0}, {NULL,0,0}}; +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken NWtokCRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int RPCConnect(struct pluginDevice * ctx); +static int RPCDisconnect(struct pluginDevice * ctx); + +static int RPCReset(struct pluginDevice*, int unitnum, const char * rebootid); +#if defined(ST_POWERON) +static int RPCOn(struct pluginDevice*, int unitnum, const char * rebootid); +#endif +#if defined(ST_POWEROFF) +static int RPCOff(struct pluginDevice*, int unitnum, const char * rebootid); +#endif +static int RPCNametoOutlet ( struct pluginDevice * ctx, const char * host ); + +/*static int RPC_parse_config_info(struct pluginDevice* ctx, const char * info);*/ + + +#define SENDCMD(cmd, timeout) { \ + int return_val = RPCSendCommand(ctx, cmd, timeout); \ + if (return_val != S_OK) { \ + return return_val; \ + } \ + } + +/* + * RPCSendCommand - send a command to the specified outlet + */ +static int +RPCSendCommand (struct pluginDevice *ctx, const char *command, int timeout) +{ + char writebuf[64]; /* All commands are short. + They should be WAY LESS + than 64 chars long! + */ + int return_val; /* system call result */ + fd_set rfds, wfds, xfds; + /* list of FDs for select() */ + struct timeval tv; /* */ + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + + snprintf (writebuf, sizeof(writebuf), "%s\r", command); + + if (Debug) { + LOG(PIL_DEBUG, "Sending %s", writebuf); + } + + /* Make sure the serial port won't block on us. use select() */ + FD_SET(ctx->fd, &wfds); + FD_SET(ctx->fd, &xfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + return_val = select(ctx->fd+1, NULL, &wfds,&xfds, &tv); + if (return_val == 0) { + /* timeout waiting on serial port */ + LOG(PIL_CRIT, "%s: Timeout writing to %s" + , pluginid, ctx->device); + return S_TIMEOUT; + } else if ((return_val == -1) || FD_ISSET(ctx->fd, &xfds)) { + /* an error occured */ + LOG(PIL_CRIT, "%s: Error before writing to %s: %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* send the command */ + if (write(ctx->fd, writebuf, strlen(writebuf)) != + (int)strlen(writebuf)) { + LOG(PIL_CRIT, "%s: Error writing to %s : %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* suceeded! */ + return S_OK; + +} /* end RPCSendCommand() */ + +/* + * RPCReset - Reset (power-cycle) the given outlet number + * + * This device can only control one power outlet - unitnum is ignored. + * + */ +static int +RPCReset(struct pluginDevice* ctx, int unitnum, const char * rebootid) +{ + + if (Debug) { + LOG(PIL_DEBUG, "Calling RPCReset (%s)", pluginid); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "toggle power" command */ + SENDCMD("//0,0,10;\r\n", 12); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + if (Debug) { + LOG(PIL_DEBUG, "Got OK"); + } + EXPECT(ctx->fd, NWtokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL"); + } + + return(S_OK); + +} /* end RPCReset() */ + + +#if defined(ST_POWERON) +/* + * RPCOn - Turn OFF the given outlet number + */ +static int +RPCOn(struct pluginDevice* ctx, int unitnum, const char * host) +{ + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "On" command */ + SENDCMD("//0,0,0;\r\n", 10); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + EXPECT(ctx->fd, NWtokCRNL, 2); + + return(S_OK); + +} /* end RPCOn() */ +#endif + + +#if defined(ST_POWEROFF) +/* + * RPCOff - Turn Off the given outlet number + */ +static int +RPCOff(struct pluginDevice* ctx, int unitnum, const char * host) +{ + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid + , ctx->device); + return S_OOPS; + } + + /* send the "Off" command */ + SENDCMD("//0,0,*;\r\n", 10); + + /* Expect "OK" */ + EXPECT(ctx->fd, NWtokOK, 5); + EXPECT(ctx->fd, NWtokCRNL, 2); + + return(S_OK); + +} /* end RPCOff() */ +#endif + + +/* + * nw_rpc100s_status - API entry point to probe the status of the stonith device + * (basically just "is it reachable and functional?", not the + * status of the individual outlets) + * + * Returns: + * S_OOPS - some error occured + * S_OK - if the stonith device is reachable and online. + */ +static int +nw_rpc100s_status(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_status (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + if (RPCConnect(ctx) != S_OK) { + return(S_OOPS); + } + + /* The "connect" really does enough work to see if the + controller is alive... It verifies that it is returning + RPS-10 Ready + */ + + return(RPCDisconnect(ctx)); +} + +/* + * nw_rpc100s_hostlist - API entry point to return the list of hosts + * for the devices on this NW_RPC100S unit + * + * This type of device is configured from the config file, + * so we don't actually have to connect to figure this + * out, just peruse the 'ctx' structure. + * Returns: + * NULL on error + * a malloced array, terminated with a NULL, + * of null-terminated malloc'ed strings. + */ +static char ** +nw_rpc100s_hostlist(StonithPlugin *s) +{ + char ** ret = NULL; /* list to return */ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_hostlist (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,NULL); + + ctx = (struct pluginDevice*) s; + + ret = OurImports->StringToHostList(ctx->node); + if (ret == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + } else { + strdown(ret[0]); + } + + return(ret); +} /* end si_hostlist() */ + +/* + * Parse the given configuration information, and stash it away... + * + * contains the parameters specific to this type of object + * + * The format of for this module is: + * [ ] ... + * + * e.g. A machine named 'nodea' can kill a machine named 'nodeb' through + * a device attached to serial port /dev/ttyS0. + * A machine named 'nodeb' can kill machines 'nodea' and 'nodec' + * through a device attached to serial port /dev/ttyS1 (outlets 0 + * and 1 respectively) + * + * stonith nodea NW_RPC100S /dev/ttyS0 nodeb 0 + * stonith nodeb NW_RPC100S /dev/ttyS0 nodea 0 nodec 1 + * + * Another possible configuration is for 2 stonith devices accessible + * through 2 different serial ports on nodeb: + * + * stonith nodeb NW_RPC100S /dev/ttyS0 nodea 0 + * stonith nodeb NW_RPC100S /dev/ttyS1 nodec 0 + */ + +/*static int +RPC_parse_config_info(struct pluginDevice* ctx, const char * info) +{ +}*/ + + +/* + * RPCConnect - + * + * Connect to the given NW_RPC100S device. + * Side Effects + * ctx->fd now contains a valid file descriptor to the serial port + * ??? LOCK THE SERIAL PORT ??? + * + * Returns + * S_OK on success + * S_OOPS on error + * S_TIMEOUT if the device did not respond + * + */ +static int +RPCConnect(struct pluginDevice * ctx) +{ + + /* Open the serial port if it isn't already open */ + if (ctx->fd < 0) { + struct termios tio; + + if (OurImports->TtyLock(ctx->device) < 0) { + LOG(PIL_CRIT, "%s: TtyLock failed.", pluginid); + return S_OOPS; + } + + ctx->fd = open (ctx->device, O_RDWR); + if (ctx->fd <0) { + LOG(PIL_CRIT, "%s: Can't open %s : %s" + , pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* set the baudrate to 9600 8 - N - 1 */ + memset (&tio, 0, sizeof(tio)); + + /* ??? ALAN - the -tradtitional flag on gcc causes the + CRTSCTS constant to generate a warning, and warnings + are treated as errors, so I can't set this flag! - EZA ??? + + Hmmm. now that I look at the documentation, RTS + is just wired high on this device! we don't need it. + */ + /* tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD | CRTSCTS ;*/ + tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD ; + tio.c_lflag = ICANON; + + if (tcsetattr (ctx->fd, TCSANOW, &tio) < 0) { + LOG(PIL_CRIT, "%s: Can't set attributes %s : %s" + , pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + /* flush all data to and fro the serial port before we start */ + if (tcflush (ctx->fd, TCIOFLUSH) < 0) { + LOG(PIL_CRIT, "%s: Can't flush %s : %s" + , pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + + } + + + /* Send a BOGUS string */ + SENDCMD("//0,0,BOGUS;\r\n", 10); + + /* Should reply with "Invalid Command" */ + if (Debug) { + LOG(PIL_DEBUG, "Waiting for \"Invalid Entry\""); + } + EXPECT(ctx->fd, NWtokInvalidEntry, 12); + if (Debug) { + LOG(PIL_DEBUG, "Got Invalid Entry"); + } + EXPECT(ctx->fd, NWtokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL"); + } + + return(S_OK); +} + +static int +RPCDisconnect(struct pluginDevice * ctx) +{ + + if (ctx->fd >= 0) { + /* Flush the serial port, we don't care what happens to the characters + and failing to do this can cause close to hang. + */ + tcflush(ctx->fd, TCIOFLUSH); + close (ctx->fd); + if (ctx->device != NULL) { + OurImports->TtyUnlock(ctx->device); + } + } + ctx->fd = -1; + + return S_OK; +} + +/* + * RPCNametoOutlet - Map a hostname to an outlet number on this stonith device. + * + * Returns: + * 0 on success ( the outlet number on the RPS10 - there is only one ) + * -1 on failure (host not found in the config file) + * + */ +static int +RPCNametoOutlet ( struct pluginDevice * ctx, const char * host ) +{ + int rc = -1; + + if (!strcasecmp(ctx->node, host)) { + rc = 0; + } + + return rc; +} + + +/* + * nw_rpc100s_reset - API call to Reset (reboot) the given host on + * this Stonith device. This involves toggling the power off + * and then on again, OR just calling the builtin reset command + * on the stonith device. + */ +static int +nw_rpc100s_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = S_OK; + int outletnum = -1; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "Calling nw_rpc100s_reset (%s)", pluginid); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + + if ((rc = RPCConnect(ctx)) != S_OK) { + return(rc); + } + + outletnum = RPCNametoOutlet(ctx, host); + LOG(PIL_DEBUG, "zk:outletname=%d", outletnum); + + if (outletnum < 0) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , ctx->device, host); + RPCDisconnect(ctx); + return(S_BADHOST); + } + + switch(request) { + +#if defined(ST_POWERON) + case ST_POWERON: + rc = RPCOn(ctx, outletnum, host); + break; +#endif +#if defined(ST_POWEROFF) + case ST_POWEROFF: + rc = RPCOff(ctx, outletnum, host); + break; +#endif + case ST_GENERIC_RESET: + rc = RPCReset(ctx, outletnum, host); + break; + default: + rc = S_INVAL; + break; + } + + lorc = RPCDisconnect(ctx); + + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +nw_rpc100s_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice* ctx; + StonithNamesToGet namestocopy [] = + { {ST_TTYDEV, NULL} + , {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + int rc; + + + ERRIFWRONGDEV(s,S_OOPS); + if (s->isconfigured) { + return S_OOPS; + } + + ctx = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + ctx->device = namestocopy[0].s_value; + ctx->node = namestocopy[1].s_value; + + return S_OK; +} + +/* + * Return STONITH config vars + */ +static const char * const * +nw_rpc100s_get_confignames(StonithPlugin* p) +{ + static const char * RpcParams[] = {ST_TTYDEV , ST_HOSTLIST, NULL }; + return RpcParams; +} + + + +/* + * nw_rpc100s_getinfo - API entry point to retrieve something from the handle + */ +static const char * +nw_rpc100s_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ctx; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ctx = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ctx->idinfo; + break; + case ST_DEVICENAME: + ret = ctx->device; + break; + case ST_DEVICEDESCR: + ret = "Micro Energetics Night/Ware RPC100S"; + break; + case ST_DEVICEURL: + ret = "http://www.microenergeticscorp.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = nw_rpc100sXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * nw_rpc100s_destroy - API entry point to destroy a NW_RPC100S Stonith object. + */ +static void +nw_rpc100s_destroy(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + VOIDERRIFWRONGDEV(s); + + ctx = (struct pluginDevice *)s; + + ctx->pluginid = NOTrpcid; + + /* close the fd if open and set ctx->fd to invalid */ + RPCDisconnect(ctx); + + if (ctx->device != NULL) { + FREE(ctx->device); + ctx->device = NULL; + } + if (ctx->node != NULL) { + FREE(ctx->node); + ctx->node = NULL; + } + FREE(ctx); +} + +/* + * nw_rpc100s_new - API entry point called to create a new NW_RPC100S Stonith + * device object. + */ +static StonithPlugin * +nw_rpc100s_new(const char *subplugin) +{ + struct pluginDevice* ctx = ST_MALLOCT(struct pluginDevice); + + if (ctx == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ctx, 0, sizeof(*ctx)); + ctx->pluginid = pluginid; + ctx->fd = -1; + ctx->device = NULL; + ctx->node = NULL; + ctx->idinfo = DEVICE; + ctx->sp.s_ops = &nw_rpc100sOps; + + return &(ctx->sp); +} diff --git a/lib/plugins/stonith/rcd_serial.c b/lib/plugins/stonith/rcd_serial.c new file mode 100644 index 0000000..f1396a7 --- /dev/null +++ b/lib/plugins/stonith/rcd_serial.c @@ -0,0 +1,602 @@ +/* + * Stonith module for RCD_SERIAL Stonith device + * + * Original code from null.c by + * Copyright (c) 2000 Alan Robertson + * + * Copious borrowings from nw_rpc100s.c by + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers + * + * and from apcsmart.c by + * Copyright (c) 2000 Andreas Piesk + * + * Modifications for RC Delayed Serial Ciruit by + * Copyright (c) 2002 John Sutton + * + * Mangled by Zhaokai , IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#define DEVICE "RC Delayed Serial" +#include "stonith_plugin_common.h" +#include "stonith_signal.h" + +#define PIL_PLUGIN rcd_serial +#define PIL_PLUGIN_S "rcd_serial" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#define ST_DTRRTS "dtr_rts" +#define ST_MSDURATION "msduration" +#define MAX_RCD_SERIALLINE 512 + +#include +#include +#include + +static StonithPlugin* rcd_serial_new(const char *); +static void rcd_serial_destroy(StonithPlugin *); +static int rcd_serial_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rcd_serial_get_confignames(StonithPlugin *); +static const char * rcd_serial_getinfo(StonithPlugin * s, int InfoType); +static int rcd_serial_status(StonithPlugin * ); +static int rcd_serial_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rcd_serial_hostlist(StonithPlugin *); + +static struct stonith_ops rcd_serialOps ={ + rcd_serial_new, /* Create new STONITH object */ + rcd_serial_destroy, /* Destroy STONITH object */ + rcd_serial_getinfo, /* Return STONITH info string */ + rcd_serial_get_confignames,/* Return STONITH info string */ + rcd_serial_set_config, /* Get configuration from NVpairs */ + rcd_serial_status, /* Return STONITH device status */ + rcd_serial_reset_req, /* Request a reset */ + rcd_serial_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &rcd_serialOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* ------------------- RCD specific stuff -------------- */ + +/* + A diagram of a circuit suitable for use with this plugin is in + README.rcd_serial which should be somewhere in the distribution (if Alan + includes it ;-) and/or at http://www.scl.co.uk/rcd_serial/ (if I remember + to put it there ;-). + + Once you've got this built, you can test things using the stonith command + as follows: + + stonith -L + will show a list of plugin types, including rcd_serial + + stonith -t rcd_serial testhost + will show required parameters + + In these 3 you can either pass the params after the -p option or you can + put them in a config file and use -F configname instead of -p "param ...". + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" -S + will show the status of the device + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" -l + will list the single host testhost + + stonith -t rcd_serial -p "testhost /dev/ttyS0 rts 1500" testhost + will reset testhost (provided testhost has its reset pins + suitably wired to the RTS signal coming out of port /dev/ttyS0 + and that 1.5s is enough time to cause a reset ;-) +*/ + +/* + Define RCD_NOPAUSE if you are using the serial port for some purpose + _in_addition_ to using it as a stonith device. For example, I use one + of the input pins on the same serial port for monitoring the state of a + power supply. Periodically, a cron job has to open the port to read the + state of this input and thus has to clear down the output pins DTR and RTS + in order to avoid causing a spurious stonith reset. Now, if it should + happen that just at the same time as we are _really_ trying to do a stonith + reset, this cron job starts up, then the stonith reset won't occur ;-(. + To avoid this (albeit unlikely) outcome, you should #define RCD_NOPAUSE. + The effect of this is that instead of setting the line high just once and + then falling into a pause until an alarm goes off, rather, the program falls + into a loop which is continuously setting the line high. That costs us a bit + of CPU as compared with sitting in a pause, but hey, how often is this code + going to get exercised! Never, we hope... +*/ +#undef RCD_NOPAUSE + +#ifdef RCD_NOPAUSE +static int RCD_alarmcaught; +#endif + +/* + * own prototypes + */ + +static void RCD_alarm_handler(int sig); +static int RCD_open_serial_port(char *device); +static int RCD_close_serial_port(char *device, int fd); + +static void +RCD_alarm_handler(int sig) { +#if !defined(HAVE_POSIX_SIGNALS) + if (sig) { + signal(sig, SIG_DFL); + }else{ + signal(sig, RCD_alarm_handler); + } +#else + struct sigaction sa; + sigset_t sigmask; + + /* Maybe a bit naughty but it works and it saves duplicating all */ + /* this setup code - if handler called with 0 for sig, we install */ + /* ourself as handler. */ + if (sig) { + sa.sa_handler = (void (*)(int))SIG_DFL; + }else{ + sa.sa_handler = RCD_alarm_handler; + } + + sigemptyset(&sigmask); + sa.sa_mask = sigmask; + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); +#endif + +#ifdef RCD_NOPAUSE + RCD_alarmcaught = 1; +#endif + return; +} + +static int +RCD_open_serial_port(char *device) { + int fd; + int status; + int bothbits; + + if (OurImports->TtyLock(device) < 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: ttylock failed.", __FUNCTION__); + } + return -1; + } + + bothbits = TIOCM_RTS | TIOCM_DTR; + + if ((fd = open(device, O_RDONLY | O_NDELAY)) != -1) { + /* + Opening the device always sets DTR & CTS high. + Clear them down immediately. + */ + status = ioctl(fd, TIOCMBIC, &bothbits); + /* If there was an error clearing bits, set the fd to -1 + * ( indicates error ) */ + if (status != 0 ) { + fd = -1; + } + } + + return fd; +} + +static int +RCD_close_serial_port(char *device, int fd) { + int rc = close(fd); + if (device != NULL) { + OurImports->TtyUnlock(device); + } + return rc; +} + +/* + * RCD_Serial STONITH device. + */ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; /* name of single host we can reset */ + int hostcount; /* i.e. 1 after initialisation */ + char * device; /* serial device name */ + char * signal; /* either rts or dtr */ + long msduration; /* how long (ms) to assert the signal */ +}; + +static const char * pluginid = "RCD_SerialDevice-Stonith"; +static const char * NOTrcd_serialID = "RCD_Serial device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_DTRRTS_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_DTRRTS \ + XML_PARM_SHORTDESC_END + +#define XML_DTRRTS_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The hardware handshaking technique to use with " ST_TTYDEV "(\"dtr\" or \"rts\")" \ + XML_PARM_LONGDESC_END + +#define XML_DTRRTS_PARM \ + XML_PARAMETER_BEGIN(ST_DTRRTS, "string", "1", "0") \ + XML_DTRRTS_SHORTDESC \ + XML_DTRRTS_LONGDESC \ + XML_PARAMETER_END + +#define XML_MSDURATION_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MSDURATION \ + XML_PARM_SHORTDESC_END + +#define XML_MSDURATION_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The delay duration (in milliseconds) between the assertion of the control signal on " ST_TTYDEV " and the closing of the reset switch" \ + XML_PARM_LONGDESC_END + +#define XML_MSDURATION_PARM \ + XML_PARAMETER_BEGIN(ST_MSDURATION, "string", "1", "0") \ + XML_MSDURATION_SHORTDESC \ + XML_MSDURATION_LONGDESC \ + XML_PARAMETER_END + +static const char *rcd_serialXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_TTYDEV_PARM + XML_DTRRTS_PARM + XML_MSDURATION_PARM + XML_PARAMETERS_END; + +static int +rcd_serial_status(StonithPlugin *s) +{ + struct pluginDevice* rcd; + int fd; + const char * err; + + ERRIFWRONGDEV(s,S_OOPS); + + rcd = (struct pluginDevice*) s; + + /* + All we can do is make sure the serial device exists and + can be opened and closed without error. + */ + + if ((fd = RCD_open_serial_port(rcd->device)) == -1) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: open of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + if (RCD_close_serial_port(rcd->device, fd) != 0) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: close of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + return S_OK; +} + + +/* + * Return the list of hosts configured for this RCD_SERIAL device + */ +static char ** +rcd_serial_hostlist(StonithPlugin *s) +{ + struct pluginDevice* rcd; + + ERRIFWRONGDEV(s,NULL); + rcd = (struct pluginDevice*) s; + if (rcd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in RCD_SERIAL_list_hosts"); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)rcd->hostlist); +} + +/* + * At last, we really do it! I don't know what the request argument + * is so am just ignoring it... + */ +static int +rcd_serial_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice* rcd; + int fd; + int sigbit; + struct itimerval timer; + const char * err; + + ERRIFWRONGDEV(s,S_OOPS); + + rcd = (struct pluginDevice *) s; + + /* check that host matches */ + if (strcasecmp(host, rcd->hostlist[0])) { + LOG(PIL_CRIT, "%s: host '%s' not in hostlist.", + __FUNCTION__, host); + return(S_BADHOST); + } + + /* Set the appropriate bit for the signal */ + sigbit = *(rcd->signal)=='r' ? TIOCM_RTS : TIOCM_DTR; + + /* Set up the timer */ + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_usec = 0; + timer.it_value.tv_sec = rcd->msduration / 1000; + timer.it_value.tv_usec = (rcd->msduration % 1000) * 1000; + + /* Open the device */ + if ((fd = RCD_open_serial_port(rcd->device)) == -1) { +#ifdef HAVE_STRERROR + err = strerror(errno); +#else + err = sys_errlist[errno]; +#endif + LOG(PIL_CRIT, "%s: open of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + /* Start the timer */ + RCD_alarm_handler(0); +#ifdef RCD_NOPAUSE + RCD_alarmcaught = 0; +#endif + setitimer(ITIMER_REAL, &timer, 0); + + /* Set the line high */ + ioctl(fd, TIOCMBIS, &sigbit); + + /* Wait for the alarm signal */ +#ifdef RCD_NOPAUSE + while(!RCD_alarmcaught) ioctl(fd, TIOCMBIS, &sigbit); +#else + pause(); +#endif + + /* Clear the line low */ + ioctl(fd, TIOCMBIC, &sigbit); + + /* Close the port */ + if (RCD_close_serial_port(rcd->device, fd) != 0) { + err = strerror(errno); + LOG(PIL_CRIT, "%s: close of %s failed - %s", + __FUNCTION__, rcd->device, err); + return(S_OOPS); + } + + LOG(PIL_INFO,"Host rcd_serial-reset: %s", host); + return S_OK; +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +rcd_serial_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice* rcd; + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {ST_TTYDEV, NULL} + , {ST_DTRRTS, NULL} + , {ST_MSDURATION, NULL} + , {NULL, NULL} + }; + char *endptr; + int rc = 0; + + LOG(PIL_DEBUG, "%s:called", __FUNCTION__); + + ERRIFWRONGDEV(s,S_OOPS); + if (s->isconfigured) { + return S_OOPS; + } + + rcd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + if ((rcd->hostlist = (char **)MALLOC(2*sizeof(char*))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + FREE(namestocopy[0].s_value); + FREE(namestocopy[1].s_value); + FREE(namestocopy[2].s_value); + FREE(namestocopy[3].s_value); + return S_OOPS; + } + rcd->hostlist[0] = namestocopy[0].s_value; + strdown(rcd->hostlist[0]); + rcd->hostlist[1] = NULL; + rcd->hostcount = 1; + rcd->device = namestocopy[1].s_value; + rcd->signal = namestocopy[2].s_value; + if (strcmp(rcd->signal, "rts") && strcmp(rcd->signal, "dtr")) { + LOG(PIL_CRIT, "%s: Invalid signal name '%s'", + pluginid, rcd->signal); + FREE(namestocopy[3].s_value); + return S_BADCONFIG; + } + + errno = 0; + rcd->msduration = strtol(namestocopy[3].s_value, &endptr, 0); + if (((errno == ERANGE) + && (rcd->msduration == LONG_MIN || rcd->msduration == LONG_MAX)) + || *endptr != 0 || rcd->msduration < 1) { + LOG(PIL_CRIT, "%s: Invalid msduration '%s'", + pluginid, namestocopy[3].s_value); + FREE(namestocopy[3].s_value); + return S_BADCONFIG; + } + FREE(namestocopy[3].s_value); + + return S_OK; +} + +/* + * Return STONITH config vars + */ +static const char * const * +rcd_serial_get_confignames(StonithPlugin* p) +{ + static const char * RcdParams[] = {ST_HOSTLIST, ST_TTYDEV + , ST_DTRRTS, ST_MSDURATION, NULL }; + return RcdParams; +} + +/* + * Return STONITH info string + */ +static const char * +rcd_serial_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* rcd; + const char * ret; + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + rcd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = rcd->idinfo; + break; + case ST_DEVICENAME: + ret = rcd->device; + break; + case ST_DEVICEDESCR: + ret = "RC Delayed Serial STONITH Device\n" + "This device can be constructed cheaply from" + " readily available components,\n" + "with sufficient expertise and testing.\n" + "See README.rcd_serial for circuit diagram.\n"; + break; + case ST_DEVICEURL: + ret = "http://www.scl.co.uk/rcd_serial/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = rcd_serialXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * RCD_SERIAL Stonith destructor... + */ +static void +rcd_serial_destroy(StonithPlugin *s) +{ + struct pluginDevice* rcd; + + VOIDERRIFWRONGDEV(s); + + rcd = (struct pluginDevice *)s; + + rcd->pluginid = NOTrcd_serialID; + if (rcd->hostlist) { + stonith_free_hostlist(rcd->hostlist); + rcd->hostlist = NULL; + } + rcd->hostcount = -1; + if (rcd->device) { + FREE(rcd->device); + } + if (rcd->signal) { + FREE(rcd->signal); + } + FREE(rcd); +} + +/* + * Create a new RCD_Serial Stonith device. + * Too bad this function can't be static. (Hmm, weird, it _is_ static?) + */ +static StonithPlugin * +rcd_serial_new(const char *subplugin) +{ + struct pluginDevice* rcd = ST_MALLOCT(struct pluginDevice); + + if (rcd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(rcd, 0, sizeof(*rcd)); + + rcd->pluginid = pluginid; + rcd->hostlist = NULL; + rcd->hostcount = -1; + rcd->device = NULL; + rcd->signal = NULL; + rcd->msduration = 0; + rcd->idinfo = DEVICE; + rcd->sp.s_ops = &rcd_serialOps; + + return &(rcd->sp); +} diff --git a/lib/plugins/stonith/rhcs.c b/lib/plugins/stonith/rhcs.c new file mode 100644 index 0000000..293a081 --- /dev/null +++ b/lib/plugins/stonith/rhcs.c @@ -0,0 +1,1035 @@ +/* + * Stonith module for RedHat Cluster Suite fencing plugins + * + * Copyright (c) 2001 SuSE Linux AG + * Portions Copyright (c) 2004, tummy.com, ltd. + * + * Based on ssh.c, Authors: Joachim Gleissner , + * Lars Marowsky-Bree + * Modified for external.c: Scott Kleihege + * Reviewed, tested, and config parsing: Sean Reifschneider + * And overhauled by Lars Marowsky-Bree , so the circle + * closes... + * Mangled by Zhaokai , IBM, 2005 + * Changed to allow full-featured external plugins by Dave Blaschke + * + * Modified for rhcs.c: Dejan Muhamedagic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN rhcs +#define PIL_PLUGIN_S "rhcs" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL + +#include + +static StonithPlugin * rhcs_new(const char *); +static void rhcs_destroy(StonithPlugin *); +static int rhcs_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rhcs_get_confignames(StonithPlugin *); +static const char * rhcs_getinfo(StonithPlugin * s, int InfoType); +static int rhcs_status(StonithPlugin * ); +static int rhcs_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rhcs_hostlist(StonithPlugin *); + +static struct stonith_ops rhcsOps ={ + rhcs_new, /* Create new STONITH object */ + rhcs_destroy, /* Destroy STONITH object */ + rhcs_getinfo, /* Return STONITH info string */ + rhcs_get_confignames, /* Return STONITH info string */ + rhcs_set_config, /* Get configuration from NVpairs */ + rhcs_status, /* Return STONITH device status */ + rhcs_reset_req, /* Request a reset */ + rhcs_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &rhcsOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * RHCS STONITH device + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + GHashTable * cmd_opts; + char * subplugin; + char ** confignames; + char * hostlist; + char * outputbuf; + xmlDoc * metadata; +}; + +static const char * pluginid = "RHCSDevice-Stonith"; +static const char * NOTpluginID = "RHCS device has been destroyed"; + +/* Prototypes */ + +/* Run the command with op and return the exit status + the output + * (NULL -> discard output) */ +static int rhcs_run_cmd(struct pluginDevice *sd, const char *op, + const char *host, char **output); +/* Just free up the configuration and the memory, if any */ +static void rhcs_unconfig(struct pluginDevice *sd); + +static int +rhcs_status(StonithPlugin *s) +{ + struct pluginDevice * sd; + const char * op = "monitor"; + int rc; + char * output = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + rc = rhcs_run_cmd(sd, op, NULL, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + } + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + } + if (output) { + FREE(output); + } + return rc; +} + +static int +get_num_tokens(char *str) +{ + int namecount = 0; + + if (!str) + return namecount; + while (*str != EOS) { + str += strspn(str, WHITESPACE); + if (*str == EOS) + break; + str += strcspn(str, WHITESPACE); + namecount++; + } + return namecount; +} + +static char ** +rhcs_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + const char * op = "gethosts"; + int i, namecount; + char ** ret; + char * tmp; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + namecount = get_num_tokens(sd->hostlist); + ret = MALLOC((namecount+1)*sizeof(char *)); + if (!ret) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + memset(ret, 0, (namecount+1)*sizeof(char *)); + + /* White-space split the sd->hostlist here */ + i = 0; + tmp = strtok(sd->hostlist, WHITESPACE); + while (tmp != NULL) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s host %s", + __FUNCTION__, sd->subplugin, tmp); + } + ret[i] = STRDUP(tmp); + if (!ret[i]) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + stonith_free_hostlist(ret); + return NULL; + } + i++; + tmp = strtok(NULL, WHITESPACE); + } + + if (i == 0) { + LOG(PIL_CRIT, "%s: '%s %s' returned an empty hostlist", + __FUNCTION__, sd->subplugin, op); + stonith_free_hostlist(ret); + ret = NULL; + } + + return(ret); +} + +static int +rhcs_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice * sd; + const char * op; + int rc; + char * output = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "Host rhcs-reset initiating on %s", host); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + + switch (request) { + case ST_GENERIC_RESET: + op = "reboot"; + break; + + case ST_POWEROFF: + op = "off"; + break; + + case ST_POWERON: + op = "on"; + break; + + default: + LOG(PIL_CRIT, "%s: Unknown stonith request %d", + __FUNCTION__, request); + return S_OOPS; + break; + } + + rc = rhcs_run_cmd(sd, op, host, &output); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' for host %s failed with rc %d", + __FUNCTION__, sd->subplugin, op, host, rc); + if (output) { + LOG(PIL_CRIT, "plugin output: %s", output); + FREE(output); + } + return S_RESETFAIL; + } + else { + if (Debug) { + LOG(PIL_DEBUG, "%s: running '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + if (output) { + LOG(PIL_INFO, "plugin output: %s", output); + FREE(output); + } + return S_OK; + } + +} + +static int +rhcs_parse_config_info(struct pluginDevice* sd, StonithNVpair * info) +{ + char * key; + char * value; + StonithNVpair * nv; + + sd->hostlist = NULL; + sd->cmd_opts = g_hash_table_new(g_str_hash, g_str_equal); + + /* TODO: Maybe treat "" as delimeters too so + * whitespace can be passed to the plugins... */ + for (nv = info; nv->s_name; nv++) { + if (!nv->s_name || !nv->s_value) { + continue; + } + key = STRDUP(nv->s_name); + if (!key) { + goto err_mem; + } + value = STRDUP(nv->s_value); + if (!value) { + FREE(key); + goto err_mem; + } + if (!strcmp(key,"hostlist")) { + sd->hostlist = value; + FREE(key); + } else { + g_hash_table_insert(sd->cmd_opts, key, value); + } + } + + return(S_OK); + +err_mem: + LOG(PIL_CRIT, "%s: out of memory!", __FUNCTION__); + rhcs_unconfig(sd); + + return(S_OOPS); +} + +static gboolean +let_remove_eachitem(gpointer key, gpointer value, gpointer user_data) +{ + if (key) { + FREE(key); + } + if (value) { + FREE(value); + } + return TRUE; +} + +static void +rhcs_unconfig(struct pluginDevice *sd) { + if (sd->cmd_opts) { + g_hash_table_foreach_remove(sd->cmd_opts, + let_remove_eachitem, NULL); + g_hash_table_destroy(sd->cmd_opts); + sd->cmd_opts = NULL; + } + if (sd->hostlist) { + FREE(sd->hostlist); + sd->hostlist = NULL; + } + if (sd->metadata) { + xmlFreeDoc(sd->metadata); + xmlCleanupParser(); + sd->metadata = NULL; + } +} + +/* + * Parse the information in the given string + * and stash it away... + */ +static int +rhcs_set_config(StonithPlugin* s, StonithNVpair *list) +{ + struct pluginDevice * sd; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + /* make sure that command has not already been set */ + if (s->isconfigured) { + return(S_OOPS); + } + + sd = (struct pluginDevice*) s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(S_OOPS); + } + +#if 0 + /* the required parameters may be acquired from the metadata + * */ + if (sd->confignames == NULL) { + /* specified by name=value pairs, check required parms */ + if (rhcs_get_confignames(s) == NULL) { + return(S_OOPS); + } + + for (p = sd->confignames; *p; p++) { + if (OurImports->GetValue(list, *p) == NULL) { + LOG(PIL_INFO, "Cannot get parameter %s from " + "StonithNVpair", *p); + } + } + } +#endif + + return rhcs_parse_config_info(sd, list); +} + + +/* Only interested in regular files starting with fence_ that are also executable */ +static int +rhcs_exec_select(const struct dirent *dire) +{ + struct stat statf; + char filename[FILENAME_MAX]; + int rc; + + rc = snprintf(filename, FILENAME_MAX, "%s/%s", + STONITH_RHCS_PLUGINDIR, dire->d_name); + if (rc <= 0 || rc >= FILENAME_MAX) { + return 0; + } + + if ((stat(filename, &statf) == 0) && + (S_ISREG(statf.st_mode)) && + (statf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) { + if (statf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_WARN, "Executable file %s ignored " + "(writable by group/others)", filename); + return 0; + }else{ + return 1; + } + } + + return 0; +} + +static xmlDoc * +load_metadata(struct pluginDevice * sd) +{ + xmlDoc *doc = NULL; + const char *op = "metadata"; + int rc; + char *ret = NULL; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + rc = rhcs_run_cmd(sd, op, NULL, &ret); + if (rc != 0) { + LOG(PIL_CRIT, "%s: '%s %s' failed with rc %d", + __FUNCTION__, sd->subplugin, op, rc); + if (ret) { + LOG(PIL_CRIT, "plugin output: %s", ret); + FREE(ret); + } + goto err; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: '%s %s' returned %d", + __FUNCTION__, sd->subplugin, op, rc); + } + + doc = xmlParseMemory(ret, strlen(ret)); + if (!doc) { + LOG(PIL_CRIT, "%s: could not parse metadata", + __FUNCTION__); + goto err; + } + sd->metadata = doc; + +err: + if (ret) { + FREE(ret); + } + return doc; +} + +static const char *skip_attrs[] = { + "action", "verbose", "debug", "version", "help", "separator", + NULL +}; +/* XML stuff */ +typedef int (*node_proc) + (xmlNodeSet *nodes, struct pluginDevice *sd); + +static int +proc_xpath(const char *xpathexp, struct pluginDevice *sd, node_proc fun) +{ + xmlXPathObject *xpathObj = NULL; + xmlXPathContext *xpathCtx = NULL; + int rc = 1; + + if (!sd->metadata && !load_metadata(sd)) { + LOG(PIL_INFO, "%s: no metadata", __FUNCTION__); + return 1; + } + + /* Create xpath evaluation context */ + xpathCtx = xmlXPathNewContext(sd->metadata); + if(xpathCtx == NULL) { + LOG(PIL_CRIT, "%s: unable to create new XPath context", __FUNCTION__); + return 1; + } + /* Evaluate xpath expression */ + xpathObj = xmlXPathEvalExpression((const xmlChar*)xpathexp, xpathCtx); + if(xpathObj == NULL) { + LOG(PIL_CRIT, "%s: unable to evaluate expression %s", + __FUNCTION__, xpathexp); + goto err; + } + + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + rc = fun(xpathObj->nodesetval, sd); +err: + if (xpathObj) + xmlXPathFreeObject(xpathObj); + if (xpathCtx) + xmlXPathFreeContext(xpathCtx); + return rc; +} + +static int +load_confignames(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + xmlChar *attr; + const char * const*skip; + xmlNode *cur; + int i, j, namecount; + + namecount = nodes->nodeNr; + if (!namecount) { + LOG(PIL_INFO, "%s: no configuration parameters", __FUNCTION__); + return 1; + } + sd->confignames = (char **)MALLOC((namecount+1)*sizeof(char *)); + if (sd->confignames == NULL) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return 1; + } + + /* now copy over confignames */ + j = 0; + for (i = 0; i < nodes->nodeNr; i++) { + cur = nodes->nodeTab[i]; + attr = xmlGetProp(cur, (const xmlChar*)"name"); + for (skip = skip_attrs; *skip; skip++) { + if (!strcmp(*skip,(char *)attr)) + goto skip; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: %s configname %s", + __FUNCTION__, sd->subplugin, (char *)attr); + } + sd->confignames[j++] = strdup((char *)attr); + xmlFree(attr); + skip: + continue; + } + sd->confignames[j] = NULL; + + return 0; +} + +static int +dump_content(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + xmlChar *content = NULL; + xmlNode *cur; + int rc = 1; + + if (!nodes || !nodes->nodeTab || !nodes->nodeTab[0]) { + LOG(PIL_WARN, "%s: %s no nodes", + __FUNCTION__, sd->subplugin); + return 1; + } + cur = nodes->nodeTab[0]; + content = xmlNodeGetContent(cur); + if (content && strlen((char *)content) > 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s found content for %s", + __FUNCTION__, sd->subplugin, cur->name); + } + sd->outputbuf = STRDUP((char *)content); + rc = !(*sd->outputbuf); + } else { + if (Debug) { + LOG(PIL_DEBUG, "%s: %s no content for %s", + __FUNCTION__, sd->subplugin, cur->name); + } + rc = 1; + } + + if (content) + xmlFree(content); + return rc; +} + +static int +dump_params_xml(xmlNodeSet *nodes, struct pluginDevice *sd) +{ + int len = 0; + xmlNode *cur; + xmlBuffer *xml_buffer = NULL; + int rc = 0; + + xml_buffer = xmlBufferCreate(); + if (!xml_buffer) { + LOG(PIL_CRIT, "%s: failed to create xml buffer", __FUNCTION__); + return 1; + } + cur = nodes->nodeTab[0]; + len = xmlNodeDump(xml_buffer, sd->metadata, cur, 0, TRUE); + if (len <= 0) { + LOG(PIL_CRIT, "%s: could not dump xml for %s", + __FUNCTION__, (char *)xmlGetProp(cur, (const xmlChar*)"name")); + rc = 1; + goto err; + } + sd->outputbuf = STRDUP((char *)xml_buffer->content); +err: + xmlBufferFree(xml_buffer); + return rc; +} + +/* + * Return STONITH config vars + */ +static const char * const * +rhcs_get_confignames(StonithPlugin* p) +{ + struct pluginDevice * sd; + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + sd = (struct pluginDevice *)p; + + if (sd->subplugin != NULL) { + if (!sd->metadata && !load_metadata(sd)) { + return NULL; + } + proc_xpath("/resource-agent/parameters/parameter", sd, load_confignames); + } else { + /* return list of subplugins in rhcs directory */ + struct dirent ** files = NULL; + int dircount; + + /* get the rhcs plugin's confignames (list of subplugins) */ + dircount = scandir(STONITH_RHCS_PLUGINDIR, &files, + SCANSEL_CAST rhcs_exec_select, NULL); + if (dircount < 0) { + return NULL; + } + + sd->confignames = (char **)MALLOC((dircount+1)*sizeof(char *)); + if (!sd->confignames) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + return NULL; + } + + for (i = 0; i < dircount; i++) { + sd->confignames[i] = STRDUP(files[i]->d_name+strlen("fence_")); + free(files[i]); + files[i] = NULL; + } + free(files); + sd->confignames[dircount] = NULL; + } + + return (const char * const *)sd->confignames; +} + +/* + * Return STONITH info string + */ +static const char * +fake_op(struct pluginDevice * sd, const char *op) +{ + const char *pfx = "RHCS plugin "; + char *ret = NULL; + + LOG(PIL_INFO, "rhcs plugins don't really support %s", op); + ret = MALLOC(strlen(pfx) + strlen(op) + 1); + strcpy(ret, pfx); + strcat(ret, op); + sd->outputbuf = ret; + return(ret); +} + +static const char * +rhcs_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd; + const char * op; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + sd = (struct pluginDevice *)s; + if (sd->subplugin == NULL) { + LOG(PIL_CRIT, "%s: invoked without subplugin", __FUNCTION__); + return(NULL); + } + + if (!sd->metadata && !load_metadata(sd)) { + return NULL; + } + + switch (reqtype) { + case ST_DEVICEID: + op = "getinfo-devid"; + return fake_op(sd, op); + break; + + case ST_DEVICENAME: + if (!proc_xpath("/resource-agent/shortdesc", sd, dump_content)) { + return sd->outputbuf; + } else { + op = "getinfo-devname"; + return fake_op(sd, op); + } + break; + + case ST_DEVICEDESCR: + if (!proc_xpath("/resource-agent/longdesc", sd, dump_content)) { + return sd->outputbuf; + } else { + op = "getinfo-devdescr"; + return fake_op(sd, op); + } + break; + + case ST_DEVICEURL: + op = "getinfo-devurl"; + return fake_op(sd, op); + break; + + case ST_CONF_XML: + if (!proc_xpath("/resource-agent/parameters", sd, dump_params_xml)) { + return sd->outputbuf; + } + break; + + default: + return NULL; + } + return NULL; +} + +/* + * RHCS Stonith destructor... + */ +static void +rhcs_destroy(StonithPlugin *s) +{ + struct pluginDevice * sd; + char ** p; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginID; + rhcs_unconfig(sd); + if (sd->confignames != NULL) { + for (p = sd->confignames; *p; p++) { + FREE(*p); + } + FREE(sd->confignames); + sd->confignames = NULL; + } + if (sd->subplugin != NULL) { + FREE(sd->subplugin); + sd->subplugin = NULL; + } + if (sd->outputbuf != NULL) { + FREE(sd->outputbuf); + sd->outputbuf = NULL; + } + FREE(sd); +} + +/* Create a new rhcs Stonith device */ +static StonithPlugin * +rhcs_new(const char *subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + if (subplugin != NULL) { + sd->subplugin = STRDUP(subplugin); + if (sd->subplugin == NULL) { + FREE(sd); + return(NULL); + } + } + sd->sp.s_ops = &rhcsOps; + return &(sd->sp); +} + +#define MAXLINE 512 + +static void +printparam_to_fd(int fd, const char *key, const char *value) +{ + char arg[MAXLINE]; + int cnt; + + cnt = snprintf(arg, MAXLINE, "%s=%s\n", key, value); + if (cnt <= 0 || cnt >= MAXLINE) { + LOG(PIL_CRIT, "%s: param/value pair too large", __FUNCTION__); + return; + } + if (Debug) { + LOG(PIL_DEBUG, "set rhcs plugin param '%s=%s'", key, value); + } + if (write(fd, arg, cnt) < 0) { + LOG(PIL_CRIT, "%s: write: %m", __FUNCTION__); + } +} + +static void +rhcs_print_var(gpointer key, gpointer value, gpointer user_data) +{ + printparam_to_fd(GPOINTER_TO_UINT(user_data), (char *)key, (char *)value); +} + +/* Run the command with op as command line argument(s) and return the exit + * status + the output */ + +static int +rhcs_run_cmd(struct pluginDevice *sd, const char *op, const char *host, char **output) +{ + const int BUFF_LEN=4096; + char buff[BUFF_LEN]; + int read_len = 0; + int rc; + char * data = NULL; + char cmd[FILENAME_MAX+64]; + struct stat buf; + int slen; + int pid, status; + int fd1[2]; /* our stdout/their stdin */ + int fd2[2]; /* our stdin/their stdout and stderr */ + + rc = snprintf(cmd, FILENAME_MAX, "%s/fence_%s", + STONITH_RHCS_PLUGINDIR, sd->subplugin); + if (rc <= 0 || rc >= FILENAME_MAX) { + LOG(PIL_CRIT, "%s: external command too long.", __FUNCTION__); + return -1; + } + + if (stat(cmd, &buf) != 0) { + LOG(PIL_CRIT, "%s: stat(2) of %s failed: %s", + __FUNCTION__, cmd, strerror(errno)); + return -1; + } + + if (!S_ISREG(buf.st_mode) + || (!(buf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { + LOG(PIL_CRIT, "%s: %s found NOT to be executable.", + __FUNCTION__, cmd); + return -1; + } + + if (buf.st_mode & (S_IWGRP|S_IWOTH)) { + LOG(PIL_CRIT, "%s: %s found to be writable by group/others, " + "NOT executing for security purposes.", + __FUNCTION__, cmd); + return -1; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: Calling '%s'", __FUNCTION__, cmd ); + } + + if (pipe(fd1) || pipe(fd2)) + goto err; + + pid = fork(); + if (pid < 0) { + LOG(PIL_CRIT, "%s: fork: %m", __FUNCTION__); + goto err; + } + if (pid) { /* parent */ + close(fd1[0]); + close(fd2[1]); + + if (sd->cmd_opts) { + printparam_to_fd(fd1[1], "agent", sd->subplugin); + printparam_to_fd(fd1[1], "action", op); + if( host ) + printparam_to_fd(fd1[1], "nodename", host); + g_hash_table_foreach(sd->cmd_opts, rhcs_print_var, + GUINT_TO_POINTER(fd1[1])); + } + close(fd1[1]); /* we have nothing more to say */ + + fcntl(fd2[0], F_SETFL, fcntl(fd2[0], F_GETFL, 0) | O_NONBLOCK); + data = NULL; + slen=0; + data = MALLOC(1); + /* read stdout/stderr from the fence agent */ + do { + data[slen]=EOS; + read_len = read(fd2[0], buff, BUFF_LEN); + if (read_len > 0) { + data=REALLOC(data, slen+read_len+1); + if (data == NULL) { + goto err; + } + memcpy(data+slen, buff, read_len); + slen += read_len; + data[slen] = EOS; + } else if (read_len < 0) { + if (errno == EAGAIN) + continue; + LOG(PIL_CRIT, "%s: read from pipe: %m", __FUNCTION__); + goto err; + } + } while (read_len); + + if (!data) { + LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__); + goto err; + } + close(fd2[0]); + waitpid(pid, &status, 0); + if (!WIFEXITED(status)) { + LOG(PIL_CRIT, "%s: fence agent failed: %m", __FUNCTION__); + goto err; + } else { + rc = WEXITSTATUS(status); + if (rc) { + LOG(PIL_CRIT, "%s: fence agent exit code: %d", + __FUNCTION__, rc); + goto err; + } + } + } else { /* child */ + close(fd1[1]); + close(fd2[0]); + close(STDIN_FILENO); + if (dup(fd1[0]) < 0) + goto err; + close(fd1[0]); + close(STDOUT_FILENO); + if (dup(fd2[1]) < 0) + goto err; + close(STDERR_FILENO); + if (dup(fd2[1]) < 0) + goto err; + close(fd2[1]); + rc = sd->cmd_opts ? + execlp(cmd, cmd, NULL) : execlp(cmd, cmd, "-o", op, NULL); + if (rc < 0) { + LOG(PIL_CRIT, "%s: Calling '%s' failed: %m", + __FUNCTION__, cmd); + } + goto err; + } + + if (Debug && data) { + LOG(PIL_DEBUG, "%s: '%s' output: %s", __FUNCTION__, cmd, data); + } + + if (output) { + *output = data; + } else { + FREE(data); + } + + return 0; + +err: + if (data) { + FREE(data); + } + if (output) { + *output = NULL; + } + + return(-1); + +} diff --git a/lib/plugins/stonith/ribcl.py.in b/lib/plugins/stonith/ribcl.py.in new file mode 100644 index 0000000..14e070c --- /dev/null +++ b/lib/plugins/stonith/ribcl.py.in @@ -0,0 +1,101 @@ +#!@PYTHON@ + + +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import sys +import socket +from httplib import * +from time import sleep + + +argv = sys.argv + + +try: + host = argv[1].split('.')[0]+'-rm' + cmd = argv[2] +except IndexError: + print "Not enough arguments" + sys.exit(1) + + +login = [ '', + '' ] + + +logout = [ '', '' ] + + +status = [ '', '', + '' ] + + +reset = [ '', '', '' ] + + +off = [ '', '', + '' ] + + +on = [ '', '', + '' ] + + +todo = { 'reset':reset, 'on':on, 'off':off, 'status':status } + + +acmds=[] +try: + if cmd == 'reset' and host.startswith('gfxcl'): + acmds.append(login + todo['off'] + logout) + acmds.append(login + todo['on'] + logout) + else: + acmds.append(login + todo[cmd] + logout) +except KeyError: + print "Invalid command: "+ cmd + sys.exit(1) + + +try: + for cmds in acmds: + + + c=HTTPSConnection(host) + c.send('\r\n') + c.sock.recv(1024) + + + for line in cmds: + c.send(line+'\r\n') + c.sock.recv(1024) + + + c.close() + sleep(1) + + +except socket.gaierror, msg: + print msg + sys.exit(1) +except socket.sslerror, msg: + print msg + sys.exit(1) +except socket.error, msg: + print msg + sys.exit(1) + diff --git a/lib/plugins/stonith/riloe.c b/lib/plugins/stonith/riloe.c new file mode 100644 index 0000000..a4a8312 --- /dev/null +++ b/lib/plugins/stonith/riloe.c @@ -0,0 +1,338 @@ +/* + * Stonith module for RILOE Stonith device + * + * Copyright (c) 2004 Alain St-Denis + * + * Mangled by Zhaokai , IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define DEVICE "Compaq RILOE" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN riloe +#define PIL_PLUGIN_S "riloe" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +static StonithPlugin * riloe_new(const char *); +static void riloe_destroy(StonithPlugin *); +static int riloe_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * riloe_get_confignames(StonithPlugin * ); +static const char * riloe_getinfo(StonithPlugin * s, int InfoType); +static int riloe_status(StonithPlugin * ); +static int riloe_reset_req(StonithPlugin * s, int request, const char * host); +static char ** riloe_hostlist(StonithPlugin *); + +static struct stonith_ops riloeOps ={ + riloe_new, /* Create new STONITH object */ + riloe_destroy, /* Destroy STONITH object */ + riloe_getinfo, /* Return STONITH info string */ + riloe_get_confignames, /* Return STONITH info string */ + riloe_set_config, /* Get configuration from NVpairs */ + riloe_status, /* Return STONITH device status */ + riloe_reset_req, /* Request a reset */ + riloe_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &riloeOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define RILOE_COMMAND STONITH_MODULES "/ribcl.py" + +/* + * Riloe STONITH device. We are very agreeable, but don't do much :-) + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "RiloeDevice-Stonith"; +static const char * NOTriloeID = "Riloe device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *riloeXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +riloe_status(StonithPlugin *s) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + + +/* + * Return the list of hosts configured for this RILOE device + */ + +static char ** +riloe_hostlist(StonithPlugin *s) +{ + struct pluginDevice* nd; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + nd = (struct pluginDevice*) s; + if (nd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in %s", __FUNCTION__); + return(NULL); + } + + return OurImports->CopyHostList((const char * const*)nd->hostlist); +} + +/* + * Parse the config information, and stash it away... + */ + +static int +RILOE_parse_config_info(struct pluginDevice* nd, const char * info) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (nd->hostcount >= 0) { + return(S_OOPS); + } + + nd->hostlist = OurImports->StringToHostList(info); + if (nd->hostlist == NULL) { + LOG(PIL_CRIT,"StringToHostList() failed"); + return S_OOPS; + } + for (nd->hostcount = 0; nd->hostlist[nd->hostcount]; nd->hostcount++) { + strdown(nd->hostlist[nd->hostcount]); + } + return(S_OK); +} + + +/* + * Pretend to reset the given host on this Stonith device. + * (we don't even error check the "request" type) + */ +static int +riloe_reset_req(StonithPlugin * s, int request, const char * host) +{ + char cmd[4096]; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + snprintf(cmd, sizeof(cmd), "%s %s reset", RILOE_COMMAND, host); + + if (Debug) { + LOG(PIL_DEBUG, "command %s will be executed", cmd); + } + + if (system(cmd) == 0) { + return S_OK; + } else { + LOG(PIL_CRIT, "command %s failed", cmd); + return(S_RESETFAIL); + } +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +riloe_set_config(StonithPlugin* s, StonithNVpair *list) +{ + StonithNamesToGet namestocopy [] = + { {ST_HOSTLIST, NULL} + , {NULL, NULL} + }; + struct pluginDevice* nd; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + nd = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + + rc = RILOE_parse_config_info(nd , namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return the Stonith plugin configuration parameter + */ +static const char* const * +riloe_get_confignames(StonithPlugin* p) +{ + static const char * RiloeParams[] = {ST_HOSTLIST, NULL }; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + return RiloeParams; +} + +/* + * Return STONITH info string + */ + +static const char * +riloe_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nd; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = nd->idinfo; + break; + case ST_DEVICEDESCR: + ret = "Compaq RILOE STONITH device\n" + "Very early version!"; + break; + case ST_DEVICEURL: + ret = "http://www.hp.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = riloeXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * RILOE Stonith destructor... + */ +static void +riloe_destroy(StonithPlugin *s) +{ + struct pluginDevice* nd; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + nd = (struct pluginDevice *)s; + + nd->pluginid = NOTriloeID; + if (nd->hostlist) { + stonith_free_hostlist(nd->hostlist); + nd->hostlist = NULL; + } + nd->hostcount = -1; + FREE(nd); +} + +/* Create a new Riloe Stonith device. Too bad this function can't be static */ +static StonithPlugin * +riloe_new(const char *subplugin) +{ + struct pluginDevice* nd = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (nd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nd, 0, sizeof(*nd)); + nd->pluginid = pluginid; + nd->hostlist = NULL; + nd->hostcount = -1; + nd->idinfo = DEVICE; + nd->sp.s_ops = &riloeOps; + + return &(nd->sp); +} diff --git a/lib/plugins/stonith/rps10.c b/lib/plugins/stonith/rps10.c new file mode 100644 index 0000000..08d9873 --- /dev/null +++ b/lib/plugins/stonith/rps10.c @@ -0,0 +1,1070 @@ +/* + * Stonith module for WTI Remote Power Controllers (RPS-10M device) + * + * Original code from baytech.c by + * Copyright (c) 2000 Alan Robertson + * + * Modifications for WTI RPS10 + * Copyright (c) 2000 Computer Generation Incorporated + * Eric Z. Ayers + * + * Mangled by Zhaokai , IBM, 2005 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#define DEVICE "WTI RPS10 Power Switch" +#include "stonith_plugin_common.h" + +#include +#define PIL_PLUGIN rps10 +#define PIL_PLUGIN_S "rps10" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define ST_RPS10 "serial_to_targets" +#define MAX_PRSID 256 +#include + +static StonithPlugin * rps10_new(const char *); +static void rps10_destroy(StonithPlugin *); +static int rps10_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * rps10_get_confignames(StonithPlugin *); +static const char * rps10_getinfo(StonithPlugin * s, int InfoType); +static int rps10_status(StonithPlugin * ); +static int rps10_reset_req(StonithPlugin * s, int request, const char * host); +static char ** rps10_hostlist(StonithPlugin *); + +static struct stonith_ops rps10Ops ={ + rps10_new, /* Create new STONITH object */ + rps10_destroy, /* Destroy STONITH object */ + rps10_getinfo, /* Return STONITH info string */ + rps10_get_confignames, /* Return STONITH info string */ + rps10_set_config, /* Get configuration from NVpairs */ + rps10_status, /* Return STONITH device status */ + rps10_reset_req, /* Request a reset */ + rps10_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_signal.h" +#define DOESNT_USE_STONITHKILLCOMM +#define DOESNT_USE_STONITHSCANLINE +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &rps10Ops + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * This was written for a Western Telematic Inc. (WTI) + * Remote Power Switch - RPS-10M. + * + * It has a DB9 serial port, a Rotary Address Switch, + * and a pair of RJ-11 jacks for linking multiple switches + * together. The 'M' unit is a master unit which can control + * up to 9 additional slave units. (the master unit also has an + * A/C outlet, so you can control up to 10 devices) + * + * There are a set of dip switches. The default shipping configuration + * is with all dip switches down. I highly recommend that you flip + * switch #3 up, so that when the device is plugged in, the power + * to the unit comes on. + * + * The serial interface is fixed at 9600 BPS (well, you *CAN* + * select 2400 BPS with a dip switch, but why?) 8-N-1 + * + * The ASCII command string is: + * + * ^B^X^X^B^X^Xac^M + * + * ^B^X^X^B^X^X "fixed password" prefix (CTRL-B CTRL-X ... ) + * ^M the carriage return character + * + * a = 0-9 Indicates the address of the module to receive the command + * a = * Sends the command to all modules + * + * c = 0 Switch the AC outlet OFF + * Returns: + * Plug 0 Off + * Complete + * + * c = 1 Switch the AC outlet ON + * Returns: + * Plug 0 On + * Complete + * + * c = T Toggle AC OFF (delay) then back ON + * Returns: + * Plug 0 Off + * Plug 0 On + * Complete + * + * c = ? Read and display status of the selected module + * Returns: + * Plug 0 On # or Plug 0 Off + * Complete + * + * e.g. ^B^X^X^B^X^X0T^M toggles the power on plug 0 OFF and then ON + * + * 21 September 2000 + * Eric Z. Ayers + * Computer Generation, Inc. + */ + +struct cntrlr_str { + char outlet_id; /* value 0-9, '*' */ + char * node; /* name of the node attached to this outlet */ +}; + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + + int fd; /* FD open to the serial port */ + + char * device; /* Serial device name to use to communicate + to this RPS10 + */ + +#define WTI_NUM_CONTROLLERS 10 + struct cntrlr_str + controllers[WTI_NUM_CONTROLLERS]; + /* one master switch can address 10 controllers */ + + /* Number of actually configured units */ + int unit_count; + +}; + +/* This string is used to identify this type of object in the config file */ +static const char * pluginid = "WTI_RPS10"; +static const char * NOTwtiid = "OBJECT DESTROYED: (WTI RPS-10)"; + +#include "stonith_config_xml.h" + +#define XML_RPS10_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Value in the format \"serial_device remotenode outlet [remotenode outlet]...\"" \ + XML_PARM_SHORTDESC_END + +#define XML_RPS10_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The RPS-10 STONITH device configuration information in the format \"serial_device remotenode outlet [remotenode outlet]...\"" \ + XML_PARM_LONGDESC_END + +#define XML_RPS10_PARM \ + XML_PARAMETER_BEGIN(ST_RPS10, "string", "1", "1") \ + XML_RPS10_SHORTDESC \ + XML_RPS10_LONGDESC \ + XML_PARAMETER_END + +static const char *rps10XML = + XML_PARAMETERS_BEGIN + XML_RPS10_PARM + XML_PARAMETERS_END; + +/* WTIpassword - The fixed string ^B^X^X^B^X^X */ +static const char WTIpassword[7] = {2,24,24,2,24,24,0}; + +/* + * Different expect strings that we get from the WTI_RPS10 + * Remote Power Controllers... + */ + +static struct Etoken WTItokReady[] = { {"RPS-10 Ready", 0, 0}, {NULL,0,0}}; +static struct Etoken WTItokComplete[] = { {"Complete", 0, 0} ,{NULL,0,0}}; +static struct Etoken WTItokPlug[] = { {"Plug", 0, 0}, {NULL,0,0}}; +static struct Etoken WTItokOutlet[] = { {"0", 0, 0}, + {"1", 0, 0}, + {"2", 0, 0}, + {"3", 0, 0}, + {"4", 0, 0}, + {"5", 0, 0}, + {"6", 0, 0}, + {"7", 0, 0}, + {"8", 0, 0}, + {"9", 0, 0}, + {NULL,0,0}}; + +static struct Etoken WTItokOff[] = { {"Off", 0, 0}, {NULL,0,0}}; + +/* + * Tokens currently not used because they don't show up on all RPS10 units: + * + */ +static struct Etoken WTItokOn[] = { {"On", 0, 0}, {NULL,0,0}}; + +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken WTItokCRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int RPSConnect(struct pluginDevice * ctx); +static int RPSDisconnect(struct pluginDevice * ctx); + +static int RPSReset(struct pluginDevice*, char unit_id, const char * rebootid); +#if defined(ST_POWERON) +static int RPSOn(struct pluginDevice*, char unit_id, const char * rebootid); +#endif +#if defined(ST_POWEROFF) +static int RPSOff(struct pluginDevice*, char unit_id, const char * rebootid); +#endif +static signed char RPSNametoOutlet ( struct pluginDevice * ctx, const char * host ); + +static int RPS_parse_config_info(struct pluginDevice* ctx, const char * info); + +#define SENDCMD(outlet, cmd, timeout) { \ + int ret_val = RPSSendCommand(ctx, outlet, cmd, timeout);\ + if (ret_val != S_OK) { \ + return ret_val; \ + } \ + } + +/* + * RPSSendCommand - send a command to the specified outlet + */ +static int +RPSSendCommand (struct pluginDevice *ctx, char outlet, char command, int timeout) +{ + char writebuf[10]; /* all commands are 9 chars long! */ + int return_val; /* system call result */ + fd_set rfds, wfds, xfds; + struct timeval tv; /* */ + + /* list of FDs for select() */ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&xfds); + + snprintf (writebuf, sizeof(writebuf), "%s%c%c\r", + WTIpassword, outlet, command); + + if (Debug) { + LOG(PIL_DEBUG, "Sending %s\n", writebuf); + } + + /* Make sure the serial port won't block on us. use select() */ + FD_SET(ctx->fd, &wfds); + FD_SET(ctx->fd, &xfds); + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + return_val = select(ctx->fd+1, NULL, &wfds,&xfds, &tv); + if (return_val == 0) { + /* timeout waiting on serial port */ + LOG(PIL_CRIT, "%s: Timeout writing to %s", + pluginid, ctx->device); + return S_TIMEOUT; + } else if ((return_val == -1) || FD_ISSET(ctx->fd, &xfds)) { + /* an error occured */ + LOG(PIL_CRIT, "%s: Error before writing to %s: %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* send the command */ + if (write(ctx->fd, writebuf, strlen(writebuf)) != + (int)strlen(writebuf)) { + LOG(PIL_CRIT, "%s: Error writing to %s : %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* suceeded! */ + return S_OK; + +} /* end RPSSendCommand() */ + +/* + * RPSReset - Reset (power-cycle) the given outlet id + */ +static int +RPSReset(struct pluginDevice* ctx, char unit_id, const char * rebootid) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "toggle power" command */ + SENDCMD(unit_id, 'T', 10); + + /* Expect "Plug 0 Off" */ + /* Note: If asked to control "*", the RPS10 will report all units it + * separately; however, we don't know how many, so we can only wait + * for the first unit to report something and then wait until the + * "Complete" */ + EXPECT(ctx->fd, WTItokPlug, 5); + if (Debug) { + LOG(PIL_DEBUG, "Got Plug\n"); + } + EXPECT(ctx->fd, WTItokOutlet, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got Outlet #\n"); + } + EXPECT(ctx->fd, WTItokOff, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got Off\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being rebooted: %s", rebootid); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 14); + if (Debug) { + LOG(PIL_DEBUG, "Got Complete\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL\n"); + } + + return(S_OK); + +} /* end RPSReset() */ + + +#if defined(ST_POWERON) +/* + * RPSOn - Turn OFF the given outlet id + */ +static int +RPSOn(struct pluginDevice* ctx, char unit_id, const char * host) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "On" command */ + SENDCMD(unit_id, '1', 10); + + /* Expect "Plug 0 On" */ + EXPECT(ctx->fd, WTItokPlug, 5); + EXPECT(ctx->fd, WTItokOutlet, 2); + EXPECT(ctx->fd, WTItokOn, 2); + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being turned on: %s", host); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 5); + EXPECT(ctx->fd, WTItokCRNL, 2); + + return(S_OK); + +} /* end RPSOn() */ +#endif + + +#if defined(ST_POWEROFF) +/* + * RPSOff - Turn Off the given outlet id + */ +static int +RPSOff(struct pluginDevice* ctx, char unit_id, const char * host) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd < 0) { + LOG(PIL_CRIT, "%s: device %s is not open!", pluginid, + ctx->device); + return S_OOPS; + } + + /* send the "Off" command */ + SENDCMD(unit_id, '0', 10); + + /* Expect "Plug 0 Off" */ + EXPECT(ctx->fd, WTItokPlug, 5); + EXPECT(ctx->fd, WTItokOutlet, 2); + EXPECT(ctx->fd, WTItokOff, 2); + EXPECT(ctx->fd, WTItokCRNL, 2); + LOG(PIL_INFO, "Host is being turned on: %s", host); + + /* Expect "Complete" */ + EXPECT(ctx->fd, WTItokComplete, 5); + EXPECT(ctx->fd, WTItokCRNL, 2); + + return(S_OK); + +} /* end RPSOff() */ +#endif + + +/* + * rps10_status - API entry point to probe the status of the stonith device + * (basically just "is it reachable and functional?", not the + * status of the individual outlets) + * + * Returns: + * S_OOPS - some error occured + * S_OK - if the stonith device is reachable and online. + */ +static int +rps10_status(StonithPlugin *s) +{ + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + if (RPSConnect(ctx) != S_OK) { + return(S_OOPS); + } + + /* The "connect" really does enough work to see if the + controller is alive... It verifies that it is returning + RPS-10 Ready + */ + + return(RPSDisconnect(ctx)); +} + +/* + * rps10_hostlist - API entry point to return the list of hosts + * for the devices on this WTI_RPS10 unit + * + * This type of device is configured from the config file, + * so we don't actually have to connect to figure this + * out, just peruse the 'ctx' structure. + * Returns: + * NULL on error + * a malloced array, terminated with a NULL, + * of null-terminated malloc'ed strings. + */ +static char ** +rps10_hostlist(StonithPlugin *s) +{ + char ** ret = NULL; /* list to return */ + int i; + int j; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + ctx = (struct pluginDevice*) s; + + if (ctx->unit_count >= 1) { + ret = (char **)MALLOC((ctx->unit_count+1)*sizeof(char*)); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + ret[ctx->unit_count]=NULL; /* null terminate the array */ + for (i=0; i < ctx->unit_count; i++) { + ret[i] = STRDUP(ctx->controllers[i].node); + if (ret[i] == NULL) { + for(j=0; j for this module is: + * [ ] ... + * + * e.g. A machine named 'nodea' can kill a machine named 'nodeb' through + * a device attached to serial port /dev/ttyS0. + * A machine named 'nodeb' can kill machines 'nodea' and 'nodec' + * through a device attached to serial port /dev/ttyS1 (outlets 0 + * and 1 respectively) + * + * + * + * stonith nodea rps10 /dev/ttyS0 nodeb 0 + * stonith nodeb rps10 /dev/ttyS0 nodea 0 nodec 1 + * + * Another possible configuration is for 2 stonith devices + * accessible through 2 different serial ports on nodeb: + * + * stonith nodeb rps10 /dev/ttyS0 nodea 0 + * stonith nodeb rps10 /dev/ttyS1 nodec 0 + */ + +/* + * OOPS! + * + * Most of the large block of comments above is incorrect as far as this + * module is concerned. It is somewhat applicable to the heartbeat code, + * but not to this Stonith module. + * + * The format of parameter string for this module is: + * [ ] ... + */ + +static int +RPS_parse_config_info(struct pluginDevice* ctx, const char * info) +{ + char *copy; + char *token; + char *outlet, *node; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* strtok() is nice to use to parse a string with + (other than it isn't threadsafe), but it is destructive, so + we're going to alloc our own private little copy for the + duration of this function. + */ + + copy = STRDUP(info); + if (!copy) { + LOG(PIL_CRIT, "out of memory"); + return S_OOPS; + } + + /* Grab the serial device */ + token = strtok (copy, " \t"); + + if (!token) { + LOG(PIL_CRIT, "%s: Can't find serial device on config line '%s'", + pluginid, info); + goto token_error; + } + + ctx->device = STRDUP(token); + if (!ctx->device) { + LOG(PIL_CRIT, "out of memory"); + goto token_error; + } + + /* Loop through the rest of the command line which should consist of */ + /* pairs */ + while ((node = strtok (NULL, " \t")) + && (outlet = strtok (NULL, " \t\n"))) { + char outlet_id; + + /* validate the outlet token */ + if ((sscanf (outlet, "%c", &outlet_id) != 1) + || !( ((outlet_id >= '0') && (outlet_id <= '9')) + || (outlet_id == '*') || (outlet_id == 'A') ) + ) { + LOG(PIL_CRIT + , "%s: the outlet_id %s must be between" + " 0 and 9 or '*' / 'A'", + pluginid, outlet); + goto token_error; + } + + if (outlet_id == 'A') { + /* Remap 'A' to '*'; in some configurations, + * a '*' can't be configured because it breaks + * scripts -- lmb */ + outlet_id = '*'; + } + + if (ctx->unit_count >= WTI_NUM_CONTROLLERS) { + LOG(PIL_CRIT, + "%s: Tried to configure too many controllers", + pluginid); + goto token_error; + } + + ctx->controllers[ctx->unit_count].node = STRDUP(node); + strdown(ctx->controllers[ctx->unit_count].node); + ctx->controllers[ctx->unit_count].outlet_id = outlet_id; + ctx->unit_count++; + + } + + /* free our private copy of the string we've been destructively + * parsing with strtok() + */ + FREE(copy); + return ((ctx->unit_count > 0) ? S_OK : S_BADCONFIG); + +token_error: + FREE(copy); + if (ctx->device) { + FREE(ctx->device); + ctx->device = NULL; + } + return(S_BADCONFIG); +} + + +/* + * dtrtoggle - toggle DTR on the serial port + * + * snarfed from minicom, sysdep1.c, a well known POSIX trick. + * + */ +static void dtrtoggle(int fd) { + struct termios tty, old; + int sec = 2; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + tcgetattr(fd, &tty); + tcgetattr(fd, &old); + cfsetospeed(&tty, B0); + cfsetispeed(&tty, B0); + tcsetattr(fd, TCSANOW, &tty); + if (sec>0) { + sleep(sec); + tcsetattr(fd, TCSANOW, &old); + } + + if (Debug) { + LOG(PIL_DEBUG, "dtrtoggle Complete (%s)\n", pluginid); + } +} + +/* + * RPSConnect - + * + * Connect to the given WTI_RPS10 device. + * Side Effects + * DTR on the serial port is toggled + * ctx->fd now contains a valid file descriptor to the serial port + * ??? LOCK THE SERIAL PORT ??? + * + * Returns + * S_OK on success + * S_OOPS on error + * S_TIMEOUT if the device did not respond + * + */ +static int +RPSConnect(struct pluginDevice * ctx) +{ + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Open the serial port if it isn't already open */ + if (ctx->fd < 0) { + struct termios tio; + + if (OurImports->TtyLock(ctx->device) < 0) { + LOG(PIL_CRIT, "%s: TtyLock failed.", pluginid); + return S_OOPS; + } + + ctx->fd = open (ctx->device, O_RDWR); + if (ctx->fd <0) { + LOG(PIL_CRIT, "%s: Can't open %s : %s", + pluginid, ctx->device, strerror(errno)); + return S_OOPS; + } + + /* set the baudrate to 9600 8 - N - 1 */ + memset (&tio, 0, sizeof(tio)); + + /* ??? ALAN - the -tradtitional flag on gcc causes the + CRTSCTS constant to generate a warning, and warnings + are treated as errors, so I can't set this flag! - EZA ??? + + Hmmm. now that I look at the documentation, RTS + is just wired high on this device! we don't need it. + */ + /* tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD | CRTSCTS ;*/ + tio.c_cflag = B9600 | CS8 | CLOCAL | CREAD ; + tio.c_lflag = ICANON; + + if (tcsetattr (ctx->fd, TCSANOW, &tio) < 0) { + LOG(PIL_CRIT, "%s: Can't set attributes %s : %s", + pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + /* flush all data to and fro the serial port before we start */ + if (tcflush (ctx->fd, TCIOFLUSH) < 0) { + LOG(PIL_CRIT, "%s: Can't flush %s : %s", + pluginid, ctx->device, strerror(errno)); + close (ctx->fd); + OurImports->TtyUnlock(ctx->device); + ctx->fd=-1; + return S_OOPS; + } + + } + + /* Toggle DTR - this 'resets' the controller serial port interface + In minicom, try CTRL-A H to hangup and you can see this behavior. + */ + dtrtoggle(ctx->fd); + + /* Wait for the switch to respond with "RPS-10 Ready". + Emperically, this usually takes 5-10 seconds... + ... If this fails, this may be a hint that you got + a broken serial cable, which doesn't connect hardware + flow control. + */ + if (Debug) { + LOG(PIL_DEBUG, "Waiting for READY\n"); + } + EXPECT(ctx->fd, WTItokReady, 12); + if (Debug) { + LOG(PIL_DEBUG, "Got READY\n"); + } + EXPECT(ctx->fd, WTItokCRNL, 2); + if (Debug) { + LOG(PIL_DEBUG, "Got NL\n"); + } + + return(S_OK); +} + +static int +RPSDisconnect(struct pluginDevice * ctx) +{ + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx->fd >= 0) { + /* Flush the serial port, we don't care what happens to the + * characters and failing to do this can cause close to hang. + */ + tcflush(ctx->fd, TCIOFLUSH); + close (ctx->fd); + if (ctx->device != NULL) { + OurImports->TtyUnlock(ctx->device); + } + } + ctx->fd = -1; + + return S_OK; +} + +/* + * RPSNametoOutlet - Map a hostname to an outlet on this stonith device. + * + * Returns: + * 0-9, * on success ( the outlet id on the RPS10 ) + * -1 on failure (host not found in the config file) + * + */ +static signed char +RPSNametoOutlet ( struct pluginDevice * ctx, const char * host ) +{ + int i=0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* scan the controllers[] array to see if this host is there */ + for (i=0;iunit_count;i++) { + /* return the outlet id */ + if ( ctx->controllers[i].node + && !strcasecmp(host, ctx->controllers[i].node)) { + /* found it! */ + break; + } + } + + if (i == ctx->unit_count) { + return -1; + } else { + return ctx->controllers[i].outlet_id; + } +} + + +/* + * rps10_reset - API call to Reset (reboot) the given host on + * this Stonith device. This involves toggling the power off + * and then on again, OR just calling the builtin reset command + * on the stonith device. + */ +static int +rps10_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = S_OK; + int lorc = S_OK; + signed char outlet_id = -1; + struct pluginDevice* ctx; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + ctx = (struct pluginDevice*) s; + + if ((rc = RPSConnect(ctx)) != S_OK) { + return(rc); + } + + outlet_id = RPSNametoOutlet(ctx, host); + + if (outlet_id < 0) { + LOG(PIL_WARN, "%s: %s doesn't control host [%s]" + , pluginid, ctx->device, host ); + RPSDisconnect(ctx); + return(S_BADHOST); + } + + switch(request) { + +#if defined(ST_POWERON) + case ST_POWERON: + rc = RPSOn(ctx, outlet_id, host); + break; +#endif +#if defined(ST_POWEROFF) + case ST_POWEROFF: + rc = RPSOff(ctx, outlet_id, host); + break; +#endif + case ST_GENERIC_RESET: + rc = RPSReset(ctx, outlet_id, host); + break; + default: + rc = S_INVAL; + break; + } + + lorc = RPSDisconnect(ctx); + + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +rps10_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* ctx; + StonithNamesToGet namestocopy [] = + { {ST_RPS10, NULL} + , {NULL, NULL} + }; + int rc=0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + if (s->isconfigured) { + /* The module is already configured. */ + return(S_OOPS); + } + + ctx = (struct pluginDevice*) s; + + if((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK){ + LOG(PIL_DEBUG , "get all calues failed"); + return rc; + } + + rc = RPS_parse_config_info(ctx, namestocopy[0].s_value); + FREE(namestocopy[0].s_value); + return rc; +} + +/* + * Return the Stonith plugin configuration parameter + * + */ +static const char * const * +rps10_get_confignames(StonithPlugin* p) +{ + static const char * Rps10Params[] = {ST_RPS10 ,NULL }; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + return Rps10Params; +} + +/* + * rps10_getinfo - API entry point to retrieve something from the handle + */ +static const char * +rps10_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* ctx; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + ctx = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ctx->idinfo; + break; + case ST_DEVICENAME: + ret = ctx->device; + break; + case ST_DEVICEDESCR: + ret = "Western Telematic Inc. (WTI) " + "Remote Power Switch - RPS-10M.\n"; + break; + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = rps10XML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * rps10_destroy - API entry point to destroy a WTI_RPS10 Stonith object. + */ +static void +rps10_destroy(StonithPlugin *s) +{ + struct pluginDevice* ctx; + int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + ctx = (struct pluginDevice *)s; + + ctx->pluginid = NOTwtiid; + + /* close the fd if open and set ctx->fd to invalid */ + RPSDisconnect(ctx); + + if (ctx->device != NULL) { + FREE(ctx->device); + ctx->device = NULL; + } + if (ctx->unit_count > 0) { + for (i = 0; i < ctx->unit_count; i++) { + if (ctx->controllers[i].node != NULL) { + FREE(ctx->controllers[i].node); + ctx->controllers[i].node = NULL; + } + } + } + FREE(ctx); +} + +/* + * rps10_new - API entry point called to create a new WTI_RPS10 Stonith device + * object. + */ +static StonithPlugin * +rps10_new(const char *subplugin) +{ + struct pluginDevice* ctx = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if (ctx == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(ctx, 0, sizeof(*ctx)); + ctx->pluginid = pluginid; + ctx->fd = -1; + ctx->unit_count = 0; + ctx->device = NULL; + ctx->idinfo = DEVICE; + ctx->sp.s_ops = &rps10Ops; + + return &(ctx->sp); +} diff --git a/lib/plugins/stonith/ssh.c b/lib/plugins/stonith/ssh.c new file mode 100644 index 0000000..e90c199 --- /dev/null +++ b/lib/plugins/stonith/ssh.c @@ -0,0 +1,351 @@ +/* + * Stonith module for SSH Stonith device + * + * Copyright (c) 2001 SuSE Linux AG + * + * Authors: Joachim Gleissner , Lars Marowsky-Brée + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include + +#define DEVICE "SSH STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN ssh +#define PIL_PLUGIN_S "ssh" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +static StonithPlugin * ssh_new(const char *); +static void ssh_destroy(StonithPlugin *); +static const char * const * ssh_get_confignames(StonithPlugin *); +static int ssh_set_config(StonithPlugin *, StonithNVpair*); +static const char * ssh_get_info(StonithPlugin * s, int InfoType); +static int ssh_status(StonithPlugin * ); +static int ssh_reset_req(StonithPlugin * s, int request +, const char * host); +static char ** ssh_hostlist(StonithPlugin *); + +static struct stonith_ops sshOps ={ + ssh_new, /* Create new STONITH object */ + ssh_destroy, /* Destroy STONITH object */ + ssh_get_info, /* Return STONITH info string */ + ssh_get_confignames, /* Return configuration parameters */ + ssh_set_config, /* set configuration */ + ssh_status, /* Return STONITH device status */ + ssh_reset_req, /* Request a reset */ + ssh_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &sshOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* uncomment this if you have an ssh that can do what it claims +#define SSH_COMMAND "ssh -q -x -o PasswordAuthentication=no StrictHostKeyChecking=no" +*/ +/* use this if you have the (broken) OpenSSH 2.1.1 */ +/* sunjd@cn.ibm.com added the option -f to temporily work around the block issue + * in which the child process always stay in 'system' call. Please FIX this. + * Additonally, this issue seems related to both of 2.6 kernel and stonithd. + */ +#define SSH_COMMAND "ssh -q -x -n -l root" + +/* We need to do a real hard reboot without syncing anything to simulate a + * power cut. + * We have to do it in the background, otherwise this command will not + * return. + */ +#define REBOOT_COMMAND "nohup sh -c '(sleep 2; nohup " REBOOT " " REBOOT_OPTIONS ") /dev/null 2>&1' &" +#undef REBOOT_COMMAND +#define REBOOT_COMMAND "echo 'sleep 2; " REBOOT " " REBOOT_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +#define POWEROFF_COMMAND "echo 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" + +#define MAX_PING_ATTEMPTS 15 + +/* + * SSH STONITH device + * + * I used the null device as template, so I guess there is missing + * some functionality. + * + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char ** hostlist; + int hostcount; +}; + +static const char * pluginid = "SSHDevice-Stonith"; +static const char * NOTpluginid = "SSH device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *sshXML = + XML_PARAMETERS_BEGIN + XML_HOSTLIST_PARM + XML_PARAMETERS_END; + +static int +ssh_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s, S_OOPS); + + return system(NULL) ? S_OK : S_OOPS; +} + + +/* + * Return the list of hosts configured for this SSH device + */ + +static char ** +ssh_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd = (struct pluginDevice*)s; + + ERRIFWRONGDEV(s, NULL); + + if (sd->hostcount < 0) { + LOG(PIL_CRIT + , "unconfigured stonith object in %s", __FUNCTION__); + return(NULL); + } + + return OurImports->CopyHostList((const char * const *)sd->hostlist); +} + + +/* + * Reset the given host on this Stonith device. + */ +static int +ssh_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + char cmd[4096]; + int i, status = -1; + + ERRIFWRONGDEV(s, S_OOPS); + + if (request == ST_POWERON) { + LOG(PIL_CRIT, "%s not capable of power-on operation", DEVICE); + return S_INVAL; + } else if (request != ST_POWEROFF && request != ST_GENERIC_RESET) { + return S_INVAL; + } + + for (i = 0; i < sd->hostcount; i++) { + if (strcasecmp(host, sd->hostlist[i]) == 0) { + break; + } + } + + if (i >= sd->hostcount) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , sd->idinfo, host); + return(S_BADHOST); + } + + LOG(PIL_INFO, "Initiating ssh-%s on host: %s" + , request == ST_POWEROFF ? "poweroff" : "reset", host); + + snprintf(cmd, sizeof(cmd)-1, "%s \"%s\" \"%s\"", SSH_COMMAND + , host + , request == ST_POWEROFF ? POWEROFF_COMMAND : REBOOT_COMMAND); + + status = system(cmd); + if (WIFEXITED(status) && 0 == WEXITSTATUS(status)) { + if (Debug) { + LOG(PIL_DEBUG, "checking whether %s stonith'd", host); + } + + snprintf(cmd, sizeof(cmd)-1 + , "ping -w1 -c1 %s >/dev/null 2>&1", host); + + for (i = 0; i < MAX_PING_ATTEMPTS; i++) { + status = system(cmd); + if (WIFEXITED(status) && 1 == WEXITSTATUS(status)) { + if (Debug) { + LOG(PIL_DEBUG, "unable to ping %s" + " after %d tries, stonith did work" + , host, i); + } + return S_OK; + } + sleep(1); + } + + LOG(PIL_CRIT, "still able to ping %s after %d tries, stonith" + " did not work", host, MAX_PING_ATTEMPTS); + return S_RESETFAIL; + }else{ + LOG(PIL_CRIT, "command %s failed", cmd); + return S_RESETFAIL; + } +} + +static const char * const * +ssh_get_confignames(StonithPlugin* p) +{ + static const char * SshParams[] = {ST_HOSTLIST, NULL }; + return SshParams; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +ssh_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * hlist; + + ERRIFWRONGDEV(s,S_OOPS); + + if ((hlist = OurImports->GetValue(list, ST_HOSTLIST)) == NULL) { + return S_OOPS; + } + sd->hostlist = OurImports->StringToHostList(hlist); + if (sd->hostlist == NULL) { + LOG(PIL_CRIT, "out of memory"); + sd->hostcount = 0; + }else{ + for (sd->hostcount = 0; sd->hostlist[sd->hostcount] + ; sd->hostcount++) { + strdown(sd->hostlist[sd->hostcount]); + } + } + + return sd->hostcount ? S_OK : S_OOPS; +} + + +static const char * +ssh_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + switch (reqtype) { + case ST_DEVICEID: + ret = sd->idinfo; + break; + + + case ST_DEVICENAME: + ret = "ssh STONITH device"; + break; + + + case ST_DEVICEDESCR: /* Description of device type */ + ret = "SSH-based host reset\n" + "Fine for testing, but not suitable for production!"; + break; + + + case ST_DEVICEURL: + ret = "http://openssh.org"; + break; + + + case ST_CONF_XML: /* XML metadata */ + ret = sshXML; + break; + + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * SSH Stonith destructor... + */ +static void +ssh_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + + VOIDERRIFWRONGDEV(s); + + sd->pluginid = NOTpluginid; + if (sd->hostlist) { + stonith_free_hostlist(sd->hostlist); + sd->hostlist = NULL; + } + sd->hostcount = -1; + FREE(sd); +} + +/* Create a new ssh Stonith device */ +static StonithPlugin* +ssh_new(const char *subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->hostlist = NULL; + sd->hostcount = -1; + sd->idinfo = DEVICE; + sd->sp.s_ops = &sshOps; + return &(sd->sp); +} diff --git a/lib/plugins/stonith/stonith_config_xml.h b/lib/plugins/stonith/stonith_config_xml.h new file mode 100644 index 0000000..ff04ae9 --- /dev/null +++ b/lib/plugins/stonith/stonith_config_xml.h @@ -0,0 +1,157 @@ +/* + * stonith_config_xml.h: common macros easing the writing of config + * XML for STONITH plugins. Only a STONITH + * plugin should include this header! + * + * Copyright (C) International Business Machines Corp., 2005 + * Author: Dave Blaschke + * Support: linux-ha@lists.linux-ha.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _STONITH_CONFIG_XML_H +#define _STONITH_CONFIG_XML_H + +/* + * The generic constants for XML + */ + +/* ? */ +#define XML_PARAMETERS_BEGIN "" +#define XML_PARAMETERS_END "" + +/* ? */ +#define XML_PARAMETER_BEGIN(name,type,req,uniq) \ + "" \ + "\n" +#define XML_PARAMETER_END "\n" + +/* ? */ +#define XML_PARM_SHORTDESC_BEGIN(lang) \ + "\n" +#define XML_PARM_SHORTDESC_END "\n" + +/* ? */ +#define XML_PARM_LONGDESC_BEGIN(lang) \ + "\n" +#define XML_PARM_LONGDESC_END "\n" + +/* + * The short and long descriptions for the few standardized parameter names; + * these can be translated by appending different languages to these constants + * (must include XML_PARM_****DESC_BEGIN(), the translated description, and + * XML_PARM_****DESC_END for each language) + */ +#define XML_HOSTLIST_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Hostlist" \ + XML_PARM_SHORTDESC_END + +#define XML_HOSTLIST_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The list of hosts that the STONITH device controls" \ + XML_PARM_LONGDESC_END + +#define XML_IPADDR_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "IP Address" \ + XML_PARM_SHORTDESC_END + +#define XML_IPADDR_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The IP address of the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_LOGIN_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Login" \ + XML_PARM_SHORTDESC_END + +#define XML_LOGIN_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The username used for logging in to the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PASSWD_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "Password" \ + XML_PARM_SHORTDESC_END + +#define XML_PASSWD_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The password used for logging in to the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_COMMUNITY_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "SNMP Community" \ + XML_PARM_SHORTDESC_END + +#define XML_COMMUNITY_LONGDESC "" \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The SNMP community string associated with the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_TTYDEV_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + "TTY Device" \ + XML_PARM_SHORTDESC_END + +#define XML_TTYDEV_LONGDESC "" \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The TTY device used for connecting to the STONITH device" \ + XML_PARM_LONGDESC_END + +/* + * Complete parameter descriptions for the few standardized parameter names + */ +#define XML_HOSTLIST_PARM \ + XML_PARAMETER_BEGIN(ST_HOSTLIST, "string", "1", "0") \ + XML_HOSTLIST_SHORTDESC \ + XML_HOSTLIST_LONGDESC \ + XML_PARAMETER_END + +#define XML_IPADDR_PARM \ + XML_PARAMETER_BEGIN(ST_IPADDR, "string", "1", "0") \ + XML_IPADDR_SHORTDESC \ + XML_IPADDR_LONGDESC \ + XML_PARAMETER_END + +#define XML_LOGIN_PARM \ + XML_PARAMETER_BEGIN(ST_LOGIN, "string", "1", "0") \ + XML_LOGIN_SHORTDESC \ + XML_LOGIN_LONGDESC \ + XML_PARAMETER_END + +#define XML_PASSWD_PARM \ + XML_PARAMETER_BEGIN(ST_PASSWD, "string", "1", "0") \ + XML_PASSWD_SHORTDESC \ + XML_PASSWD_LONGDESC \ + XML_PARAMETER_END + +#define XML_COMMUNITY_PARM \ + XML_PARAMETER_BEGIN(ST_COMMUNITY, "string", "1", "0") \ + XML_COMMUNITY_SHORTDESC \ + XML_COMMUNITY_LONGDESC \ + XML_PARAMETER_END + +#define XML_TTYDEV_PARM \ + XML_PARAMETER_BEGIN(ST_TTYDEV, "string", "1", "0") \ + XML_TTYDEV_SHORTDESC \ + XML_TTYDEV_LONGDESC \ + XML_PARAMETER_END + +#endif diff --git a/lib/plugins/stonith/stonith_expect_helpers.h b/lib/plugins/stonith/stonith_expect_helpers.h new file mode 100644 index 0000000..f9eaa19 --- /dev/null +++ b/lib/plugins/stonith/stonith_expect_helpers.h @@ -0,0 +1,120 @@ +/* + * stonith_expect_helpers.h: Some common expect defines. + * + * Copyright (C) 2004 Lars Marowsky-Bree + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* This is still somewhat ugly. It needs to be included after the PILS + * definitions so that it can access them, but the code reduction seemed + * to justify this. Hopefully it can be made somewhat more elegant + * eventually. */ + +/* + * Many expect/telnet plugins use these defines and functions. + */ + +#define SEND(fd,s) { \ + size_t slen = strlen(s); \ + if (Debug) { \ + LOG(PIL_DEBUG \ + , "Sending [%s] (len %d)" \ + , (s) \ + , (int)slen); \ + } \ + if (write((fd), (s), slen) != slen) { \ + LOG(PIL_CRIT \ + , "%s: write failed" \ + , __FUNCTION__); \ + } \ + } + +#define EXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(errno == ETIMEDOUT \ + ? S_TIMEOUT : S_OOPS); \ + } + +#define NULLEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(NULL); \ + } + +#define SNARF(fd,s, to) { \ + if (StonithScanLine(fd,to,(s),sizeof(s))\ + != S_OK){ \ + return(S_OOPS); \ + } \ + } + +#define NULLSNARF(fd,s, to){ \ + if (StonithScanLine(fd,to,(s),sizeof(s))\ + != S_OK) { \ + return(NULL); \ + } \ + } + +/* Look for any of the given patterns. We don't care which */ +static int +StonithLookFor(int fd, struct Etoken * tlist, int timeout) +{ + int rc; + char savebuf[512]; + + if ((rc = EXPECT_TOK(fd, tlist, timeout, savebuf, sizeof(savebuf) + , Debug)) < 0) { + LOG(PIL_CRIT, "Did not find string %s from " DEVICE "." + , tlist[0].string); + LOG(PIL_CRIT, "Received [%s]", savebuf); + } + return(rc); +} + +#ifndef DOESNT_USE_STONITHSCANLINE +/* Accept either a CR/NL or an NL/CR */ +static struct Etoken CRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + +static int +StonithScanLine(int fd, int timeout, char * buf, int max) +{ + if (EXPECT_TOK(fd, CRNL, timeout, buf, max, Debug) < 0) { + LOG(PIL_CRIT, "Could not read line from" DEVICE "."); + return(S_OOPS); + } + return(S_OK); +} +#endif + +#ifndef DOESNT_USE_STONITHKILLCOMM +static void +Stonithkillcomm(int *rdfd, int *wrfd, int *pid) +{ + if ((rdfd != NULL) && (*rdfd >= 0)) { + close(*rdfd); + *rdfd = -1; + } + if ((wrfd != NULL) && (*wrfd >= 0)) { + close(*wrfd); + *wrfd = -1; + } + if ((pid != NULL) && (*pid > 0)) { + STONITH_KILL(*pid, SIGKILL); + (void)waitpid(*pid, NULL, 0); + *pid = -1; + } +} +#endif diff --git a/lib/plugins/stonith/stonith_plugin_common.h b/lib/plugins/stonith/stonith_plugin_common.h new file mode 100644 index 0000000..dcdd7c8 --- /dev/null +++ b/lib/plugins/stonith/stonith_plugin_common.h @@ -0,0 +1,127 @@ +/* + * stonith_plugin_common.h: common macros easing the writing of STONITH + * plugins. Only a STONITH plugin should + * include this header! + * + * Copyright (C) 2004 Lars Marowsky-Bree + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _STONITH_PLUGIN_COMMON_H +#define _STONITH_PLUGIN_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_TERMIO_H +# include +#endif +#ifdef HAVE_SYS_TERMIOS_H +#include +#else +#ifdef HAVE_TERMIOS_H +#include +#endif +#endif +#include + + +#include +#include + +#define LOG(w...) PILCallLog(PluginImports->log, w) + +#define MALLOC PluginImports->alloc +#define REALLOC PluginImports->mrealloc +#define STRDUP PluginImports->mstrdup +#define FREE PluginImports->mfree +#define EXPECT_TOK OurImports->ExpectToken +#define STARTPROC OurImports->StartProcess + +#ifdef MALLOCT +# undef MALLOCT +#endif +#define ST_MALLOCT(t) ((t *)(MALLOC(sizeof(t)))) + +#define N_(text) (text) +#define _(text) dgettext(ST_TEXTDOMAIN, text) + +#define WHITESPACE " \t\n\r\f" + +#ifndef MIN +/* some macros */ +# define MIN( i, j ) ( i > j ? j : i ) +#endif + +#define REPLSTR(s,v) { \ + if ((s) != NULL) { \ + FREE(s); \ + (s)=NULL; \ + } \ + (s) = STRDUP(v); \ + if ((s) == NULL) { \ + PILCallLog(PluginImports->log, \ + PIL_CRIT, "out of memory"); \ + } \ + } + +#ifndef DEVICE +#define DEVICE "Dummy" +#endif + +#define PIL_PLUGINTYPE STONITH_TYPE +#define PIL_PLUGINTYPE_S STONITH_TYPE_S + +#define ISCORRECTDEV(i) ((i)!= NULL \ + && ((struct pluginDevice *)(i))->pluginid == pluginid) + +#define ERRIFWRONGDEV(s, retval) if (!ISCORRECTDEV(s)) { \ + LOG(PIL_CRIT, "%s: invalid argument", __FUNCTION__); \ + return(retval); \ + } + +#define VOIDERRIFWRONGDEV(s) if (!ISCORRECTDEV(s)) { \ + LOG(PIL_CRIT, "%s: invalid argument", __FUNCTION__); \ + return; \ + } + +#define ISCONFIGED(i) (i->isconfigured) + +#define ERRIFNOTCONFIGED(s,retval) ERRIFWRONGDEV(s,retval); \ + if (!ISCONFIGED(s)) { \ + LOG(PIL_CRIT, "%s: not configured", __FUNCTION__); \ + return(retval); \ + } + +#define VOIDERRIFNOTCONFIGED(s) VOIDERRIFWRONGDEV(s); \ + if (!ISCONFIGED(s)) { \ + LOG(PIL_CRIT, "%s: not configured", __FUNCTION__); \ + return; \ + } + +#endif + diff --git a/lib/plugins/stonith/stonith_signal.h b/lib/plugins/stonith/stonith_signal.h new file mode 100644 index 0000000..99513f5 --- /dev/null +++ b/lib/plugins/stonith/stonith_signal.h @@ -0,0 +1,68 @@ +/* + * stonith_signal.h: signal handling routines to be used by stonith + * plugin libraries + * + * Copyright (C) 2002 Horms + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _STONITH_SIGNAL_H +#define _STONITH_SIGNAL_H + +#include +#include + +int +stonith_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact); + +int +stonith_signal_set_simple_handler(int sig, void (*handler)(int) +, struct sigaction *oldact) +{ + struct sigaction sa; + sigset_t mask; + + (void)stonith_signal_set_simple_handler; + if(sigemptyset(&mask) < 0) { + return(-1); + } + + sa.sa_handler = handler; + sa.sa_mask = mask; + sa.sa_flags = 0; + + if(sigaction(sig, &sa, oldact) < 0) { + return(-1); + } + + return(0); +} + +#define STONITH_SIGNAL(_sig, _handler) \ + stonith_signal_set_simple_handler((_sig), (_handler), NULL) +#ifdef HAVE_SIGIGNORE +#define STONITH_IGNORE_SIG(_sig) \ + sigignore((_sig)) +#else +#define STONITH_IGNORE_SIG(_sig) \ + STONITH_SIGNAL((_sig), SIG_IGN) +#endif +#define STONITH_DEFAULT_SIG(_sig) STONITH_SIGNAL((_sig), SIG_DFL) + +#define STONITH_KILL(_pid, _sig) kill((_pid), (_sig)) + +#endif /* _STONITH_SIGNAL_H */ diff --git a/lib/plugins/stonith/suicide.c b/lib/plugins/stonith/suicide.c new file mode 100644 index 0000000..b9d1db4 --- /dev/null +++ b/lib/plugins/stonith/suicide.c @@ -0,0 +1,274 @@ +/* File: suicide.c + * Description: Stonith module for suicide + * + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#define DEVICE "Suicide STONITH device" +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN suicide +#define PIL_PLUGIN_S "suicide" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +static StonithPlugin * suicide_new(const char *); +static void suicide_destroy(StonithPlugin *); +static const char * const * suicide_get_confignames(StonithPlugin *); +static int suicide_set_config(StonithPlugin *, StonithNVpair*); +static const char * suicide_get_info(StonithPlugin * s, int InfoType); +static int suicide_status(StonithPlugin * ); +static int suicide_reset_req(StonithPlugin * s, int request + , const char * host); +static char ** suicide_hostlist(StonithPlugin *); + +static struct stonith_ops suicideOps ={ + suicide_new, /* Create new STONITH object */ + suicide_destroy, /* Destroy STONITH object */ + suicide_get_info, /* Return STONITH info string */ + suicide_get_confignames, /* Return configuration parameters */ + suicide_set_config, /* Set configuration */ + suicide_status, /* Return STONITH device status */ + suicide_reset_req, /* Request a reset */ + suicide_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &suicideOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +#define REBOOT_COMMAND "nohup sh -c 'sleep 2; " REBOOT " " REBOOT_OPTIONS " /dev/null 2>&1' &" +#define POWEROFF_COMMAND "nohup sh -c 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS " /dev/null 2>&1' &" +/* +#define REBOOT_COMMAND "echo 'sleep 2; " REBOOT " " REBOOT_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +#define POWEROFF_COMMAND "echo 'sleep 2; " POWEROFF_CMD " " POWEROFF_OPTIONS "' | SHELL=/bin/sh at now >/dev/null 2>&1" +*/ + +/* + * Suicide STONITH device + */ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; +}; + +static const char * pluginid = "SuicideDevice-Stonith"; +static const char * NOTpluginid = "Suicide device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *suicideXML = + XML_PARAMETERS_BEGIN + XML_PARAMETERS_END; + +static int +suicide_status(StonithPlugin *s) +{ + ERRIFWRONGDEV(s, S_OOPS); + + return S_OK; +} + +/* + * Return the list of hosts configured for this Suicide device + */ +static char ** +suicide_hostlist(StonithPlugin *s) +{ + char** ret = NULL; + struct utsname name; + + ERRIFWRONGDEV(s, NULL); + + if (uname(&name) == -1) { + LOG(PIL_CRIT, "uname error %d", errno); + return ret; + } + + ret = OurImports->StringToHostList(name.nodename); + if (ret == NULL) { + LOG(PIL_CRIT, "out of memory"); + return ret; + } + strdown(ret[0]); + + return ret; +} + +/* + * Suicide - reset or poweroff itself. + */ +static int +suicide_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = -1; + struct utsname name; + + ERRIFWRONGDEV(s, S_OOPS); + + if (request == ST_POWERON) { + LOG(PIL_CRIT, "%s not capable of power-on operation", DEVICE); + return S_INVAL; + } else if (request != ST_POWEROFF && request != ST_GENERIC_RESET) { + LOG(PIL_CRIT, "As for suicide virtual stonith device, " + "reset request=%d is not supported", request); + return S_INVAL; + } + + if (uname(&name) == -1) { + LOG(PIL_CRIT, "uname error %d", errno); + return S_RESETFAIL ; + } + + if (strcmp(name.nodename, host)) { + LOG(PIL_CRIT, "%s doesn't control host [%s]" + , name.nodename, host); + return S_RESETFAIL ; + } + + LOG(PIL_INFO, "Initiating suicide on host %s", host); + + rc = system( + request == ST_GENERIC_RESET ? REBOOT_COMMAND : POWEROFF_COMMAND); + + if (rc == 0) { + LOG(PIL_INFO, "Suicide stonith succeeded."); + return S_OK; + } else { + LOG(PIL_CRIT, "Suicide stonith failed."); + return S_RESETFAIL ; + } +} + +static const char * const * +suicide_get_confignames(StonithPlugin* p) +{ + /* Donnot need to initialize from external. */ + static const char * SuicideParams[] = { NULL }; + return SuicideParams; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +suicide_set_config(StonithPlugin* s, StonithNVpair* list) +{ + ERRIFWRONGDEV(s,S_OOPS); + return S_OK; +} + +static const char * +suicide_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + sd = (struct pluginDevice *)s; + + switch (reqtype) { + case ST_DEVICEID: + ret = sd->idinfo; + break; + + case ST_DEVICENAME: + ret = "suicide STONITH device"; + break; + + case ST_DEVICEDESCR: /* Description of device type */ + ret = "Virtual device to reboot/powerdown itself.\n"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = suicideXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Suicide Stonith destructor... + */ +static void +suicide_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd; + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice *)s; + + sd->pluginid = NOTpluginid; + FREE(sd); +} + +/* Create a new suicide Stonith device */ +static StonithPlugin* +suicide_new(const char * subplugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->idinfo = DEVICE; + sd->sp.s_ops = &suicideOps; + return &(sd->sp); +} diff --git a/lib/plugins/stonith/vacm.c b/lib/plugins/stonith/vacm.c new file mode 100644 index 0000000..ce6d041 --- /dev/null +++ b/lib/plugins/stonith/vacm.c @@ -0,0 +1,485 @@ + +/****************************************************************************** +* +* Copyright 2000 Sistina Software, Inc. +* Tiny bits Copyright 2000 Alan Robertson +* Tiny bits Copyright 2000 Zac Sprackett, VA Linux Systems +* Tiny bits Copyright 2005 International Business Machines +* Significantly Mangled by Sun Jiang Dong , IBM, 2005 +* +* This is free software released under the GNU General Public License. +* There is no warranty for this software. See the file COPYING for +* details. +* +* See the file CONTRIBUTORS for a list of contributors. +* +* This file is maintained by: +* Michael C Tilstra +* +* Becasue I have no device to test, now I just make it pass the compiling +* with vacm-2.0.5a. Please review before using. +* Sun Jiang Dong , IBM, 2005 +* +* This module provides a driver for the VA Linux Cluster Manager. +* For more information on VACM, see http://vacm.sourceforge.net/ +* +* This module is rather poorly commented. But if you've read the +* VACM Manual, and looked at the code example they have, this +* should make pretty clean sense. (You obiviously should have +* looked at the other stonith source too) +* +*/ + +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define DEVICE "VA Linux Cluster Manager" + +#include "stonith_plugin_common.h" +#include "vacmclient_api.h" + +#define PIL_PLUGIN vacm +#define PIL_PLUGIN_S "vacm" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +static StonithPlugin * vacm_new(const char *); +static void vacm_destroy(StonithPlugin *); +static const char * const * vacm_get_confignames(StonithPlugin *); +static int vacm_set_config(StonithPlugin *, StonithNVpair *); +static const char * vacm_getinfo(StonithPlugin * s, int InfoType); +static int vacm_status(StonithPlugin * ); +static int vacm_reset_req(StonithPlugin * s, int request, const char * host); +static char ** vacm_hostlist(StonithPlugin *); + +static struct stonith_ops vacmOps ={ + vacm_new, /* Create new STONITH object */ + vacm_destroy, /* Destroy STONITH object */ + vacm_getinfo, /* Return STONITH info string */ + vacm_get_confignames, /* Return configuration parameters */ + vacm_set_config, /* Set configuration */ + vacm_status, /* Return STONITH device status */ + vacm_reset_req, /* Request a reset */ + vacm_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug); +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &vacmOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/*structs*/ +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + void *h; /* a handle to the nexxus. */ + char * nexxus; + char * user; + char * passwd; +}; + +#define ST_NEXXUS "nexxus" + +static const char * pluginid = "VACMDevice-Stonith"; +static const char * NOTpluginid = "VACM device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_NEXXUS_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_NEXXUS \ + XML_PARM_SHORTDESC_END + +#define XML_NEXXUS_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The Nexxus component of the VA Cluster Manager" \ + XML_PARM_LONGDESC_END + +#define XML_NEXXUS_PARM \ + XML_PARAMETER_BEGIN(ST_NEXXUS, "string", "1", "1") \ + XML_NEXXUS_SHORTDESC \ + XML_NEXXUS_LONGDESC \ + XML_PARAMETER_END + +static const char *vacmXML = + XML_PARAMETERS_BEGIN + XML_NEXXUS_PARM + XML_LOGIN_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + +/*funcs*/ +int +vacm_status(StonithPlugin *s) +{ + struct pluginDevice *sd; + char snd[] = "NEXXUS:VERSION"; + char *rcv, *tk; + int rcvlen; + + ERRIFWRONGDEV(s,S_OOPS); + sd = (struct pluginDevice*)s; + + /* If grabbing the nexxus version works, then the status must be ok. + * right? + */ + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + break; + } + if (!(tk = strtok(rcv,":"))) { /*NEXXUS*/ + break; + }else if (!(tk=strtok(NULL,":"))) { /* Job ID */ + break; + }else if (!(tk=strtok(NULL,":"))) { /* one of the below */ + break; + } else if ( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return S_OK; /* YEAH!! */ + }else if(!strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + }else if(!strcmp(tk, "JOB_ERROR")) { + free(rcv); + break; + }else if(!strcmp(tk, "VERSION")) { + free(rcv); + continue; + } else { + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n" + , tk, rcv); + break; + } + } + + return S_OOPS; +} + +/* Better make sure the current group is correct. + * Can't think of a good way to do this. + */ +char ** +vacm_hostlist(StonithPlugin *s) +{ + struct pluginDevice *sd; + char snd[] = "NEXXUS:NODE_LIST"; + char *rcv,*tk; + int rcvlen; + char ** hlst=NULL; + int hacnt=0, hrcnt=0; +#define MSTEP 20 + + ERRIFWRONGDEV(s, NULL); + sd = (struct pluginDevice*)s; + + hlst = (char **)MALLOC(MSTEP * sizeof(char*)); + if (hlst == NULL) { + LOG(PIL_CRIT, "out of memory"); + return NULL; + } + hacnt=MSTEP; + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if(api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + goto HL_cleanup; + } + if(!(tk=strtok(rcv, ":"))) { /* NEXXUS */ + goto HL_cleanup; + }else if(!(tk=strtok(NULL,":"))) { /* Job ID */ + goto HL_cleanup; + }else if(!(tk=strtok(NULL,":"))) { /* JOB_* or NODELIST */ + goto HL_cleanup; + }else if( !strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + }else if( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return hlst; + }else if( !strcmp(tk, "JOB_ERROR")) { + free(rcv); + break; + }else if( !strcmp(tk, "NODELIST")) { + if(!(tk = strtok(NULL,":"))) { /* group */ + goto HL_cleanup; + }else if((tk = strtok(NULL," \t\n\r"))) { /*Finally, a machine name.*/ + if( hrcnt >= (hacnt-1)) { /* grow array. */ + char **oldhlst = hlst; + hlst = (char **)REALLOC(hlst, (hacnt +MSTEP)*sizeof(char*)); + if( !hlst ) { + stonith_free_hostlist(oldhlst); + return NULL; + } + hacnt += MSTEP; + } + hlst[hrcnt] = STRDUP(tk); /* stuff the name. */ + hlst[hrcnt+1] = NULL; /* set next to NULL for looping */ + if (hlst[hrcnt] == NULL) { + stonith_free_hostlist(hlst); + return NULL; + } + strdown(hlst[hrcnt]); + hrcnt++; + } + }else { + /* WTF?! */ + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n",tk,rcv); + break; + } + } + +HL_cleanup: + stonith_free_hostlist(hlst); /* give the mem back */ + return NULL; +} + +#define SND_SIZE 256 +int +vacm_reset_req(StonithPlugin *s, int request, const char *host) +{ + struct pluginDevice *sd; + char snd[SND_SIZE]; /* god forbid its bigger than this */ + char *rcv, *tk; + int rcvlen; + + ERRIFWRONGDEV(s,S_OOPS); + sd = (struct pluginDevice*)s; + + switch(request) { +#ifdef ST_POWERON + case ST_POWERON: + snprintf(snd, SND_SIZE, "EMP:POWER_ON:%s", host); + break; +#endif /*ST_POWERON*/ +#ifdef ST_POWEROFF + case ST_POWEROFF: + snprintf(snd, SND_SIZE, "EMP:POWER_OFF:%s", host); + break; +#endif /*ST_POWEROFF*/ + case ST_GENERIC_RESET: + snprintf(snd, SND_SIZE, "EMP:POWER_CYCLE:%s", host); + break; + default: + return S_INVAL; + } + + api_nexxus_send_ipc(sd->h, snd, strlen(snd)+1); + while(1) { + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + return S_RESETFAIL; + } + if (!(tk = strtok(rcv,":"))) { /*EMP*/ + break; + }else if (!(tk=strtok(NULL,":"))) { /* Job ID */ + break; + }else if (!(tk=strtok(NULL,":"))) { /* one of teh below */ + break; + } else if ( !strcmp(tk, "JOB_COMPLETED")) { + free(rcv); + return S_OK; + } else if(!strcmp(tk, "JOB_STARTED")) { + free(rcv); + continue; + } else if(!strcmp(tk, "JOB_ERROR")) { + free(rcv); + return S_RESETFAIL; + } else { + /* WTF?! */ + LOG(PIL_CRIT, "Unexpected token \"%s\" in line \"%s\"\n" + , tk, rcv); + break; + } + } + + return S_RESETFAIL; +} + +/* list => "nexxus:username:password" */ +static const char * const * +vacm_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_NEXXUS, ST_LOGIN, ST_PASSWD, NULL}; + return ret; +} + +static int +vacm_set_config(StonithPlugin *s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy [] = + { {ST_NEXXUS, NULL} + , {ST_LOGIN, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + char *rcv; + int rcvlen; + + ERRIFWRONGDEV(s, S_OOPS); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->nexxus = namestocopy[0].s_value; + sd->user = namestocopy[1].s_value; + sd->passwd = namestocopy[2].s_value; + /* When to initialize the sd->h */ + + if (api_nexxus_connect(sd->nexxus, sd->user, sd->passwd, &sd->h)<0){ + return S_OOPS; + } + if (api_nexxus_wait_for_data(sd->h, &rcv, &rcvlen, 20)<0) { + return S_OOPS; + } + if (strcmp(rcv, "NEXXUS_READY")) { + rc = S_BADCONFIG; + }else{ + rc = S_OK; + } + free(rcv); + + return(rc); +} + +/* + * The "vacmconf:" is in the conffile so that one file could be used for + * multiple device configs. This module will only look at the first line + * that starts with this token. All other line are ignored. (and thus + * could contain configs for other modules.) + * + * I don't think any other stonith modules do this currently. + */ +const char * +vacm_getinfo(StonithPlugin *s, int reqtype) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + switch (reqtype) { + + case ST_DEVICEID: /* What type of device? */ + ret = sd->idinfo; + break; + + case ST_DEVICENAME: /* Which particular device? */ + ret = dgettext(ST_TEXTDOMAIN, "VACM"); + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "A driver for the VA Linux Cluster Manager."; + break; + + case ST_DEVICEURL: /* VACM's web site */ + ret = "http://vacm.sourceforge.net/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = vacmXML; + break; + + default: + ret = NULL; + break; + } + + return ret; +} + +void +vacm_destroy(StonithPlugin *s) +{ + struct pluginDevice *sd; + + VOIDERRIFWRONGDEV(s); + sd = (struct pluginDevice*)s; + + if( sd->h ) { + api_nexxus_disconnect(sd->h); + } + + sd->pluginid = NOTpluginid; + if (sd->nexxus != NULL) { + FREE(sd->nexxus); + sd->nexxus = NULL; + } + if (sd->user != NULL) { + FREE(sd->user); + sd->user = NULL; + } + if (sd->passwd != NULL) { + FREE(sd->passwd); + sd->passwd = NULL; + } + + FREE(sd); +} + +static StonithPlugin * +vacm_new(const char *subplugin) +{ + struct pluginDevice *sd; + + sd = MALLOC(sizeof(struct pluginDevice)); + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(sd, 0, sizeof(*sd)); + sd->h = NULL; + sd->pluginid = pluginid; + sd->nexxus = NULL; + sd->user = NULL; + sd->passwd = NULL; + sd->idinfo = DEVICE; + sd->sp.s_ops = &vacmOps; + return &(sd->sp); /* same as "sd" */ +} diff --git a/lib/plugins/stonith/wti_mpc.c b/lib/plugins/stonith/wti_mpc.c new file mode 100644 index 0000000..548f91c --- /dev/null +++ b/lib/plugins/stonith/wti_mpc.c @@ -0,0 +1,856 @@ +/* + * Stonith module for WTI MPC (SNMP) + * Copyright (c) 2001 Andreas Piesk + * Mangled by Sun Jiang Dong , IBM, 2005 + * + * Modified for WTI MPC by Denis Chapligin , SatGate, 2009 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version.* + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +/* device ID */ +#define DEVICE "WTI MPC" + +#include "stonith_plugin_common.h" +#undef FREE /* defined by snmp stuff */ + +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif + +#ifdef HAVE_NET_SNMP_NET_SNMP_CONFIG_H +# include +# include +# include +# define INIT_AGENT() init_master_agent() +#else +# include +# include +# include +# ifndef NETSNMP_DS_APPLICATION_ID +# define NETSNMP_DS_APPLICATION_ID DS_APPLICATION_ID +# endif +# ifndef NETSNMP_DS_AGENT_ROLE +# define NETSNMP_DS_AGENT_ROLE DS_AGENT_ROLE +# endif +# define netsnmp_ds_set_boolean ds_set_boolean +# define INIT_AGENT() init_master_agent(161, NULL, NULL) +#endif + +#define PIL_PLUGIN wti_mpc +#define PIL_PLUGIN_S "wti_mpc" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include + +#define DEBUGCALL \ + if (Debug) { \ + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); \ + } + +static StonithPlugin * wti_mpc_new(const char *); +static void wti_mpc_destroy(StonithPlugin *); +static const char * const * wti_mpc_get_confignames(StonithPlugin *); +static int wti_mpc_set_config(StonithPlugin *, StonithNVpair *); +static const char * wti_mpc_getinfo(StonithPlugin * s, int InfoType); +static int wti_mpc_status(StonithPlugin * ); +static int wti_mpc_reset_req(StonithPlugin * s, int request, const char * host); +static char ** wti_mpc_hostlist(StonithPlugin *); + +static struct stonith_ops wti_mpcOps ={ + wti_mpc_new, /* Create new STONITH object */ + wti_mpc_destroy, /* Destroy STONITH object */ + wti_mpc_getinfo, /* Return STONITH info string */ + wti_mpc_get_confignames, /* Get configuration parameters */ + wti_mpc_set_config, /* Set configuration */ + wti_mpc_status, /* Return STONITH device status */ + wti_mpc_reset_req, /* Request a reset */ + wti_mpc_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + DEBUGCALL; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &wti_mpcOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * APCMaster tested with APC Masterswitch 9212 + */ + +/* outlet commands / status codes */ +#define OUTLET_ON 5 +#define OUTLET_OFF 6 +#define OUTLET_REBOOT 7 + +/* oids */ +#define OID_IDENT ".1.3.6.1.2.1.1.5.0" + +#define OID_GROUP_NAMES_V1 ".1.3.6.1.4.1.2634.3.1.3.1.2.%u" +#define OID_GROUP_STATE_V1 ".1.3.6.1.4.1.2634.3.1.3.1.3.%i" + +#define OID_GROUP_NAMES_V3 ".1.3.6.1.4.1.2634.3.100.300.1.2.%u" +#define OID_GROUP_STATE_V3 ".1.3.6.1.4.1.2634.3.100.300.1.3.%i" + +#define MAX_OUTLETS 128 + +/* + snmpset -c private -v1 172.16.0.32:161 + ".1.3.6.1.4.1.318.1.1.12.3.3.1.1.4.1" i 1 + The last octet in the OID is the plug number. The value can + be 1 thru 8 because there are 8 power plugs on this device. + The integer that can be set is as follows: 1=on, 2=off, and + 3=reset +*/ + +/* own defines */ +#define MAX_STRING 128 +#define ST_PORT "port" +#define ST_MIBVERSION "mib-version" + +/* structur of stonith object */ +struct pluginDevice { + StonithPlugin sp; /* StonithPlugin object */ + const char* pluginid; /* id of object */ + const char* idinfo; /* type of device */ + struct snmp_session* sptr; /* != NULL->session created */ + char * hostname; /* masterswitch's hostname */ + /* or ip addr */ + int port; /* snmp port */ + int mib_version; /* mib version to use */ + char * community; /* snmp community (r/w) */ + int num_outlets; /* number of outlets */ +}; + +/* constant strings */ +static const char *pluginid = "WTI-MPC-Stonith"; +static const char *NOTpluginID = "WTI MPC device has been destroyed"; + +#include "stonith_config_xml.h" + +#define XML_PORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_PORT \ + XML_PARM_SHORTDESC_END + +#define XML_PORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The port number on which the SNMP server is running on the STONITH device" \ + XML_PARM_LONGDESC_END + +#define XML_PORT_PARM \ + XML_PARAMETER_BEGIN(ST_PORT, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +#define XML_MIBVERSION_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_MIBVERSION \ + XML_PARM_SHORTDESC_END + +#define XML_MIBVERSION_LONGDESC \ + XML_MIBVERSION_LONGDESC_BEGIN("en") \ + "Version number of MPC MIB that we should use. Valid values are 1 (for 1.44 firmware) and 3 (for 1.62 firmware and later)" \ + XML_PARM_LONGDESC_END + +#define XML_MIBVERSION_PARM \ + XML_PARAMETER_BEGIN(ST_MIBVERSION, "string", "1", "0") \ + XML_PORT_SHORTDESC \ + XML_PORT_LONGDESC \ + XML_PARAMETER_END + +static const char *apcmastersnmpXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PORT_PARM + XML_COMMUNITY_PARM + XML_MIBVERSION_PARM + XML_PARAMETERS_END; + +/* + * own prototypes + */ + +static void MPC_error(struct snmp_session *sptr, const char *fn +, const char *msg); +static struct snmp_session *MPC_open(char *hostname, int port +, char *community); +static void *MPC_read(struct snmp_session *sptr, const char *objname +, int type); +static int MPC_write(struct snmp_session *sptr, const char *objname +, char type, char *value); + +static void +MPC_error(struct snmp_session *sptr, const char *fn, const char *msg) +{ + int snmperr = 0; + int cliberr = 0; + char *errstr; + + snmp_error(sptr, &cliberr, &snmperr, &errstr); + LOG(PIL_CRIT + , "%s: %s (cliberr: %i / snmperr: %i / error: %s)." + , fn, msg, cliberr, snmperr, errstr); + free(errstr); +} + + +/* + * creates a snmp session + */ +static struct snmp_session * +MPC_open(char *hostname, int port, char *community) +{ + static struct snmp_session session; + struct snmp_session *sptr; + + DEBUGCALL; + + /* create session */ + snmp_sess_init(&session); + + /* fill session */ + session.peername = hostname; + session.version = SNMP_VERSION_1; + session.remote_port = port; + session.community = (u_char *)community; + session.community_len = strlen(community); + session.retries = 5; + session.timeout = 1000000; + + /* open session */ + sptr = snmp_open(&session); + + if (sptr == NULL) { + MPC_error(&session, __FUNCTION__, "cannot open snmp session"); + } + + /* return pointer to opened session */ + return (sptr); +} + +/* + * parse config + */ + +/* + * read value of given oid and return it as string + */ +static void * +MPC_read(struct snmp_session *sptr, const char *objname, int type) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct variable_list *vars; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + static char response_str[MAX_STRING]; + static int response_int; + + DEBUGCALL; + + /* convert objname into oid; return NULL if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (NULL); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_GET)) != NULL) { + + /* get-request have no values */ + snmp_add_null_var(pdu, name, namelen); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == SNMPERR_SUCCESS) { + + /* request succeed, got valid response ? */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* go through the returned vars */ + for (vars = resp->variables; vars; + vars = vars->next_variable) { + + /* return response as string */ + if ((vars->type == type) && (type == ASN_OCTET_STR)) { + memset(response_str, 0, MAX_STRING); + strncpy(response_str, (char *)vars->val.string, + MIN(vars->val_len, MAX_STRING)); + snmp_free_pdu(resp); + return ((void *) response_str); + } + /* return response as integer */ + if ((vars->type == type) && (type == ASN_INTEGER)) { + response_int = *vars->val.integer; + snmp_free_pdu(resp); + return ((void *) &response_int); + } + } + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + MPC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free repsonse pdu (necessary?) */ + snmp_free_pdu(resp); + }else{ + MPC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error: return nothing */ + return (NULL); +} + +/* + * write value of given oid + */ +static int +MPC_write(struct snmp_session *sptr, const char *objname, char type, + char *value) +{ + oid name[MAX_OID_LEN]; + size_t namelen = MAX_OID_LEN; + struct snmp_pdu *pdu; + struct snmp_pdu *resp; + + DEBUGCALL; + + /* convert objname into oid; return FALSE if invalid */ + if (!read_objid(objname, name, &namelen)) { + LOG(PIL_CRIT, "%s: cannot convert %s to oid.", __FUNCTION__, objname); + return (FALSE); + } + + /* create pdu */ + if ((pdu = snmp_pdu_create(SNMP_MSG_SET)) != NULL) { + + /* add to be written value to pdu */ + snmp_add_var(pdu, name, namelen, type, value); + + /* send pdu and get response; return NULL if error */ + if (snmp_synch_response(sptr, pdu, &resp) == STAT_SUCCESS) { + + /* go through the returned vars */ + if (resp->errstat == SNMP_ERR_NOERROR) { + + /* request successful done */ + snmp_free_pdu(resp); + return (TRUE); + + }else{ + LOG(PIL_CRIT, "%s: error in response packet, reason %ld [%s]." + , __FUNCTION__, resp->errstat, snmp_errstring(resp->errstat)); + } + }else{ + MPC_error(sptr, __FUNCTION__, "error sending/receiving pdu"); + } + /* free pdu (again: necessary?) */ + snmp_free_pdu(resp); + }else{ + MPC_error(sptr, __FUNCTION__, "cannot create pdu"); + } + /* error */ + return (FALSE); +} + +/* + * return the status for this device + */ + +static int +wti_mpc_status(StonithPlugin * s) +{ + struct pluginDevice *ad; + char *ident; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + if ((ident = MPC_read(ad->sptr, OID_IDENT, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT, "%s: cannot read ident.", __FUNCTION__); + return (S_ACCESS); + } + + /* status ok */ + return (S_OK); +} + +/* + * return the list of hosts configured for this device + */ + +static char ** +wti_mpc_hostlist(StonithPlugin * s) +{ + char **hl; + struct pluginDevice *ad; + int j, h, num_outlets; + char *outlet_name; + char objname[MAX_STRING]; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, NULL); + + ad = (struct pluginDevice *) s; + + /* allocate memory for array of up to NUM_OUTLETS strings */ + if ((hl = (char **)MALLOC((ad->num_outlets+1) * sizeof(char *))) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + /* clear hostlist array */ + memset(hl, 0, (ad->num_outlets + 1) * sizeof(char *)); + num_outlets = 0; + + /* read NUM_OUTLETS values and put them into hostlist array */ + for (j = 0; j < ad->num_outlets; ++j) { + + /* prepare objname */ + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,j+1); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,j+1); + break; + } + if (Debug) { + LOG(PIL_DEBUG, "%s: using %s as group names oid", __FUNCTION__, objname); + } + + /* read outlet name */ + if ((outlet_name = MPC_read(ad->sptr, objname, ASN_OCTET_STR)) == + NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, j+1); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + + /* Check whether the host is already listed */ + for (h = 0; h < num_outlets; ++h) { + if (strcasecmp(hl[h],outlet_name) == 0) + break; + } + + if (h >= num_outlets) { + /* put outletname in hostlist */ + if (Debug) { + LOG(PIL_DEBUG, "%s: added %s to hostlist." + , __FUNCTION__, outlet_name); + } + + if ((hl[num_outlets] = STRDUP(outlet_name)) == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + stonith_free_hostlist(hl); + hl = NULL; + return (hl); + } + strdown(hl[num_outlets]); + num_outlets++; + } + } + + + if (Debug) { + LOG(PIL_DEBUG, "%s: %d unique hosts connected to %d outlets." + , __FUNCTION__, num_outlets, j); + } + /* return list */ + return (hl); +} + +/* + * reset the host + */ + +static int +wti_mpc_reset_req(StonithPlugin * s, int request, const char *host) +{ + struct pluginDevice *ad; + char objname[MAX_STRING]; + char value[MAX_STRING]; + char *outlet_name; + int req_oid = OUTLET_REBOOT; + int outlet; + int found_outlet=-1; + + DEBUGCALL; + + ERRIFNOTCONFIGED(s, S_OOPS); + + ad = (struct pluginDevice *) s; + + /* read max. as->num_outlets values */ + for (outlet = 1; outlet <= ad->num_outlets; outlet++) { + + /* prepare objname */ + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,outlet); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,outlet); + break; + } + + /* read outlet name */ + if ((outlet_name = MPC_read(ad->sptr, objname, ASN_OCTET_STR)) + == NULL) { + LOG(PIL_CRIT, "%s: cannot read name for outlet %d." + , __FUNCTION__, outlet); + return (S_ACCESS); + } + if (Debug) { + LOG(PIL_DEBUG, "%s: found outlet: %s.", __FUNCTION__, outlet_name); + } + + /* found one */ + if (strcasecmp(outlet_name, host) == 0) { + if (Debug) { + LOG(PIL_DEBUG, "%s: found %s at outlet %d." + , __FUNCTION__, host, outlet); + } + + /* Ok, stop iterating over host list */ + found_outlet=outlet; + break; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: outlet: %i.", __FUNCTION__, outlet); + } + + /* host not found in outlet names */ + if (found_outlet == -1) { + LOG(PIL_CRIT, "%s: no active outlet for '%s'.", __FUNCTION__, host); + return (S_BADHOST); + } + + + /* choose the OID for the stonith request */ + switch (request) { + case ST_POWERON: + req_oid = OUTLET_ON; + break; + case ST_POWEROFF: + req_oid = OUTLET_OFF; + break; + case ST_GENERIC_RESET: + req_oid = OUTLET_REBOOT; + break; + default: break; + } + + /* Turn them all off */ + + /* prepare objnames */ + + switch (ad->mib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_STATE_V3,found_outlet); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_STATE_V1,found_outlet); + break; + } + + snprintf(value, MAX_STRING, "%i", req_oid); + + /* send reboot cmd */ + if (!MPC_write(ad->sptr, objname, 'i', value)) { + LOG(PIL_CRIT + , "%s: cannot send reboot command for outlet %d." + , __FUNCTION__, found_outlet); + return (S_RESETFAIL); + } + + return (S_OK); +} + +/* + * Get the configuration parameter names. + */ + +static const char * const * +wti_mpc_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_PORT, ST_COMMUNITY, ST_MIBVERSION, NULL}; + return ret; +} + +/* + * Set the configuration parameters. + */ + +static int +wti_mpc_set_config(StonithPlugin * s, StonithNVpair * list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + char * i; + int mo; + char objname[MAX_STRING]; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PORT, NULL} + , {ST_COMMUNITY, NULL} + , {ST_MIBVERSION, NULL} + , {NULL, NULL} + }; + + DEBUGCALL; + ERRIFWRONGDEV(s,S_INVAL); + if (sd->sp.isconfigured) { + return S_OOPS; + } + + if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + sd->hostname = namestocopy[0].s_value; + sd->port = atoi(namestocopy[1].s_value); + PluginImports->mfree(namestocopy[1].s_value); + sd->community = namestocopy[2].s_value; + sd->mib_version = atoi(namestocopy[3].s_value); + PluginImports->mfree(namestocopy[3].s_value); + + /* try to resolve the hostname/ip-address */ + if (gethostbyname(sd->hostname) != NULL) { + /* init snmp library */ + init_snmp("wti_mpc"); + + /* now try to get a snmp session */ + if ((sd->sptr = MPC_open(sd->hostname, sd->port, sd->community)) != NULL) { + + /* ok, get the number of groups from the mpc */ + sd->num_outlets=0; + /* We scan goup names table starting from 1 to MAX_OUTLETS */ + /* and increase num_outlet counter on every group entry with name */ + /* first entry without name is the mark of the end of the group table */ + for (mo=1;momib_version) { + case 3: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V3,mo); + break; + case 1: + default: + snprintf(objname,MAX_STRING,OID_GROUP_NAMES_V1,mo); + break; + } + + if (Debug) { + LOG(PIL_DEBUG, "%s: used for groupTable retrieval: %s." + , __FUNCTION__, objname); + } + + if ((i = MPC_read(sd->sptr, objname, ASN_OCTET_STR)) == NULL) { + LOG(PIL_CRIT + , "%s: cannot read number of outlets." + , __FUNCTION__); + return (S_ACCESS); + } + if (strlen(i)) { + /* store the number of outlets */ + sd->num_outlets++; + } else { + break; + } + } + if (Debug) { + LOG(PIL_DEBUG, "%s: number of outlets: %i." + , __FUNCTION__, sd->num_outlets ); + } + + /* Everything went well */ + return (S_OK); + }else{ + LOG(PIL_CRIT, "%s: cannot create snmp session." + , __FUNCTION__); + } + }else{ + LOG(PIL_CRIT, "%s: cannot resolve hostname '%s', h_errno %d." + , __FUNCTION__, sd->hostname, h_errno); + } + + /* not a valid config */ + return (S_BADCONFIG); +} + +/* + * get info about the stonith device + */ + +static const char * +wti_mpc_getinfo(StonithPlugin * s, int reqtype) +{ + struct pluginDevice *ad; + const char *ret = NULL; + + DEBUGCALL; + + ERRIFWRONGDEV(s, NULL); + + ad = (struct pluginDevice *) s; + + switch (reqtype) { + case ST_DEVICEID: + ret = ad->idinfo; + break; + + case ST_DEVICENAME: + ret = ad->hostname; + break; + + case ST_DEVICEDESCR: + ret = "WTI MPC (via SNMP)\n" + "The WTI MPC can accept multiple simultaneous SNMP clients"; + break; + + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = apcmastersnmpXML; + break; + + } + return ret; +} + + +/* + * APC StonithPlugin destructor... + */ + +static void +wti_mpc_destroy(StonithPlugin * s) +{ + struct pluginDevice *ad; + + DEBUGCALL; + + VOIDERRIFWRONGDEV(s); + + ad = (struct pluginDevice *) s; + + ad->pluginid = NOTpluginID; + + /* release snmp session */ + if (ad->sptr != NULL) { + snmp_close(ad->sptr); + ad->sptr = NULL; + } + + /* reset defaults */ + if (ad->hostname != NULL) { + PluginImports->mfree(ad->hostname); + ad->hostname = NULL; + } + if (ad->community != NULL) { + PluginImports->mfree(ad->community); + ad->community = NULL; + } + ad->num_outlets = 0; + + PluginImports->mfree(ad); +} + +/* + * Create a new APC StonithPlugin device. Too bad this function can't be + * static + */ + +static StonithPlugin * +wti_mpc_new(const char *subplugin) +{ + struct pluginDevice *ad = ST_MALLOCT(struct pluginDevice); + + DEBUGCALL; + + /* no memory for stonith-object */ + if (ad == NULL) { + LOG(PIL_CRIT, "%s: out of memory.", __FUNCTION__); + return (NULL); + } + + /* clear stonith-object */ + memset(ad, 0, sizeof(*ad)); + + /* set defaults */ + ad->pluginid = pluginid; + ad->sptr = NULL; + ad->hostname = NULL; + ad->community = NULL; + ad->mib_version=1; + ad->idinfo = DEVICE; + ad->sp.s_ops = &wti_mpcOps; + + /* return the object */ + return (&(ad->sp)); +} diff --git a/lib/plugins/stonith/wti_nps.c b/lib/plugins/stonith/wti_nps.c new file mode 100644 index 0000000..f0b81f7 --- /dev/null +++ b/lib/plugins/stonith/wti_nps.c @@ -0,0 +1,813 @@ +/* + * + * Copyright 2001 Mission Critical Linux, Inc. + * + * All Rights Reserved. + */ +/* + * Stonith module for WTI Network Power Switch Devices (NPS-xxx) + * Also supports the WTI Telnet Power Switch Devices (TPS-xxx) + * + * Copyright 2001 Mission Critical Linux, Inc. + * author: mike ledoux + * author: Todd Wheeling + * Mangled by Zhaokai , IBM, 2005 + * Further hurt by Lon , Red Hat, 2005 + * + * Supported WTI devices: + * NPS-115 + * NPS-230 + * IPS-15 + * IPS-800 + * IPS-800-CE + * NBB-1600 + * NBB-1600-CE + * TPS-2 + * + * Based strongly on original code from baytech.c by Alan Robertson. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* Observations/Notes + * + * 1. The WTI Network Power Switch, unlike the BayTech network power switch, + * accpets only one (telnet) connection/session at a time. When one + * session is active, any subsequent attempt to connect to the NPS will + * result in a connection refused/closed failure. In a cluster environment + * or other environment utilizing polling/monitoring of the NPS + * (from multiple nodes), this can clearly cause problems. Obviously the + * more nodes and the shorter the polling interval, the more frequently such + * errors/collisions may occur. + * + * 2. We observed that on busy networks where there may be high occurances + * of broadcasts, the NPS became unresponsive. In some + * configurations this necessitated placing the power switch onto a + * private subnet. + */ + +#include +#define DEVICE "WTI Network Power Switch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN wti_nps +#define PIL_PLUGIN_S "wti_nps" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define MAX_WTIPLUGINID 256 + +#include + +#include "stonith_signal.h" + +static StonithPlugin * wti_nps_new(const char *); +static void wti_nps_destroy(StonithPlugin *); +static const char * const * wti_nps_get_confignames(StonithPlugin *); +static int wti_nps_set_config(StonithPlugin * , StonithNVpair * ); +static const char * wti_nps_get_info(StonithPlugin * s, int InfoType); +static int wti_nps_status(StonithPlugin * ); +static int wti_nps_reset_req(StonithPlugin * s, int request, const char * host); +static char ** wti_nps_hostlist(StonithPlugin *); + +static struct stonith_ops wti_npsOps ={ + wti_nps_new, /* Create new STONITH object */ + wti_nps_destroy, /* Destroy STONITH object */ + wti_nps_get_info, /* Return STONITH info string */ + wti_nps_get_confignames,/* Return configration parameters */ + wti_nps_set_config, /* set configration */ + wti_nps_status, /* Return STONITH device status */ + wti_nps_reset_req, /* Request a reset */ + wti_nps_hostlist, /* Return list of supported hosts */ +}; + +PIL_PLUGIN_BOILERPLATE2("1.0", Debug) +static const PILPluginImports* PluginImports; +static PILPlugin* OurPlugin; +static PILInterface* OurInterface; +static StonithImports* OurImports; +static void* interfprivate; + +#include "stonith_expect_helpers.h" + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports); + +PIL_rc +PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports) +{ + /* Force the compiler to do a little type checking */ + (void)(PILPluginInitFun)PIL_PLUGIN_INIT; + + PluginImports = imports; + OurPlugin = us; + + /* Register ourself as a plugin */ + imports->register_plugin(us, &OurPIExports); + + /* Register our interface implementation */ + return imports->register_interface(us, PIL_PLUGINTYPE_S + , PIL_PLUGIN_S + , &wti_npsOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have a NPS-110. This code has been tested with this switch. + * (Tested with NPS-230 and TPS-2 by lmb) + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * passwd; +}; + +static const char * pluginid = "WTINPS-Stonith"; +static const char * NOTnpsid = "WTINPS device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *wti_npsXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + + +/* + * Different expect strings that we get from the WTI + * Network Power Switch + */ + +#define WTINPSSTR " Power Switch" +#define WTINBBSTR "Boot Bar" + +static struct Etoken password[] = { {"Password:", 0, 0}, {NULL,0,0}}; +static struct Etoken Prompt[] = { {"PS>", 0, 0} + , {"IPS>", 0, 0} + , {"BB>", 0, 0} + , {NULL,0,0}}; +static struct Etoken LoginOK[] = { {WTINPSSTR, 0, 0} + , {WTINBBSTR, 0, 0} + , {"Invalid password", 1, 0} + , {NULL,0,0} }; +static struct Etoken Separator[] = { {"-----+", 0, 0} ,{NULL,0,0}}; + +/* We may get a notice about rebooting, or a request for confirmation */ +static struct Etoken Processing[] = { {"rocessing - please wait", 0, 0} + , {"(Y/N):", 1, 0} + , {NULL,0,0}}; + +static int NPS_connect_device(struct pluginDevice * nps); +static int NPSLogin(struct pluginDevice * nps); +static int NPSNametoOutlet(struct pluginDevice*, const char * name, char **outlets); +static int NPSReset(struct pluginDevice*, char * outlets, const char * rebootid); +static int NPSLogout(struct pluginDevice * nps); + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int NPS_onoff(struct pluginDevice*, const char * outlets, const char * unitid +, int request); +#endif + +/* Attempt to login up to 20 times... */ +static int +NPSRobustLogin(struct pluginDevice * nps) +{ + int rc = S_OOPS; + int j = 0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + for ( ; ; ) { + if (NPS_connect_device(nps) == S_OK) { + rc = NPSLogin(nps); + if (rc == S_OK) { + break; + } + } + if ((++j) == 20) { + break; + } + else { + sleep(1); + } + } + + return rc; +} + +/* Login to the WTI Network Power Switch (NPS) */ +static int +NPSLogin(struct pluginDevice * nps) +{ + char IDinfo[128]; + char * idptr = IDinfo; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Look for the unit type info */ + if (EXPECT_TOK(nps->rdfd, password, 2, IDinfo + , sizeof(IDinfo), Debug) < 0) { + LOG(PIL_CRIT, "No initial response from %s.", nps->idinfo); + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + idptr += strspn(idptr, WHITESPACE); + /* + * We should be looking at something like this: + * Enter Password: + */ + + SEND(nps->wrfd, nps->passwd); + SEND(nps->wrfd, "\r"); + /* Expect "Network Power Switch vX.YY" */ + + switch (StonithLookFor(nps->rdfd, LoginOK, 5)) { + + case 0: /* Good! */ + LOG(PIL_INFO, "Successful login to %s.", nps->idinfo); + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", nps->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + return(S_OK); +} + +/* Log out of the WTI NPS */ + +static int +NPSLogout(struct pluginDevice* nps) +{ + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect back prompt */ + /* + SEND(nps->wrfd, "/h\r"); + */ + /* Expect "PS>" */ + rc = StonithLookFor(nps->rdfd, Prompt, 5); + + /* "/x" is Logout, "/x,y" auto-confirms */ + SEND(nps->wrfd, "/x,y\r"); + + close(nps->wrfd); + close(nps->rdfd); + nps->wrfd = nps->rdfd = -1; + + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} + +/* Reset (power-cycle) the given outlets */ +static int +NPSReset(struct pluginDevice* nps, char * outlets, const char * rebootid) +{ + char unum[32]; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect back prompt */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* Send REBOOT command for given outlets */ + snprintf(unum, sizeof(unum), "/BOOT %s,y\r", outlets); + SEND(nps->wrfd, unum); + + /* Expect "Processing "... or "(Y/N)" (if confirmation turned on) */ + + retry: + switch (StonithLookFor(nps->rdfd, Processing, 5)) { + case 0: /* Got "Processing" Do nothing */ + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(nps->wrfd, "Y\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + LOG(PIL_INFO, "Host is being rebooted: %s", rebootid); + + /* Expect "PS>" */ + if (StonithLookFor(nps->rdfd, Prompt, 60) < 0) { + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + + /* All Right! Power is back on. Life is Good! */ + + LOG(PIL_INFO, "Power restored to host: %s", rebootid); + SEND(nps->wrfd, "/h\r"); + return(S_OK); +} + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int +NPS_onoff(struct pluginDevice* nps, const char * outlets, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "/On" : "/Off"); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect prompt back */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* Send ON/OFF command for given outlet */ + snprintf(unum, sizeof(unum), "%s %s,y\r", onoff, outlets); + SEND(nps->wrfd, unum); + + /* Expect "Processing"... or "(Y/N)" (if confirmation turned on) */ + + if (StonithLookFor(nps->rdfd, Processing, 5) == 1) { + /* They've turned on that annoying command confirmation :-( */ + SEND(nps->wrfd, "Y\r"); + } + EXPECT(nps->rdfd, Prompt, 60); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to NPS outlet(s) %s turned %s", outlets, onoff); + + SEND(nps->wrfd, "/h\r"); + return(S_OK); +} +#endif /* defined(ST_POWERON) && defined(ST_POWEROFF) */ + +/* + * Map the given host name into an (AC) Outlet number on the power strip + */ + +static int +NPSNametoOutlet(struct pluginDevice* nps, const char * name, char **outlets) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + char buf[32]; + int left = 17; + int ret = -1; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if ((*outlets = (char *)MALLOC(left*sizeof(char))) == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(-1); + } + + strncpy(*outlets, "", left); + left = left - 1; /* ensure terminating '\0' */ + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(nps->wrfd, "/s\r"); + + /* Expect: "-----+" so we can skip over it... */ + EXPECT(nps->rdfd, Separator, 5); + + do { + NameMapping[0] = EOS; + SNARF(nps->rdfd, NameMapping, 5); + + if (sscanf(NameMapping + , "%d | %16c",&sockno, sockname) == 2) { + + char * last = sockname+16; + *last = EOS; + --last; + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strncasecmp(name, sockname, 16) == 0) { + ret = sockno; + snprintf(buf, sizeof(buf), "%d ", sockno); + strncat(*outlets, buf, left); + left = left - strlen(buf); + } + } + } while (strlen(NameMapping) > 2 && left > 0); + + return(ret); +} + +static int +wti_nps_status(StonithPlugin *s) +{ + struct pluginDevice* nps; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = NPSRobustLogin(nps) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + return(rc); + } + + /* Send "/h" help command and expect back prompt */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + return(NPSLogout(nps)); +} + +/* + * Return the list of hosts (outlet names) for the devices on this NPS unit + */ + +static char ** +wti_nps_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* nps; + unsigned int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + nps = (struct pluginDevice*) s; + if (NPSRobustLogin(nps) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + return(NULL); + } + + /* Expect "PS>" */ + NULLEXPECT(nps->rdfd, Prompt, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(nps->wrfd, "/s\r"); + + /* Expect: "-----" so we can skip over it... */ + NULLEXPECT(nps->rdfd, Separator, 5); + NULLEXPECT(nps->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + int sockno; + char sockname[64]; + NameMapping[0] = EOS; + NULLSNARF(nps->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d | %16c",&sockno, sockname) == 2) { + + char * last = sockname+16; + char * nm; + *last = EOS; + --last; + + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (numnames >= DIMOF(NameList)-1) { + break; + } + if (!strcmp(sockname,"(undefined)") || + !strcmp(sockname,"---")) { + /* lhh - skip undefined */ + continue; + } + if ((nm = STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } + } while (strlen(NameMapping) > 2); + + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memset(ret, 0, (numnames+1)*sizeof(char*)); + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)NPSLogout(nps); + + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; iOpenStreamSocket(nps->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + nps->rdfd = nps->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +wti_nps_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + int lorc = 0; + struct pluginDevice* nps; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = NPSRobustLogin(nps)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + }else{ + char *outlets; + int noutlet; + + outlets = NULL; + noutlet = NPSNametoOutlet(nps, host, &outlets); + + if (noutlet < 1) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , nps->device, host); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + return(S_BADHOST); + } + switch(request) { + +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + case ST_POWEROFF: + rc = NPS_onoff(nps, outlets, host, request); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; +#endif + case ST_GENERIC_RESET: + rc = NPSReset(nps, outlets, host); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; + default: + rc = S_INVAL; + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; + } + } + + lorc = NPSLogout(nps); + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +wti_nps_set_config(StonithPlugin * s, StonithNVpair *list) +{ + struct pluginDevice* nps; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + nps->device = namestocopy[0].s_value; + nps->passwd = namestocopy[1].s_value; + return S_OK; +} + + +/* + * Return the Stonith plugin configuration parameter + * + */ +static const char * const * +wti_nps_get_confignames(StonithPlugin * p) +{ + static const char * names[] = { ST_IPADDR , ST_PASSWD , NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + +/* + * Get info about the stonith device + * + */ +static const char * +wti_nps_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nps; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nps = (struct pluginDevice *)s; + + switch (reqtype) { + + case ST_DEVICEID: + ret = nps->idinfo; + break; + case ST_DEVICENAME: + ret = nps->device; + break; + case ST_DEVICEDESCR: + ret = "Western Telematic (WTI) Network Power Switch Devices (NPS-xxx)\n" + "Also supports the WTI Telnet Power Switch Devices (TPS-xxx)\n" + "NOTE: The WTI Network Power Switch, accepts only " + "one (telnet) connection/session at a time."; + break; + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = wti_npsXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * WTI NPS Stonith destructor... + */ +static void +wti_nps_destroy(StonithPlugin *s) +{ + struct pluginDevice* nps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + nps = (struct pluginDevice *)s; + + nps->pluginid = NOTnpsid; + if (nps->rdfd >= 0) { + close(nps->rdfd); + nps->rdfd = -1; + } + if (nps->wrfd >= 0) { + close(nps->wrfd); + nps->wrfd = -1; + } + if (nps->device != NULL) { + FREE(nps->device); + nps->device = NULL; + } + if (nps->passwd != NULL) { + FREE(nps->passwd); + nps->passwd = NULL; + } + FREE(nps); +} + +/* Create a new BayTech Stonith device. */ + +static StonithPlugin * +wti_nps_new(const char *subplugin) +{ + struct pluginDevice* nps = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (nps == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nps, 0, sizeof(*nps)); + nps->pluginid = pluginid; + nps->pid = -1; + nps->rdfd = -1; + nps->wrfd = -1; + nps->device = NULL; + nps->passwd = NULL; + nps->idinfo = DEVICE; + nps->sp.s_ops = &wti_npsOps; + + return &(nps->sp); +} + diff --git a/lib/stonith/Makefile.am b/lib/stonith/Makefile.am new file mode 100644 index 0000000..429e1d3 --- /dev/null +++ b/lib/stonith/Makefile.am @@ -0,0 +1,54 @@ +# +# Stonith: Shoot The Node In The Head +# +# Copyright (C) 2001 Alan Robertson +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +## include files + +## binaries +sbin_PROGRAMS = stonith meatclient + +stonith_SOURCES = main.c + +stonith_LDADD = libstonith.la $(top_builddir)/lib/pils/libpils.la $(GLIBLIB) \ + $(top_builddir)/lib/clplumbing/libplumb.la \ + $(top_builddir)/lib/clplumbing/libplumbgpl.la +stonith_LDFLAGS = @LIBADD_DL@ @LIBLTDL@ -export-dynamic @DLOPEN_FORCE_FLAGS@ @LIBADD_INTL@ + +meatclient_SOURCES = meatclient.c +meatclient_LDADD = $(GLIBLIB) libstonith.la + +## libraries + +lib_LTLIBRARIES = libstonith.la + +libstonith_la_SOURCES = expect.c stonith.c st_ttylock.c +libstonith_la_LDFLAGS = -version-info 1:0:0 +libstonith_la_LIBADD = $(top_builddir)/lib/pils/libpils.la \ + $(top_builddir)/replace/libreplace.la \ + $(GLIBLIB) + +helperdir = $(datadir)/$(PACKAGE_NAME) +helper_SCRIPTS = ha_log.sh + +EXTRA_DIST = $(helper_SCRIPTS) diff --git a/lib/stonith/README b/lib/stonith/README new file mode 100644 index 0000000..6b98ef9 --- /dev/null +++ b/lib/stonith/README @@ -0,0 +1,31 @@ +The STONITH module (a.k.a. STOMITH) provides an extensible interface +for remotely powering down a node in the cluster. The idea is quite simple: +When the software running on one machine wants to make sure another +machine in the cluster is not using a resource, pull the plug on the other +machine. It's simple and reliable, albiet admittedly brutal. + +Here's an example command line invocation used to power off a machine +named 'nodeb'. The parameters are dependent on the type of device you +are using for this capability. + +stonith -t rps10 -p "/dev/ttyS5 nodeb 0 " nodeb + +Currently supported devices: + + apcsmart: APCSmart (tested with 2 old 900XLI) + baytech: Baytech RPC5 + meatware: Alerts an operator to manually turn off a device. + nw_rpc100s: Micro Energetics Night/Ware RPC100S + rps10: Western Telematics RPS10 + vacm_stonith: VA Linux Cluster Manager (see README.vacm) + + +To see the parameter syntax for a module, run the 'stonith' command and omit the +-p parameter. For example: + +$ /usr/sbin/stonith -t rps10 test + +stonith: Invalid config file for rps10 device. +stonith: Config file syntax: [ [...] ] +All tokens are white-space delimited. +Blank lines and lines beginning with # are ignored diff --git a/lib/stonith/expect.c b/lib/stonith/expect.c new file mode 100644 index 0000000..bb1f818 --- /dev/null +++ b/lib/stonith/expect.c @@ -0,0 +1,539 @@ +/* + * Simple expect module for the STONITH library + * + * Copyright (c) 2000 Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#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 +#define ENABLE_PIL_DEFS_PRIVATE +#include + +#ifdef _POSIX_PRIORITY_SCHEDULING +# include +#endif + +#include +#include + +extern PILPluginUniv* StonithPIsys; + +#define LOG(args...) PILCallLog(StonithPIsys->imports->log, args) +#define DEBUG(args...) LOG(PIL_DEBUG, args) +#undef DEBUG +#define DEBUG(args...) PILCallLog(StonithPIsys->imports->log, PIL_DEBUG, args) +#define MALLOC StonithPIsys->imports->alloc +#define REALLOC StonithPIsys->imports->mrealloc +#define STRDUP StonithPIsys->imports->mstrdup +#define FREE(p) {StonithPIsys->imports->mfree(p); (p) = NULL;} + +#ifdef TIMES_ALLOWS_NULL_PARAM +# define TIMES_PARAM NULL +#else + static struct tms dummy_longclock_tms_struct; +# define TIMES_PARAM &dummy_longclock_tms_struct +#endif + +static unsigned long +our_times(void) /* Make times(2) behave rationally on Linux */ +{ + clock_t ret; +#ifndef DISABLE_TIMES_KLUDGE + int save_errno = errno; + + /* + * This code copied from clplumbing/longclock.c to avoid + * making STONITH depend on clplumbing. See it for an explanation + */ + + errno = 0; +#endif /* DISABLE_TIMES_KLUDGE */ + + ret = times(TIMES_PARAM); + +#ifndef DISABLE_TIMES_KLUDGE + if (errno != 0) { + ret = (clock_t) (-errno); + } + errno = save_errno; +#endif /* DISABLE_TIMES_KLUDGE */ + return (unsigned long)ret; +} + +/* + * Look for ('expect') any of a series of tokens in the input + * Return the token type for the given token or -1 on error. + */ + +static int +ExpectToken(int fd, struct Etoken * toklist, int to_secs, char * savebuf +, int maxline, int Debug) +{ + unsigned long starttime; + unsigned long endtime; + int wraparound=0; + unsigned Hertz = sysconf(_SC_CLK_TCK); + int tickstousec = (1000000/Hertz); + unsigned long now; + unsigned long ticks; + int nchars = 1; /* reserve space for an EOS */ + struct timeval tv; + char * buf = savebuf; + + struct Etoken * this; + + /* Figure out when to give up. Handle lbolt wraparound */ + + starttime = our_times(); + ticks = (to_secs*Hertz); + endtime = starttime + ticks; + + if (endtime < starttime) { + wraparound = 1; + } + + if (buf) { + *buf = EOS; + } + + for (this=toklist; this->string; ++this) { + this->matchto = 0; + } + + + while (now = our_times(), + (wraparound && (now > starttime || now <= endtime)) + || (!wraparound && now <= endtime)) { + + fd_set infds; + char ch; + unsigned long timeleft; + int retval; + + timeleft = endtime - now; + + tv.tv_sec = timeleft / Hertz; + tv.tv_usec = (timeleft % Hertz) * tickstousec; + + if (tv.tv_sec == 0 && tv.tv_usec < tickstousec) { + /* Give 'em a little chance */ + tv.tv_usec = tickstousec; + } + + /* Watch our FD to see when it has input. */ + FD_ZERO(&infds); + FD_SET(fd, &infds); + + retval = select(fd+1, &infds, NULL, NULL, &tv); + if (retval <= 0) { + errno = ETIMEDOUT; + return(-1); + } + /* Whew! All that work just to read one character! */ + + if (read(fd, &ch, sizeof(ch)) <= 0) { + return(-1); + } + /* Save the text, if we can */ + if (buf && nchars < maxline-1) { + *buf = ch; + ++buf; + *buf = EOS; + ++nchars; + } + if (Debug > 1) { + DEBUG("Got '%c'", ch); + } + + /* See how this character matches our expect strings */ + + for (this=toklist; this->string; ++this) { + + if (ch == this->string[this->matchto]) { + + /* It matches the current token */ + + ++this->matchto; + if (this->string[this->matchto] == EOS){ + /* Hallelujah! We matched */ + if (Debug) { + DEBUG("Matched [%s] [%d]" + , this->string + , this->toktype); + if (savebuf) { + DEBUG("Saved [%s]" + , savebuf); + } + } + return(this->toktype); + } + }else{ + + /* It doesn't appear to match this token */ + + int curlen; + int nomatch=1; + /* + * If we already had a match (matchto is + * greater than zero), we look for a match + * of the tail of the pattern matched so far + * (with the current character) against the + * head of the pattern. + */ + + /* + * This is to make the string "aab" match + * the pattern "ab" correctly + * Painful, but nice to do it right. + */ + + for (curlen = (this->matchto) + ; nomatch && curlen >= 0 + ; --curlen) { + const char * tail; + tail=(this->string) + + this->matchto + - curlen; + + if (strncmp(this->string, tail + , curlen) == 0 + && this->string[curlen] == ch) { + + if (this->string[curlen+1]==EOS){ + /* We matched! */ + /* (can't happen?) */ + return(this->toktype); + } + this->matchto = curlen+1; + nomatch=0; + } + } + if (nomatch) { + this->matchto = 0; + } + } + } + } + errno = ETIMEDOUT; + return(-1); +} + +/* + * Start a process with its stdin and stdout redirected to pipes + * so the parent process can talk to it. + */ +static int +StartProcess(const char * cmd, int * readfd, int * writefd) +{ + pid_t pid; + int wrpipe[2]; /* The pipe the parent process writes to */ + /* (which the child process reads from) */ + int rdpipe[2]; /* The pipe the parent process reads from */ + /* (which the child process writes to) */ + + if (pipe(wrpipe) < 0) { + perror("cannot create pipe\n"); + return(-1); + } + if (pipe(rdpipe) < 0) { + perror("cannot create pipe\n"); + close(wrpipe[0]); + close(wrpipe[1]); + return(-1); + } + switch(pid=fork()) { + + case -1: perror("cannot StartProcess cmd"); + close(rdpipe[0]); + close(wrpipe[1]); + close(wrpipe[0]); + close(rdpipe[1]); + return(-1); + + case 0: /* We are the child */ + + /* Redirect stdin */ + close(0); + dup2(wrpipe[0], 0); + close(wrpipe[0]); + close(wrpipe[1]); + + /* Redirect stdout */ + close(1); + dup2(rdpipe[1], 1); + close(rdpipe[0]); + close(rdpipe[1]); +#if defined(SCHED_OTHER) && !defined(ON_DARWIN) + { + /* + * Try and (re)set our scheduling to "normal" + * Sometimes our callers run in soft + * real-time mode. The program we exec might + * not be very well behaved - this is bad for + * operation in high-priority (soft real-time) + * mode. In particular, telnet is prone to + * going into infinite loops when killed. + */ + struct sched_param sp; + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = 0; + sched_setscheduler(0, SCHED_OTHER, &sp); + } +#endif + execlp("/bin/sh", "sh", "-c", cmd, (const char *)NULL); + perror("cannot exec shell!"); + exit(1); + + default: /* We are the parent */ + *readfd = rdpipe[0]; + close(rdpipe[1]); + + *writefd = wrpipe[1]; + close(wrpipe[0]); + return(pid); + } + /*NOTREACHED*/ + return(-1); +} + +static char ** +stonith_copy_hostlist(const char * const * hostlist) +{ + int hlleng = 1; + const char * const * here = hostlist; + char ** hret; + char ** ret; + + for (here = hostlist; *here; ++here) { + ++hlleng; + } + ret = (char**)MALLOC(hlleng * sizeof(char *)); + if (ret == NULL) { + return ret; + } + + hret = ret; + for (here = hostlist; *here; ++here,++hret) { + *hret = STRDUP(*here); + if (*hret == NULL) { + stonith_free_hostlist(ret); + return NULL; + } + } + *hret = NULL; + return ret; +} + +static char ** +StringToHostList(const char * s) +{ + const char * here; + int hlleng = 0; + char ** ret; + char ** hret; + const char * delims = " \t\n\f\r,"; + + /* Count the number of strings (words) in the result */ + here = s; + while (*here != EOS) { + /* skip delimiters */ + here += strspn(here, delims); + if (*here == EOS) { + break; + } + /* skip over substring proper... */ + here += strcspn(here, delims); + ++hlleng; + } + + + /* Malloc space for the result string pointers */ + ret = (char**)MALLOC((hlleng+1) * sizeof(char *)); + if (ret == NULL) { + return NULL; + } + + hret = ret; + here = s; + + /* Copy each substring into a separate string */ + while (*here != EOS) { + int slen; /* substring length */ + + /* skip delimiters */ + here += strspn(here, delims); + if (*here == EOS) { + break; + } + /* Compute substring length */ + slen = strcspn(here, delims); + *hret = MALLOC((slen+1) * sizeof(char)); + if (*hret == NULL) { + stonith_free_hostlist(hret); + return NULL; + } + /* Copy string (w/o EOS) */ + memcpy(*hret, here, slen); + /* Add EOS to result string */ + (*hret)[slen] = EOS; + strdown(*hret); + here += slen; + ++hret; + } + *hret = NULL; + return ret; +} + + +static const char * +GetValue(StonithNVpair* parameters, const char * name) +{ + while (parameters->s_name) { + if (strcmp(name, parameters->s_name) == 0) { + return parameters->s_value; + } + ++parameters; + } + return NULL; +} + +static int +CopyAllValues(StonithNamesToGet* output, StonithNVpair * input) +{ + int j; + int rc; + + for (j=0; output[j].s_name; ++j) { + const char * value = GetValue(input, output[j].s_name); + if (value == NULL) { + rc = S_INVAL; + output[j].s_value = NULL; + goto fail; + } + if ((output[j].s_value = STRDUP(value)) == NULL) { + rc = S_OOPS; + goto fail; + } + } + return S_OK; + +fail: + for (j=0; output[j].s_value; ++j) { + FREE(output[j].s_value); + } + return rc; +} + + +static int +OpenStreamSocket(const char * host, int port, const char * service) +{ + union s_un { + struct sockaddr_in si4; + struct sockaddr_in6 si6; + }sockun; + int sock; + int addrlen = -1; + + + memset(&sockun, 0, sizeof(sockun)); + + if (inet_pton(AF_INET, host, (void*)&sockun.si4.sin_addr) < 0) { + sockun.si4.sin_family = AF_INET; + }else if (inet_pton(AF_INET6, host, (void*)&sockun.si6.sin6_addr)<0){ + sockun.si6.sin6_family = AF_INET6; + }else{ + struct hostent* hostp = gethostbyname(host); + if (hostp == NULL) { + errno = EINVAL; + return -1; + } + sockun.si4.sin_family = hostp->h_addrtype; + memcpy(&sockun.si4.sin_addr, hostp->h_addr, hostp->h_length); + } + if ((sock = socket(sockun.si4.sin_family, SOCK_STREAM, 0)) < 0) { + return -1; + } + if (service != NULL) { + struct servent* se = getservbyname(service, "tcp"); + if (se != NULL) { + /* We convert it back later... */ + port = ntohs(se->s_port); + } + } + if (port <= 0) { + errno = EINVAL; + return -1; + } + port = htons(port); + if (sockun.si6.sin6_family == AF_INET6) { + sockun.si6.sin6_port = port; + addrlen = sizeof(sockun.si6); + }else if (sockun.si4.sin_family == AF_INET) { + sockun.si4.sin_port = port; + addrlen = sizeof(sockun.si4); + }else{ + errno = EINVAL; + return -1; + } + + if (connect(sock, (struct sockaddr*)(&sockun), addrlen)< 0){ + int save = errno; + perror("connect() failed"); + close(sock); + errno = save; + return -1; + } + return sock; +} + +StonithImports stonithimports = { + ExpectToken, + StartProcess, + OpenStreamSocket, + GetValue, + CopyAllValues, + StringToHostList, + stonith_copy_hostlist, + stonith_free_hostlist, + st_ttylock, + st_ttyunlock +}; diff --git a/lib/stonith/ha_log.sh b/lib/stonith/ha_log.sh new file mode 100755 index 0000000..73093f0 --- /dev/null +++ b/lib/stonith/ha_log.sh @@ -0,0 +1,114 @@ +#!/bin/sh +# +# +# ha_log.sh for stonith external plugins +# (equivalent to ocf_log in ocf-shellfuncs in resource-agents) +# +# Copyright (c) 2004 SUSE LINUX AG, Lars Marowsky-Brée +# All Rights Reserved. +# +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# Build version: @GLUE_BUILD_VERSION@ + +PROG=`basename $0` + +: ${HA_DATEFMT=+"%b %d %T"} +: ${HA_LOGD=yes} +: ${HA_LOGTAG=""} +: ${HA_LOGFACILITY=daemon} +: ${HA_LOGFILE=""} +: ${HA_DEBUGLOG=""} +: ${HA_debug="0"} + +hadate() { + date "+$HA_DATEFMT" +} + +level_pres() { + case "$1" in + crit) echo "CRIT";; + err|error) echo "ERROR";; + warn|warning) echo "WARN";; + notice) echo "notice";; + info) echo "info";; + debug) echo "debug";; + *) + ha_log err "$PROG: unrecognized loglevel: $1" + exit 1 + ;; + esac +} + +set_logtag() { + # add parent pid to the logtag + if [ "$HA_LOGTAG" ]; then + if [ -n "$CRM_meta_st_device_id" ]; then + HA_LOGTAG="$HA_LOGTAG($CRM_meta_st_device_id)[$PPID]" + else + HA_LOGTAG="$HA_LOGTAG[$PPID]" + fi + fi +} + +ha_log() { + loglevel=$1 + shift + prn_level=`level_pres $loglevel` + msg="$prn_level: $@" + + if [ "x$HA_debug" = "x0" -a "x$loglevel" = xdebug ] ; then + return 0 + fi + + set_logtag + + # if we're connected to a tty, then output to stderr + if tty >/dev/null; then + if [ "$HA_LOGTAG" ]; then + echo "$HA_LOGTAG: $msg" + else + echo "$msg" + fi >&2 + return 0 + fi + + [ "x$HA_LOGD" = "xyes" ] && + cat<> $dest + fi +} + +if [ $# -lt 2 ]; then + ha_log err "$PROG: not enough arguments [$#]" + exit 1 +fi + +loglevel="$1" +shift 1 +msg="$*" + +ha_log "$loglevel" "$msg" diff --git a/lib/stonith/main.c b/lib/stonith/main.c new file mode 100644 index 0000000..44e099f --- /dev/null +++ b/lib/stonith/main.c @@ -0,0 +1,727 @@ +/* + * Stonith: simple test program for exercising the Stonith API code + * + * Copyright (C) 2000 Alan Robertson + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OPTIONS "c:F:p:t:T:EsnSlLmvhVd" +#define EQUAL '=' + +extern char * optarg; +extern int optind, opterr, optopt; + +static int debug = 0; + +#define LOG_TERMINAL 0 +#define LOG_CLLOG 1 +static int log_destination = LOG_TERMINAL; + +static const char META_TEMPLATE[] = +"\n" +"\n" +"\n" +"1.0\n" +"\n" +"%s\n" +"\n" +"%s\n" +"%s\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"2.0\n" +"\n" +"\n"; + +void version(void); +void usage(const char * cmd, int exit_status, const char * devtype); +void confhelp(const char * cmd, FILE* stream, const char * devtype); +void print_stonith_meta(Stonith * stonith_obj, const char *rsc_type); +void print_types(void); +void print_confignames(Stonith *s); + +void log_buf(int severity, char *buf); +void log_msg(int severity, const char * fmt, ...)G_GNUC_PRINTF(2,3); +void trans_log(int priority, const char * fmt, ...)G_GNUC_PRINTF(2,3); + +static int pil_loglevel_to_syslog_severity[] = { + /* Indices: =0, PIL_FATAL=1, PIL_CRIT=2, PIL_WARN=3, + PIL_INFO=4, PIL_DEBUG=5 + */ + LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_WARNING, LOG_INFO, LOG_DEBUG + }; + +/* + * Note that we don't use the cl_log logging code because the STONITH + * command is intended to be shipped without the clplumbing libraries. + * + * :-( + * + * The stonith command has so far always been shipped along with + * the clplumbing library, so we'll use cl_log + * If that ever changes, we'll use something else + */ + +void +version() +{ + printf("stonith: %s (%s)\n", GLUE_VERSION, GLUE_BUILD_VERSION); + exit(0); +} + +void +usage(const char * cmd, int exit_status, const char * devtype) +{ + FILE *stream; + + stream = exit_status ? stderr : stdout; + + /* non-NULL devtype indicates help for specific device, so no usage */ + if (devtype == NULL) { + fprintf(stream, "usage:\n"); + fprintf(stream, "\t %s [-svh] " + "-L\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "-n\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "-m\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "{-p stonith-device-parameters | " + "-F stonith-device-parameters-file | " + "-E | " + "name=value...} " + "[-c count] " + "-lS\n" + , cmd); + + fprintf(stream, "\t %s [-svh] " + "-t stonith-device-type " + "{-p stonith-device-parameters | " + "-F stonith-device-parameters-file | " + "-E | " + "name=value...} " + "[-c count] " + "-T {reset|on|off} nodename\n" + , cmd); + + fprintf(stream, "\nwhere:\n"); + fprintf(stream, "\t-L\tlist supported stonith device types\n"); + fprintf(stream, "\t-l\tlist hosts controlled by this stonith device\n"); + fprintf(stream, "\t-S\treport stonith device status\n"); + fprintf(stream, "\t-s\tsilent\n"); + fprintf(stream, "\t-v\tverbose\n"); + fprintf(stream, "\t-n\toutput the config names of stonith-device-parameters\n"); + fprintf(stream, "\t-m\tdisplay meta-data of the stonith device type\n"); + fprintf(stream, "\t-h\tdisplay detailed help message with stonith device description(s)\n"); + } + + if (exit_status == 0) { + confhelp(cmd, stream, devtype); + } + + exit(exit_status); +} + +/* Thanks to Lorn Kay for the confhelp code */ +void +confhelp(const char * cmd, FILE* stream, const char * devtype) +{ + char ** typelist; + char ** this; + Stonith * s; + int devfound = 0; + + + /* non-NULL devtype indicates help for specific device, so no header */ + if (devtype == NULL) { + fprintf(stream + , "\nSTONITH -t device types and" + " associated configuration details:\n"); + } + + typelist = stonith_types(); + + if (typelist == NULL) { + fprintf(stderr, + "Failed to retrieve list of STONITH modules!\n"); + return; + } + for(this=typelist; *this && !devfound; ++this) { + const char * SwitchType = *this; + const char * cres; + const char * const * pnames; + + + if ((s = stonith_new(SwitchType)) == NULL) { + fprintf(stderr, "Invalid STONITH type %s(!)\n" + , SwitchType); + continue; + } + + if (devtype) { + if (strcmp(devtype, SwitchType)) { + continue; + } else { + devfound = 1; + } + } + + fprintf(stream, "\n\nSTONITH Device: %s - ", SwitchType); + + if ((cres = stonith_get_info(s, ST_DEVICEDESCR)) != NULL){ + fprintf(stream, "%s\n" + , cres); + } + + if ((cres = stonith_get_info(s, ST_DEVICEURL)) != NULL){ + fprintf(stream + , "For more information see %s\n" + , cres); + } + if (NULL == (pnames = stonith_get_confignames(s))) { + continue; + } + fprintf(stream + , "List of valid parameter names for %s STONITH device:\n" + , SwitchType); + for (;*pnames; ++pnames) { + fprintf(stream + , "\t%s\n", *pnames); + } + +#ifdef ST_CONFI_INFO_SYNTAX + fprintf(stream, "\nConfig info [-p] syntax for %s:\n\t%s\n" + , SwitchType, stonith_get_info(s, ST_CONF_INFO_SYNTAX)); +#else + fprintf(stream, "For Config info [-p] syntax" + ", give each of the above parameters in order as" + "\nthe -p value.\n" + "Arguments are separated by white space."); +#endif +#ifdef ST_CONFI_FILE_SYNTAX + fprintf(stream, "\nConfig file [-F] syntax for %s:\n\t%s\n" + , SwitchType, stonith->get_info(s, ST_CONF_FILE_SYNTAX)); +#else + fprintf(stream + , "\nConfig file [-F] syntax is the same as -p" + ", except # at the start of a line" + "\ndenotes a comment\n"); +#endif + + stonith_delete(s); s = NULL; + } + /* Note that the type list can't/shouldn't be freed */ + if (devtype && !devfound) { + fprintf(stderr, "Invalid device type: '%s'\n", devtype); + } + +} + +void +print_stonith_meta(Stonith * stonith_obj, const char *rsc_type) +{ + const char * meta_param = NULL; + const char * meta_longdesc = NULL; + const char * meta_shortdesc = NULL; + char *xml_meta_longdesc = NULL; + char *xml_meta_shortdesc = NULL; + static const char * no_parameter_info = ""; + + meta_longdesc = stonith_get_info(stonith_obj, ST_DEVICEDESCR); + if (meta_longdesc == NULL) { + fprintf(stderr, "stonithRA plugin: no long description"); + meta_longdesc = no_parameter_info; + } + xml_meta_longdesc = (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_longdesc); + + meta_shortdesc = stonith_get_info(stonith_obj, ST_DEVICEID); + if (meta_shortdesc == NULL) { + fprintf(stderr, "stonithRA plugin: no short description"); + meta_shortdesc = no_parameter_info; + } + xml_meta_shortdesc = (char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_shortdesc); + + meta_param = stonith_get_info(stonith_obj, ST_CONF_XML); + if (meta_param == NULL) { + fprintf(stderr, "stonithRA plugin: no list of parameters"); + meta_param = no_parameter_info; + } + + printf(META_TEMPLATE, + rsc_type, xml_meta_longdesc, xml_meta_shortdesc, meta_param); + + xmlFree(xml_meta_longdesc); + xmlFree(xml_meta_shortdesc); +} + +#define MAXNVARG 50 + +void +print_types() +{ + char ** typelist; + + typelist = stonith_types(); + if (typelist == NULL) { + log_msg(LOG_ERR, "Could not list Stonith types."); + }else{ + char ** this; + + for(this=typelist; *this; ++this) { + printf("%s\n", *this); + } + } +} + +void +print_confignames(Stonith *s) +{ + const char * const * names; + int i; + + names = stonith_get_confignames(s); + + if (names != NULL) { + for (i=0; names[i]; ++i) { + printf("%s ", names[i]); + } + } + printf("\n"); +} + +void +log_buf(int severity, char *buf) +{ + if (severity == LOG_DEBUG && !debug) + return; + if (log_destination == LOG_TERMINAL) { + fprintf(stderr, "%s: %s\n", prio2str(severity),buf); + } else { + cl_log(severity, "%s", buf); + } +} + +void +log_msg(int severity, const char * fmt, ...) +{ + va_list ap; + char buf[MAXLINE]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf)-1, fmt, ap); + va_end(ap); + log_buf(severity, buf); +} + +void +trans_log(int priority, const char * fmt, ...) +{ + int severity; + va_list ap; + char buf[MAXLINE]; + + severity = pil_loglevel_to_syslog_severity[ priority % sizeof + (pil_loglevel_to_syslog_severity) ]; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf)-1, fmt, ap); + va_end(ap); + log_buf(severity, buf); +} + +int +main(int argc, char** argv) +{ + char * cmdname; + int rc; + Stonith * s; + const char * SwitchType = NULL; + const char * optfile = NULL; + const char * parameters = NULL; + int reset_type = ST_GENERIC_RESET; + int verbose = 0; + int status = 0; + int silent = 0; + int listhosts = 0; + int listtypes = 0; + int listparanames = 0; + int params_from_env = 0; + + int c; + int errors = 0; + int argcount; + StonithNVpair nvargs[MAXNVARG]; + int nvcount=0; + int j; + int count = 1; + int help = 0; + int metadata = 0; + + /* The bladehpi stonith plugin makes use of openhpi which is + * threaded. The mix of memory allocation without thread + * initialization followed by g_thread_init followed by + * deallocating that memory results in segfault. Hence the + * following G_SLICE setting; see + * http://library.gnome.org/devel/glib/stable/glib-Memory-Slices.html#g-slice-alloc + */ + + setenv("G_SLICE", "always-malloc", 1); + + if ((cmdname = strrchr(argv[0], '/')) == NULL) { + cmdname = argv[0]; + }else{ + ++cmdname; + } + + + while ((c = getopt(argc, argv, OPTIONS)) != -1) { + switch(c) { + + case 'c': count = atoi(optarg); + if (count < 1) { + fprintf(stderr + , "bad count [%s]\n" + , optarg); + usage(cmdname, 1, NULL); + } + break; + + case 'd': debug++; + break; + + case 'F': optfile = optarg; + break; + + case 'E': params_from_env = 1; + break; + + case 'h': help++; + break; + + case 'm': metadata++; + break; + + case 'l': ++listhosts; + break; + + case 'L': ++listtypes; + break; + + case 'p': parameters = optarg; + break; + + case 's': ++silent; + break; + + case 'S': ++status; + break; + + case 't': SwitchType = optarg; + break; + + case 'T': if (strcmp(optarg, "on")== 0) { + reset_type = ST_POWERON; + }else if (strcmp(optarg, "off")== 0) { + reset_type = ST_POWEROFF; + }else if (strcmp(optarg, "reset")== 0) { + reset_type = ST_GENERIC_RESET; + }else{ + fprintf(stderr + , "bad reset type [%s]\n" + , optarg); + usage(cmdname, 1, NULL); + } + break; + + case 'n': ++listparanames; + break; + + case 'v': ++verbose; + break; + + case 'V': version(); + break; + + default: ++errors; + break; + } + } + + /* if we're invoked by stonithd, log through cl_log */ + if (!isatty(fileno(stdin))) { + log_destination = LOG_CLLOG; + cl_log_set_entity("stonith"); + cl_log_enable_stderr(debug?TRUE:FALSE); + cl_log_set_facility(HA_LOG_FACILITY); + + /* Use logd if it's enabled by heartbeat */ + cl_inherit_logging_environment(0); + } + + if (help && !errors) { + usage(cmdname, 0, SwitchType); + } + if (debug) { + PILpisysSetDebugLevel(debug); + setenv("HA_debug","2",0); + } + if ((optfile && parameters) || (optfile && params_from_env) + || (params_from_env && parameters)) { + fprintf(stderr + , "Please use just one of -F, -p, and -E options\n"); + usage(cmdname, 1, NULL); + } + + /* + * Process name=value arguments on command line... + */ + for (;optind < argc; ++optind) { + char * eqpos; + if ((eqpos=strchr(argv[optind], EQUAL)) == NULL) { + break; + } + if (parameters || optfile || params_from_env) { + fprintf(stderr + , "Cannot mix name=value and -p, -F, or -E " + "style arguments\n"); + usage(cmdname, 1, NULL); + } + if (nvcount >= MAXNVARG) { + fprintf(stderr + , "Too many name=value style arguments\n"); + exit(1); + } + nvargs[nvcount].s_name = argv[optind]; + *eqpos = EOS; + nvargs[nvcount].s_value = eqpos+1; + nvcount++; + } + nvargs[nvcount].s_name = NULL; + nvargs[nvcount].s_value = NULL; + + argcount = argc - optind; + + if (!(argcount == 1 || (argcount < 1 + && (status||listhosts||listtypes||listparanames||metadata)))) { + ++errors; + } + + if (errors) { + usage(cmdname, 1, NULL); + } + + if (listtypes) { + print_types(); + exit(0); + } + + if (SwitchType == NULL) { + log_msg(LOG_ERR,"Must specify device type (-t option)"); + usage(cmdname, 1, NULL); + } + s = stonith_new(SwitchType); + if (s == NULL) { + log_msg(LOG_ERR,"Invalid device type: '%s'", SwitchType); + exit(S_OOPS); + } + if (debug) { + stonith_set_debug(s, debug); + } + stonith_set_log(s, (PILLogFun)trans_log); + + if (!listparanames && !metadata && optfile == NULL && + parameters == NULL && !params_from_env && nvcount == 0) { + const char * const * names; + int needs_parms = 1; + + if (s != NULL && (names = stonith_get_confignames(s)) != NULL && names[0] == NULL) { + needs_parms = 0; + } + + if (needs_parms) { + fprintf(stderr + , "Must specify either -p option, -F option, -E option, or " + "name=value style arguments\n"); + if (s != NULL) { + stonith_delete(s); + } + usage(cmdname, 1, NULL); + } + } + + if (listparanames) { + print_confignames(s); + stonith_delete(s); + s=NULL; + exit(0); + } + + if (metadata) { + print_stonith_meta(s,SwitchType); + stonith_delete(s); + s=NULL; + exit(0); + } + + /* Old STONITH version 1 stuff... */ + if (optfile) { + /* Configure the Stonith object from a file */ + if ((rc=stonith_set_config_file(s, optfile)) != S_OK) { + log_msg(LOG_ERR + , "Invalid config file for %s device." + , SwitchType); +#if 0 + log_msg(LOG_INFO, "Config file syntax: %s" + , s->s_ops->getinfo(s, ST_CONF_FILE_SYNTAX)); +#endif + stonith_delete(s); s=NULL; + exit(S_BADCONFIG); + } + }else if (params_from_env) { + /* Configure Stonith object from the environment */ + StonithNVpair * pairs; + if ((pairs = stonith_env_to_NVpair(s)) == NULL) { + fprintf(stderr + , "Invalid config info for %s device.\n" + , SwitchType); + stonith_delete(s); s=NULL; + exit(1); + } + if ((rc = stonith_set_config(s, pairs)) != S_OK) { + fprintf(stderr + , "Invalid config info for %s device\n" + , SwitchType); + } + }else if (parameters) { + /* Configure Stonith object from the -p argument */ + StonithNVpair * pairs; + if ((pairs = stonith1_compat_string_to_NVpair + ( s, parameters)) == NULL) { + fprintf(stderr + , "Invalid STONITH -p parameter [%s]\n" + , parameters); + stonith_delete(s); s=NULL; + exit(1); + } + if ((rc = stonith_set_config(s, pairs)) != S_OK) { + fprintf(stderr + , "Invalid config info for %s device\n" + , SwitchType); + } + }else{ + /* + * Configure STONITH device using cmdline arguments... + */ + if ((rc = stonith_set_config(s, nvargs)) != S_OK) { + const char * const * names; + int j; + fprintf(stderr + , "Invalid config info for %s device\n" + , SwitchType); + + names = stonith_get_confignames(s); + + if (names != NULL) { + fprintf(stderr + , "Valid config names are:\n"); + + for (j=0; names[j]; ++j) { + fprintf(stderr + , "\t%s\n", names[j]); + } + } + stonith_delete(s); s=NULL; + exit(rc); + } + } + + + for (j=0; j < count; ++j) { + rc = S_OK; + + if (status) { + rc = stonith_get_status(s); + + if (!silent) { + if (rc == S_OK) { + log_msg((log_destination == LOG_TERMINAL) ? + LOG_INFO : LOG_DEBUG, + "%s device OK.", SwitchType); + }else{ + /* Uh-Oh */ + log_msg(LOG_ERR, "%s device not accessible." + , SwitchType); + } + } + } + + if (listhosts) { + char ** hostlist; + + hostlist = stonith_get_hostlist(s); + if (hostlist == NULL) { + log_msg(LOG_ERR, "Could not list hosts for %s." + , SwitchType); + rc = -1; + }else{ + char ** this; + + for(this=hostlist; *this; ++this) { + printf("%s\n", *this); + } + stonith_free_hostlist(hostlist); + } + } + + if (optind < argc) { + char *nodename; + nodename = g_strdup(argv[optind]); + strdown(nodename); + rc = stonith_req_reset(s, reset_type, nodename); + g_free(nodename); + } + } + stonith_delete(s); s = NULL; + return(rc); +} diff --git a/lib/stonith/meatclient.c b/lib/stonith/meatclient.c new file mode 100644 index 0000000..e95dc0e --- /dev/null +++ b/lib/stonith/meatclient.c @@ -0,0 +1,152 @@ +/* + * Stonith client for Human Operator Stonith device + * + * Copyright (c) 2001 Gregor Binder + * + * This program is a rewrite of the "do_meatware" program by + * David C. Teigland originally appeared + * in the GFS stomith meatware agent. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OPTIONS "c:w" + +void usage(const char * cmd); + +void +usage(const char * cmd) +{ + fprintf(stderr, "usage: %s -c node [-w]\n", cmd); + exit(S_INVAL); +} + +extern char * optarg; +extern int optind, opterr, optopt; +int +main(int argc, char** argv) +{ + char * cmdname; + const char * meatpipe_pr = HA_VARRUNDIR "/meatware"; /* if you intend to + change this, modify + meatware.c as well */ + char * opthost = NULL; + int clearhost = 0; + + int c, argcount, waitmode = 0; + int errors = 0; + + if ((cmdname = strrchr(argv[0], '/')) == NULL) { + cmdname = argv[0]; + }else{ + ++cmdname; + } + + while ((c = getopt(argc, argv, OPTIONS)) != -1) { + switch(c) { + case 'c': opthost = optarg; + ++clearhost; + break; + case 'w': ++waitmode; + break; + default: ++errors; + break; + } + } + argcount = argc - optind; + if (!(argcount == 0) || !opthost) { + errors++; + } + + if (errors) { + usage(cmdname); + } + + strdown(opthost); + + if (clearhost) { + + int rc, fd; + char resp[3]; + + char line[256]; + char meatpipe[256]; + + gboolean waited=FALSE; + + snprintf(meatpipe, 256, "%s.%s", meatpipe_pr, opthost); + + while(1) { + fd = open(meatpipe, O_WRONLY | O_NONBLOCK); + if (fd >= 0) + break; + if (!waitmode || (errno != ENOENT && errno != ENXIO)) { + if (waited) printf("\n"); + snprintf(line, sizeof(line) + , "Meatware_IPC failed: %s", meatpipe); + perror(line); + exit(S_BADHOST); + } + printf("."); fflush(stdout); waited=TRUE; + sleep(1); + } + if (waited) printf("\n"); + + printf("\nWARNING!\n\n" + "If node \"%s\" has not been manually power-cycled or " + "disconnected from all shared resources and networks, " + "data on shared disks may become corrupted and " + "migrated services might not work as expected.\n" + "Please verify that the name or address above " + "corresponds to the node you just rebooted.\n\n" + "PROCEED? [yN] ", opthost); + + rc = scanf("%s", resp); + + if (rc == 0 || rc == EOF || tolower(resp[0] != 'y')) { + printf("Meatware_client: operation canceled.\n"); + exit(S_INVAL); + } + + sprintf(line, "meatware reply %s", opthost); + + rc = write(fd, line, 256); + + if (rc < 0) { + sprintf(line, "Meatware_IPC failed: %s", meatpipe); + perror(line); + exit(S_OOPS); + } + + printf("Meatware_client: reset confirmed.\n"); + } + + exit(S_OK); +} diff --git a/lib/stonith/st_ttylock.c b/lib/stonith/st_ttylock.c new file mode 100644 index 0000000..adc918d --- /dev/null +++ b/lib/stonith/st_ttylock.c @@ -0,0 +1,225 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The following information is from the Filesystem Hierarchy Standard + * version 2.1 dated 12 April, 2000. + * + * 5.6 /var/lock : Lock files + * Lock files should be stored within the /var/lock directory structure. + * Device lock files, such as the serial device lock files that were originally + * found in either /usr/spool/locks or /usr/spool/uucp, must now be stored in + * /var/lock. The naming convention which must be used is LCK.. followed by + * the base name of the device file. For example, to lock /dev/cua0 the file + * LCK..cua0 would be created. + * + * The format used for device lock files must be the HDB UUCP lock file format. + * The HDB format is to store the process identifier (PID) as a ten byte + * ASCII decimal number, with a trailing newline. For example, if process 1230 + * holds a lock file, it would contain the eleven characters: space, space, + * space, space, space, space, one, two, three, zero, and newline. + * Then, anything wishing to use /dev/cua0 can read the lock file and act + * accordingly (all locks in /var/lock should be world-readable). + * + * + * PERMISSIONS NOTE: + * Different linux distributions set the mode of the lock directory differently + * Any process which wants to create lock files must have write permissions + * on HA_VARLOCKDIR (probably /var/lock). For things like the heartbeat API + * code, this may mean allowing the uid of the processes that use this API + * to join group uucp, or making the binaries setgid to uucp. + */ + +#define DEVDIR "/dev/" +#define DEVLEN (sizeof(DEVDIR)-1) + +static void raw_device (const char *dev, char *dest_name, size_t size); +static int DoLock(const char * prefix, const char *lockname); +static int DoUnlock(const char * prefix, const char *lockname); + +/* The code in this file originally written by Guenther Thomsen */ +/* Somewhat mangled by Alan Robertson */ + +/* + * Lock a tty (using lock files, see linux `man 2 open` close to O_EXCL) + * serial_device has to be _the complete path_, i.e. including '/dev/' to the + * special file, which denotes the tty to lock -tho + * return 0 on success, + * -1 if device is locked (lockfile exists and isn't stale), + * -2 for temporarily failure, try again, + * other negative value, if something unexpected happend (failure anyway) + */ + +static void +raw_device (const char *serial_device, char *dest_name, size_t size) +{ + char* dp = dest_name; + const char* sp = serial_device+DEVLEN; + const char* dpend = dp + size - 1; + + while (*sp != '\0' && dp < dpend) { + if (isalnum((unsigned int)*sp)) + *dp++ = *sp; + sp++; + } + *dp = EOS; +} + +int +st_ttylock(const char *serial_device) +{ + char rawname[64]; + + if (serial_device == NULL) { + errno = EFAULT; + return -3; + } + raw_device (serial_device, rawname, sizeof(rawname)); + return(DoLock("LCK..", rawname)); +} + +/* + * Unlock a tty (remove its lockfile) + * do we need to check, if its (still) ours? No, IMHO, if someone else + * locked our line, it's his fault -tho + * returns 0 on success + * <0 if some failure occured + */ + +int +st_ttyunlock(const char *serial_device) +{ + char rawname[64]; + + if (serial_device == NULL) { + errno = EFAULT; + return -3; + } + + raw_device (serial_device, rawname, sizeof(rawname)); + return(DoUnlock("LCK..", rawname)); +} + +/* This is what the FHS standard specifies for the size of our lock file */ +#define LOCKSTRLEN 11 + +static int +DoLock(const char * prefix, const char *lockname) +{ + char lf_name[256], tf_name[256], buf[LOCKSTRLEN+1]; + int fd; + long pid, mypid; + int rc; + struct stat sbuf; + + mypid = (unsigned long) getpid(); + + snprintf(lf_name, sizeof(lf_name), "%s/%s%s" + , HA_VARLOCKDIR, prefix, lockname); + + snprintf(tf_name, sizeof(tf_name), "%s/tmp%lu-%s" + , HA_VARLOCKDIR, mypid, lockname); + + if ((fd = open(lf_name, O_RDONLY)) >= 0) { + if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) { + sleep(1); /* if someone was about to create one, + * give'm a sec to do so + * Though if they follow our protocol, + * this won't happen. They should really + * put the pid in, then link, not the + * other way around. + */ + } + if (read(fd, buf, sizeof(buf)) < 1) { + /* lockfile empty -> rm it and go on */; + } else { + if (sscanf(buf, "%lu", &pid) < 1) { + /* lockfile screwed up -> rm it and go on */ + } else { + if (pid > 1 && ((long)getpid() != pid) + && ((CL_KILL((pid_t)pid, 0) >= 0) + || errno != ESRCH)) { + /* tty is locked by existing (not + * necessarily running) process + * -> give up */ + close(fd); + return -1; + } else { + /* stale lockfile -> rm it and go on */ + } + } + } + unlink(lf_name); + } + if ((fd = open(tf_name, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) { + /* Hmmh, why did we fail? Anyway, nothing we can do about it */ + return -3; + } + + /* Slight overkill with the %*d format ;-) */ + snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN-1, mypid); + + if (write(fd, buf, LOCKSTRLEN) != LOCKSTRLEN) { + /* Again, nothing we can do about this */ + return -3; + } + close(fd); + + switch (link(tf_name, lf_name)) { + case 0: + if (stat(tf_name, &sbuf) < 0) { + /* something weird happened */ + rc = -3; + break; + } + if (sbuf.st_nlink < 2) { + /* somehow, it didn't get through - NFS trouble? */ + rc = -2; + break; + } + rc = 0; + break; + case EEXIST: + rc = -1; + break; + default: + rc = -3; + } + unlink(tf_name); + return rc; +} + +static int +DoUnlock(const char * prefix, const char *lockname) +{ + char lf_name[256]; + + snprintf(lf_name, sizeof(lf_name), "%s/%s%s", HA_VARLOCKDIR + , prefix, lockname); + return unlink(lf_name); +} diff --git a/lib/stonith/stonith.c b/lib/stonith/stonith.c new file mode 100644 index 0000000..4ced8c7 --- /dev/null +++ b/lib/stonith/stonith.c @@ -0,0 +1,636 @@ +/* + * Stonith API infrastructure. + * + * Copyright (c) 2000 Alan Robertson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define ENABLE_PIL_DEFS_PRIVATE +#include +#include +#include +#include + + +#define MALLOC StonithPIsys->imports->alloc +#ifdef MALLOCT +# undef MALLOCT +#endif +#define MALLOCT(t) (t*)(MALLOC(sizeof(t))) +#define REALLOC StonithPIsys->imports->mrealloc +#define STRDUP StonithPIsys->imports->mstrdup +#define FREE(p) {StonithPIsys->imports->mfree(p); (p) = NULL;} + +#define LOG(args...) PILCallLog(StonithPIsys->imports->log, args) + +#define EXTPINAME_S "external" +#define RHCSPINAME_S "rhcs" + +PILPluginUniv* StonithPIsys = NULL; +static GHashTable* Splugins = NULL; +static int init_pluginsys(void); +extern StonithImports stonithimports; + +static PILGenericIfMgmtRqst Reqs[] = +{ + {STONITH_TYPE_S, &Splugins, &stonithimports, NULL, NULL}, + {NULL, NULL, NULL, NULL, NULL} +}; + +void PILpisysSetDebugLevel(int); +/* Initialize the plugin system... */ +static int +init_pluginsys(void) { + + if (StonithPIsys) { + return TRUE; + } + + + /* PILpisysSetDebugLevel(10); */ + StonithPIsys = NewPILPluginUniv(STONITH_MODULES); + + if (StonithPIsys) { + int rc = PILLoadPlugin(StonithPIsys, PI_IFMANAGER, "generic", Reqs); + if (rc != PIL_OK) { + fprintf(stderr, "generic plugin load failed: %d\n", rc); + DelPILPluginUniv(StonithPIsys); + StonithPIsys = NULL; + } + /*PILSetDebugLevel(StonithPIsys, PI_IFMANAGER, "generic", 10);*/ + }else{ + fprintf(stderr, "pi univ creation failed\n"); + } + return StonithPIsys != NULL; +} + +/* + * Create a new Stonith object of the requested type. + */ + +Stonith * +stonith_new(const char * type) +{ + StonithPlugin * sp = NULL; + struct stonith_ops* ops = NULL; + char * key; + char * subplugin; + char * typecopy; + + + if (!init_pluginsys()) { + return NULL; + } + + if ((typecopy = STRDUP(type)) == NULL) { + return NULL; + } + + if (((subplugin = strchr(typecopy, '/')) != NULL) && + (strncmp(EXTPINAME_S, typecopy, strlen(EXTPINAME_S)) == 0 || + strncmp(RHCSPINAME_S, typecopy, strlen(RHCSPINAME_S)) == 0)) { + *subplugin++ = 0; /* make two strings */ + } + + /* Look and see if it's already loaded... */ + + if (g_hash_table_lookup_extended(Splugins, typecopy + , (gpointer)&key, (gpointer)&ops)) { + /* Yes! Increment reference count */ + PILIncrIFRefCount(StonithPIsys, STONITH_TYPE_S, typecopy, 1); + + }else{ /* No. Try and load it... */ + if (PILLoadPlugin(StonithPIsys, STONITH_TYPE_S, typecopy, NULL) + != PIL_OK) { + FREE(typecopy); + return NULL; + } + + /* Look up the plugin in the Splugins table */ + if (!g_hash_table_lookup_extended(Splugins, typecopy + , (void*)&key, (void*)&ops)) { + /* OOPS! didn't find it(!?!)... */ + PILIncrIFRefCount(StonithPIsys, STONITH_TYPE_S + , typecopy, -1); + FREE(typecopy); + return NULL; + } + } + + if (ops != NULL) { + sp = ops->new((const char *)(subplugin)); + if (sp != NULL) { + sp->s.stype = STRDUP(typecopy); + } + } + + FREE(typecopy); + return sp ? (&sp->s) : NULL; +} + +static int +qsort_string_cmp(const void *a, const void *b) +{ + return(strcmp(*(const char * const *)a, *(const char * const *)b)); +} + +/* + * Return list of STONITH types valid in stonith_new() + */ + +static char ** +get_plugin_list(const char *pltype) +{ + char ** typelist = NULL; + const char * const *extPI; + const char * const *p; + int numextPI, i; + Stonith * ext; + + /* let the external plugin return a list */ + if ((ext = stonith_new(pltype)) == NULL) { + LOG(PIL_CRIT, "Cannot create new external " + "plugin object"); + return NULL; + } + if ((extPI = stonith_get_confignames(ext)) == NULL) { + /* don't complain if rhcs plugins are not installed */ + if (strcmp(pltype, "rhcs")) + LOG(PIL_INFO, "Cannot get %s plugin subplugins", pltype); + stonith_delete(ext); + return NULL; + } + + /* count the external plugins */ + for (numextPI = 0, p = extPI; *p; p++, numextPI++); + + typelist = (char **) + MALLOC((numextPI+1)*sizeof(char *)); + if (typelist == NULL) { + LOG(PIL_CRIT, "Out of memory"); + stonith_delete(ext); + return NULL; + } + + memset(typelist, 0, (numextPI + 1)*sizeof(char *)); + + /* copy external plugins */ + for (i = 0; i < numextPI; i++) { + int len = strlen(pltype) + + strlen(extPI[i]) + 2; + typelist[i] = MALLOC(len); + if (typelist[i] == NULL) { + LOG(PIL_CRIT, "Out of memory"); + stonith_delete(ext); + goto err; + } + snprintf(typelist[i], len, "%s/%s" + , pltype, extPI[i]); + } + + stonith_delete(ext); + + /* sort the list of plugin names */ + qsort(typelist, numextPI, sizeof(char *), qsort_string_cmp); + + return typelist; +err: + stonith_free_hostlist(typelist); + return NULL; +} + +char ** +stonith_types(void) +{ + int i, j, cur=0, rl_size, sub_pl = 0; + static char ** rl = NULL; + char ** new_list, **sub_list = NULL; + + if (!init_pluginsys()) { + return NULL; + } + + new_list = PILListPlugins(StonithPIsys, STONITH_TYPE_S, NULL); + if (new_list == NULL) { + return NULL; + } + for (i=0; new_list[i]; ++i) + ; /* count */ + rl_size = i+1; + + rl = (char**)MALLOC(rl_size * sizeof(char *)); + if (rl == NULL) { + LOG(PIL_CRIT, "Out of memory"); + goto types_exit; + } + + for (i=0; new_list[i]; ++i) { + /* look for 'external' and 'rhcs' plugins */ + if (strcmp(new_list[i], EXTPINAME_S) == 0) { + sub_list = get_plugin_list(EXTPINAME_S); + sub_pl = 1; + } else if (strcmp(new_list[i], RHCSPINAME_S) == 0) { + sub_list = get_plugin_list(RHCSPINAME_S); + sub_pl = 1; + } + if (sub_pl) { + if (sub_list) { + for (j=0; sub_list[j]; ++j) + ; /* count */ + rl_size += j; + rl = (char**)REALLOC(rl, rl_size*sizeof(char *)); + for (j=0; sub_list[j]; ++j) { + rl[cur++] = sub_list[j]; + } + FREE(sub_list); + sub_list = NULL; + } + sub_pl = 0; + } else { + rl[cur] = STRDUP(new_list[i]); + if (rl[cur] == NULL) { + LOG(PIL_CRIT, "Out of memory"); + goto types_exit_mem; + } + cur++; + } + } + + rl[cur] = NULL; + goto types_exit; + +types_exit_mem: + stonith_free_hostlist(rl); + rl = NULL; +types_exit: + PILFreePluginList(new_list); + return rl; +} + +/* Destroy the STONITH object... */ + +void +stonith_delete(Stonith *s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + char * st = sp->s.stype; + sp->s_ops->destroy(sp); + PILIncrIFRefCount(StonithPIsys, STONITH_TYPE_S, st, -1); + /* destroy should not free it */ + FREE(st); + } +} + +const char * const * +stonith_get_confignames(Stonith* s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + return sp->s_ops->get_confignames(sp); + } + return NULL; +} + +const char* +stonith_get_info(Stonith* s, int infotype) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + return sp->s_ops->get_info(sp, infotype); + } + return NULL; + +} + +void +stonith_set_debug (Stonith* s, int debuglevel) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (StonithPIsys == NULL) { + return; + } + PILSetDebugLevel(StonithPIsys, STONITH_TYPE_S, sp->s.stype, debuglevel); +} + +void +stonith_set_log(Stonith* s, PILLogFun logfun) +{ + if (StonithPIsys == NULL) { + return; + } + PilPluginUnivSetLog(StonithPIsys, logfun); +} + +int +stonith_set_config(Stonith* s, StonithNVpair* list) +{ + StonithPlugin* sp = (StonithPlugin*)s; + + if (sp && sp->s_ops) { + int rc = sp->s_ops->set_config(sp, list); + if (rc == S_OK) { + sp->isconfigured = TRUE; + } + return rc; + } + return S_INVAL; +} + +/* + * FIXME: We really ought to support files with name=value type syntax + * on each line... + * + */ +int +stonith_set_config_file(Stonith* s, const char * configname) +{ + FILE * cfgfile; + + char line[1024]; + + if ((cfgfile = fopen(configname, "r")) == NULL) { + LOG(PIL_CRIT, "Cannot open %s", configname); + return(S_BADCONFIG); + } + while (fgets(line, sizeof(line), cfgfile) != NULL){ + int len; + + if (*line == '#' || *line == '\n' || *line == EOS) { + continue; + } + + /*remove the new line in the end*/ + len = strnlen(line, sizeof(line)-1); + if (line[len-1] == '\n'){ + line[len-1] = '\0'; + }else{ + line[len] = '\0'; + } + + fclose(cfgfile); + return stonith_set_config_info(s, line); + } + fclose(cfgfile); + return S_BADCONFIG; +} + +int +stonith_set_config_info(Stonith* s, const char * info) +{ + StonithNVpair* cinfo; + int rc; + cinfo = stonith1_compat_string_to_NVpair(s, info); + if (cinfo == NULL) { + return S_BADCONFIG; + } + rc = stonith_set_config(s, cinfo); + free_NVpair(cinfo); cinfo = NULL; + return rc; +} + +char** +stonith_get_hostlist(Stonith* s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (sp && sp->s_ops && sp->isconfigured) { + return sp->s_ops->get_hostlist(sp); + } + return NULL; +} + +void +stonith_free_hostlist(char** hostlist) +{ + char ** here; + + for (here=hostlist; *here; ++here) { + FREE(*here); + } + FREE(hostlist); +} + +int +stonith_get_status(Stonith* s) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (sp && sp->s_ops && sp->isconfigured) { + return sp->s_ops->get_status(sp); + } + return S_INVAL; +} + +void +strdown(char *str) +{ + while( *str ) { + if( isupper(*str) ) + *str = tolower(*str); + str++; + } +} + +int +stonith_req_reset(Stonith* s, int operation, const char* node) +{ + StonithPlugin* sp = (StonithPlugin*)s; + if (sp && sp->s_ops && sp->isconfigured) { + char* nodecopy = STRDUP(node); + int rc; + if (nodecopy == NULL) { + return S_OOPS; + } + strdown(nodecopy); + + rc = sp->s_ops->req_reset(sp, operation, nodecopy); + FREE(nodecopy); + return rc; + } + return S_INVAL; +} +/* Stonith 1 compatibility: Convert a string to an NVpair set */ +StonithNVpair* +stonith1_compat_string_to_NVpair(Stonith* s, const char * str) +{ + /* We make some assumptions that the order of parameters in the + * result from stonith_get_confignames() matches that which + * was required from a Stonith1 module. + * Everything after the last delimiter is passed along as part of + * the final argument - white space and all... + */ + const char * const * config_names; + int n_names; + int j; + const char * delims = " \t\n\r\f"; + StonithNVpair* ret; + + if ((config_names = stonith_get_confignames(s)) == NULL) { + return NULL; + } + for (n_names=0; config_names[n_names] != NULL; ++n_names) { + /* Just count */; + } + ret = (StonithNVpair*) (MALLOC((n_names+1)*sizeof(StonithNVpair))); + if (ret == NULL) { + return NULL; + } + memset(ret, 0, (n_names+1)*sizeof(StonithNVpair)); + for (j=0; j < n_names; ++j) { + size_t len; + if ((ret[j].s_name = STRDUP(config_names[j])) == NULL) { + goto freeandexit; + } + ret[j].s_value = NULL; + str += strspn(str, delims); + if (*str == EOS) { + goto freeandexit; + } + if (j == (n_names -1)) { + len = strlen(str); + }else{ + len = strcspn(str, delims); + } + if ((ret[j].s_value = MALLOC((len+1)*sizeof(char))) == NULL) { + goto freeandexit; + } + memcpy(ret[j].s_value, str, len); + ret[j].s_value[len] = EOS; + str += len; + } + ret[j].s_name = NULL; + return ret; +freeandexit: + free_NVpair(ret); ret = NULL; + return NULL; +} + +StonithNVpair* +stonith_env_to_NVpair(Stonith* s) +{ + /* Read the config names values from the environment */ + const char * const * config_names; + int n_names; + int j; + StonithNVpair* ret; + + if ((config_names = stonith_get_confignames(s)) == NULL) { + return NULL; + } + for (n_names=0; config_names[n_names] != NULL; ++n_names) { + /* Just count */; + } + ret = (StonithNVpair*) (MALLOC((n_names+1)*sizeof(StonithNVpair))); + if (ret == NULL) { + return NULL; + } + memset(ret, 0, (n_names+1)*sizeof(StonithNVpair)); + for (j=0; j < n_names; ++j) { + char *env_value; + if ((ret[j].s_name = STRDUP(config_names[j])) == NULL) { + goto freeandexit; + } + env_value = getenv(config_names[j]); + if (env_value) { + if ((ret[j].s_value = STRDUP(env_value)) == NULL) { + goto freeandexit; + } + } else { + ret[j].s_value = NULL; + } + } + ret[j].s_name = NULL; + return ret; +freeandexit: + free_NVpair(ret); ret = NULL; + return NULL; +} + +static int NVcur = -1; +static int NVmax = -1; +static gboolean NVerr = FALSE; + +static void +stonith_walk_ghash(gpointer key, gpointer value, gpointer user_data) +{ + StonithNVpair* u = user_data; + + if (NVcur <= NVmax && !NVerr) { + u[NVcur].s_name = STRDUP(key); + u[NVcur].s_value = STRDUP(value); + if (u[NVcur].s_name == NULL || u[NVcur].s_value == NULL) { + /* Memory allocation error */ + NVerr = TRUE; + return; + } + ++NVcur; + }else{ + NVerr = TRUE; + } +} + + +StonithNVpair* +stonith_ghash_to_NVpair(GHashTable* stringtable) +{ + int hsize = g_hash_table_size(stringtable); + StonithNVpair* ret; + + if ((ret = (StonithNVpair*)MALLOC(sizeof(StonithNVpair)*(hsize+1))) == NULL) { + return NULL; + } + NVmax = hsize; + NVcur = 0; + ret[hsize].s_name = NULL; + ret[hsize].s_value = NULL; + g_hash_table_foreach(stringtable, stonith_walk_ghash, ret); + NVmax = NVcur = -1; + if (NVerr) { + free_NVpair(ret); + ret = NULL; + } + return ret; +} + +void +free_NVpair(StonithNVpair* nv) +{ + StonithNVpair* this; + + if (nv == NULL) { + return; + } + for (this=nv; this->s_name; ++this) { + FREE(this->s_name); + if (this->s_value) { + FREE(this->s_value); + } + } + FREE(nv); +} diff --git a/logd/Makefile.am b/logd/Makefile.am new file mode 100644 index 0000000..7ac75e2 --- /dev/null +++ b/logd/Makefile.am @@ -0,0 +1,56 @@ +# +# hbclient library: Linux-HA heartbeat code +# +# Copyright (C) 2001 Michael Moerz +# Copyright (C) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +halibdir = $(libdir)/@HB_PKG@ +ha_sbindir = $(sbindir) + +LIBRT = @LIBRT@ +AM_CFLAGS = @CFLAGS@ + +initddir = @INITDIR@ + +## binary progs +ha_sbin_PROGRAMS = ha_logger +halib_PROGRAMS = ha_logd logtest + +ha_logd_SOURCES = ha_logd.c +ha_logd_LDADD = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(top_builddir)/lib/clplumbing/libplumbgpl.la + +# $(top_builddir)/lib/apphb/libapphb.la + +ha_logger_SOURCES = ha_logger.c +ha_logger_LDADD = $(top_builddir)/lib/clplumbing/libplumb.la + +logtest_SOURCES = logtest.c +logtest_LDADD = $(top_builddir)/lib/clplumbing/libplumb.la + +if HAVE_SYSTEMD +systemdsystemunit_DATA = \ + logd.service +else +initd_SCRIPTS = logd +endif diff --git a/logd/ha_logd.c b/logd/ha_logd.c new file mode 100644 index 0000000..c98b9d7 --- /dev/null +++ b/logd/ha_logd.c @@ -0,0 +1,1085 @@ +/* + * ha_logd.c logging daemon + * + * Copyright (C) 2004 Guochun Shi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#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 + +/*two processes involved + 1. parent process which reads messages from all client channels + and writes them to the child process + + 2. the child process which reads messages from the parent process through IPC + and writes them to syslog/disk + + I call the parent process READ process, and the child process WRITE one, + for convenience. + +*/ + + + +#define DEFAULT_CFG_FILE HA_SYSCONFDIR "/logd.cf" +#define LOGD_PIDFILE HA_VARRUNDIR "/logd.pid" + +#define FD_STDIN 0 +#define FD_STDOUT 1 + +#define FD_STDERR 2 + + +#define WRITE_PROC_CHAN 0 +#define READ_PROC_CHAN 1 +#define LOGD_QUEUE_LEN 128 + +#define EOS '\0' +#define nullchk(a) ((a) ? (a) : "") + +static const int logd_keepalive_ms = 1000; +static const int logd_warntime_ms = 5000; +static const int logd_deadtime_ms = 10000; +static gboolean verbose = FALSE; +static pid_t write_process_pid; +static IPC_Channel *chanspair[2]; +static gboolean stop_reading = FALSE; +static gboolean needs_shutdown = FALSE; + +static struct { + char debugfile[MAXLINE]; + char logfile[MAXLINE]; + char entity[MAXENTITY]; + char syslogprefix[MAXENTITY]; + int log_facility; + mode_t logmode; + gboolean syslogfmtmsgs; +} logd_config = + { + .debugfile = "", + .logfile = "", + .entity = "logd", + .syslogprefix = "", + .log_facility = HA_LOG_FACILITY, + .logmode = 0644, + .syslogfmtmsgs = FALSE + }; + +static void logd_log(const char * fmt, ...) G_GNUC_PRINTF(1,2); +static int set_debugfile(const char* option); +static int set_logfile(const char* option); +static int set_facility(const char * value); +static int set_entity(const char * option); +static int set_syslogprefix(const char * option); +static int set_sendqlen(const char * option); +static int set_recvqlen(const char * option); +static int set_logmode(const char * option); +static int set_syslogfmtmsgs(const char * option); + + +static char* cmdname = NULL; + + +static struct directive { + const char* name; + int (*add_func)(const char*); +} Directives[] = { + {"debugfile", set_debugfile}, + {"logfile", set_logfile}, + {"logfacility", set_facility}, + {"entity", set_entity}, + {"syslogprefix",set_syslogprefix}, + {"sendqlen", set_sendqlen}, + {"recvqlen", set_recvqlen}, + {"logmode", set_logmode}, + {"syslogmsgfmt",set_syslogfmtmsgs} +}; + +static void +logd_log( const char * fmt, ...) +{ + char buf[MAXLINE]; + va_list ap; + + buf[MAXLINE-1] = EOS; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf)-1, fmt, ap); + va_end(ap); + + fprintf(stderr, "%s", buf); + + return; +} + +static int +set_debugfile(const char* option) +{ + if (!option){ + logd_config.debugfile[0] = EOS; + return FALSE; + } + + cl_log(LOG_INFO, "setting debug file to %s", option); + strncpy(logd_config.debugfile, option, MAXLINE); + return TRUE; +} +static int +set_logfile(const char* option) +{ + if (!option){ + logd_config.logfile[0] = EOS; + return FALSE; + } + cl_log(LOG_INFO, "setting log file to %s", option); + strncpy(logd_config.logfile, option, MAXLINE); + return TRUE; +} + +/* set syslog facility config variable */ +static int +set_facility(const char * value) +{ + int i; + + i = cl_syslogfac_str2int(value); + if (i >= 0) { + cl_log(LOG_INFO, "setting log facility to %s", value); + logd_config.log_facility = i; + return(TRUE); + } + else { + return(FALSE); + } +} + +static int +set_entity(const char * option) +{ + if (!option){ + logd_config.entity[0] = EOS; + return FALSE; + } + strncpy(logd_config.entity, option, MAXENTITY); + logd_config.entity[MAXENTITY-1] = '\0'; + if (strlen(option) >= MAXENTITY) + cl_log(LOG_WARNING, "setting entity to %s (truncated from %s)", + logd_config.entity, option); + else + cl_log(LOG_INFO, "setting entity to %s", logd_config.entity); + return TRUE; + +} + +static int +set_syslogprefix(const char * option) +{ + if (!option){ + logd_config.syslogprefix[0] = EOS; + return FALSE; + } + strncpy(logd_config.syslogprefix, option, MAXENTITY); + logd_config.syslogprefix[MAXENTITY-1] = '\0'; + if (strlen(option) >= MAXENTITY) + cl_log(LOG_WARNING, + "setting syslogprefix to %s (truncated from %s)", + logd_config.syslogprefix, option); + else + cl_log(LOG_INFO, + "setting syslogprefix to %s", + logd_config.syslogprefix); + return TRUE; + +} + +static int +set_sendqlen(const char * option) +{ + int length; + + if (!option){ + cl_log(LOG_ERR, "NULL send queue length"); + return FALSE; + } + + length = atoi(option); + if (length < 0){ + cl_log(LOG_ERR, "negative send queue length"); + return FALSE; + } + + cl_log(LOG_INFO, "setting send queue length to %d", length); + chanspair[READ_PROC_CHAN]->ops->set_send_qlen(chanspair[READ_PROC_CHAN], + length); + + return TRUE; + +} + +static int +set_recvqlen(const char * option) +{ + int length; + + if (!option){ + cl_log(LOG_ERR, "NULL recv queue length"); + return FALSE; + } + + length = atoi(option); + if (length < 0){ + cl_log(LOG_ERR, "negative recv queue length"); + return FALSE; + } + + cl_log(LOG_INFO, "setting recv queue length to %d", length); + chanspair[WRITE_PROC_CHAN]->ops->set_recv_qlen(chanspair[WRITE_PROC_CHAN], + length); + + return TRUE; + +} + +static int +set_logmode(const char * option) +{ + unsigned long mode; + char * endptr; + if (!option){ + cl_log(LOG_ERR, "NULL logmode parameter"); + return FALSE; + } + mode = strtoul(option, &endptr, 8); + if (*endptr != EOS) { + cl_log(LOG_ERR, "Invalid log mode [%s]", option); + return FALSE; + } + if (*option != '0') { + /* Whine if mode doesn't start with '0' */ + cl_log(LOG_WARNING, "Log mode [%s] assumed to be octal" + , option); + } + logd_config.logmode = (mode_t)mode; + return TRUE; +} +static int +set_syslogfmtmsgs(const char * option) +{ + gboolean dosyslogfmt; + + if (cl_str_to_boolean(option, &dosyslogfmt) == HA_OK) { + cl_log_enable_syslog_filefmt(dosyslogfmt); + }else{ + return FALSE; + } + return TRUE; +} + + +typedef struct { + char app_name[MAXENTITY]; + pid_t pid; + gid_t gid; + uid_t uid; + + IPC_Channel* chan; + IPC_Channel* logchan; + GCHSource* g_src; +}ha_logd_client_t; + +static GList* logd_client_list = NULL; + +static IPC_Message* +getIPCmsg(IPC_Channel* ch) +{ + + int rc; + IPC_Message* ipcmsg; + + /* FIXME: Should we block here?? */ + rc = ch->ops->waitin(ch); + + switch(rc) { + default: + case IPC_FAIL: + cl_log(LOG_ERR, "getIPCmsg: waitin failure\n"); + return NULL; + + case IPC_BROKEN: + sleep(1); + return NULL; + + case IPC_INTR: + return NULL; + + case IPC_OK: + break; + } + + ipcmsg = NULL; + rc = ch->ops->recv(ch, &ipcmsg); + if (rc != IPC_OK) { + return NULL; + } + + return ipcmsg; + +} + +/* Flow control all clients off */ +static void +logd_suspend_clients(IPC_Channel* notused1, gpointer notused2) +{ + GList * gl; + + stop_reading = TRUE; + for (gl=g_list_first(logd_client_list); gl != NULL + ; gl = g_list_next(gl)) { + ha_logd_client_t* client = gl->data; + if (client && client->g_src) { + G_main_IPC_Channel_pause(client->g_src); + }else if (client) { + cl_log(LOG_ERR, "Could not suspend client [%s] pid %d" + , nullchk(client->app_name), client->pid); + }else{ + cl_log(LOG_ERR, "%s: Could not suspend NULL client", + __FUNCTION__); + } + } +} + +/* Resume input from clients - Flow control all clients back on */ +static void +logd_resume_clients(IPC_Channel* notused1, gpointer notused2) +{ + GList * gl; + + stop_reading = FALSE; + for (gl=g_list_first(logd_client_list); gl != NULL + ; gl = g_list_next(gl)) { + ha_logd_client_t* client = gl->data; + if (client && client->g_src) { + G_main_IPC_Channel_resume(client->g_src); + }else if (client) { + cl_log(LOG_ERR, "Could not resume client [%s] pid %d" + , nullchk(client->app_name), client->pid); + }else{ + cl_log(LOG_ERR, "%s: Could not suspend NULL client", + __FUNCTION__); + } + } +} + +static gboolean +on_receive_cmd (IPC_Channel* ch, gpointer user_data) +{ + IPC_Message* ipcmsg; + ha_logd_client_t* client = (ha_logd_client_t*)user_data; + IPC_Channel* logchan= client->logchan; + + + if (!ch->ops->is_message_pending(ch)) { + goto getout; + } + + ipcmsg = getIPCmsg(ch); + if (ipcmsg == NULL){ + if (IPC_ISRCONN(ch)) { + cl_log(LOG_ERR, "%s: read error on connected channel [%s:%d]" + , __FUNCTION__, client->app_name, client->pid); + } + return FALSE; + } + + if( ipcmsg->msg_body && ipcmsg->msg_len > 0 ){ + + if (client->app_name[0] == '\0'){ + LogDaemonMsgHdr* logmsghdr; + logmsghdr = (LogDaemonMsgHdr*) ipcmsg->msg_body; + strncpy(client->app_name, logmsghdr->entity, MAXENTITY); + } + + if (!IPC_ISWCONN(logchan)){ + cl_log(LOG_ERR + , "%s: channel to write process disconnected" + , __FUNCTION__); + return FALSE; + } + if (logchan->ops->send(logchan, ipcmsg) != IPC_OK){ + cl_log(LOG_ERR + , "%s: forwarding msg from [%s:%d] to" + " write process failed" + , __FUNCTION__ + , client->app_name, client->pid); + cl_log(LOG_ERR, "queue too small? (max=%ld, current len =%ld)", + (long)logchan->send_queue->max_qlen, + (long)logchan->send_queue->current_qlen); + return TRUE; + } + + }else { + cl_log(LOG_ERR, "on_receive_cmd:" + " invalid ipcmsg\n"); + } + + getout: + return TRUE; +} + +static void +on_remove_client (gpointer user_data) +{ + + logd_client_list = g_list_remove(logd_client_list, user_data); + if (user_data){ + free(user_data); + } + return; +} + + + +/* + *GLoop Message Handlers + */ +static gboolean +on_connect_cmd (IPC_Channel* ch, gpointer user_data) +{ + ha_logd_client_t* client = NULL; + + /* check paremeters */ + if (NULL == ch) { + cl_log(LOG_ERR, "on_connect_cmd: channel is null"); + return TRUE; + } + /* create new client */ + if (NULL == (client = malloc(sizeof(ha_logd_client_t)))) { + return FALSE; + } + memset(client, 0, sizeof(ha_logd_client_t)); + client->pid = ch->farside_pid; + client->chan = ch; + client->logchan = (IPC_Channel*)user_data; + client->g_src = G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, + ch, FALSE, on_receive_cmd, + (gpointer)client, + on_remove_client); + if (client->g_src == NULL){ + cl_log(LOG_ERR, "add the client to main loop failed"); + free(client); + return TRUE; + } + if (stop_reading){ + G_main_IPC_Channel_pause(client->g_src); + } + + logd_client_list = g_list_append(logd_client_list, client); + + + return TRUE; +} + + + +static void +logd_make_daemon(gboolean daemonize) +{ + long pid; + + if (daemonize) { + if (daemon(0,0)) { + fprintf(stderr, "%s: could not start daemon\n" + , cmdname); + perror("fork"); + exit(LSB_EXIT_GENERIC); + } + } + + if (cl_lock_pidfile(LOGD_PIDFILE) < 0 ){ + pid = cl_read_pidfile(LOGD_PIDFILE); + if (pid > 0) + fprintf(stderr, "%s: already running [pid %ld].\n", + cmdname, pid); + else + fprintf(stderr, "%s: problem creating pid file %s\n", + cmdname, LOGD_PIDFILE); + exit(LSB_EXIT_OK); + } + + if (daemonize || !verbose){ + cl_log_enable_stderr(FALSE); + } + + if (daemonize){ + mode_t mask; + /* + * Some sample umask calculations: + * + * logmode = 0644 + * + * (~0644)&0777 = 0133 + * (0133 & ~0111) = 0022 + * => umask will be 022 (the expected result) + * + * logmode = 0600 + * (~0600)&0777 = 0177 + * (0177 & ~0111) = 0066 + */ + mask = (mode_t)(((~logd_config.logmode) & 0777) & (~0111)); + umask(mask); + } +} + + + +static void +logd_stop(void) +{ + + long running_logd_pid = cl_read_pidfile(LOGD_PIDFILE); + int err; + + if (running_logd_pid < 0) { + fprintf(stderr, "ha_logd already stopped.\n"); + cl_log(LOG_INFO, "ha_logd already stopped."); + exit(LSB_EXIT_OK); + } + + cl_log(LOG_DEBUG, "Stopping ha_logd with pid %ld", running_logd_pid); + if (kill((pid_t)running_logd_pid, SIGTERM) >= 0) { + /* Wait for the running logd to die */ + cl_log(LOG_INFO, "Waiting for pid=%ld to exit", + running_logd_pid); + alarm(0); + do { + sleep(1); + }while (kill((pid_t)running_logd_pid, 0) >= 0); + } + err = errno; + + if(errno == ESRCH) { + cl_log(LOG_INFO, "Pid %ld exited", running_logd_pid); + exit(LSB_EXIT_OK); + } else { + cl_perror("Pid %ld not killed", running_logd_pid); + exit((err == EPERM || err == EACCES) + ? LSB_EXIT_EPERM + : LSB_EXIT_GENERIC); + } + +} + + +static int +get_dir_index(const char* directive) +{ + int j; + for(j=0; j < DIMOF(Directives); j++){ + if (0 == strcasecmp(directive, Directives[j].name)){ + return j; + } + } + return -1; +} + + +/* Adapted from parse_config in config.c */ +static gboolean +parse_config(const char* cfgfile) +{ + FILE* f; + char buf[MAXLINE]; + char* bp; + char* cp; + char directive[MAXLINE]; + int dirlength; + int optionlength; + char option[MAXLINE]; + int dir_index; + + gboolean ret = TRUE; + + if ((f = fopen(cfgfile, "r")) == NULL){ + cl_log(LOG_WARNING, "Cannot open config file [%s]", cfgfile); + return(FALSE); + } + + while(fgets(buf, MAXLINE, f) != NULL){ + bp = buf; + /* Skip over white space*/ + bp += strspn(bp, " \t\n\r\f"); + + /* comments */ + if ((cp = strchr(bp, '#')) != NULL){ + *cp = EOS; + } + + if (*bp == EOS){ + continue; + } + + dirlength = strcspn(bp, " \t\n\f\r"); + strncpy(directive, bp, dirlength); + directive[dirlength] = EOS; + + if ((dir_index = get_dir_index(directive)) == -1){ + fprintf(stderr, "Illegal directive [%s] in %s\n" + , directive, cfgfile); + ret = FALSE; + continue; + } + + bp += dirlength; + + /* skip delimiters */ + bp += strspn(bp, " ,\t\n\f\r"); + + /* Set option */ + optionlength = strcspn(bp, " ,\t\n\f\r"); + strncpy(option, bp, optionlength); + option[optionlength] = EOS; + if (!(*Directives[dir_index].add_func)(option)) { + ret = FALSE; + } + }/*while*/ + fclose(f); + return ret; +} + +static gboolean +logd_term_action(int sig, gpointer userdata) +{ + GList *log_iter = logd_client_list; + GMainLoop *mainloop = (GMainLoop*)userdata; + ha_logd_client_t *client = NULL; + + cl_log(LOG_DEBUG, "logd_term_action: received SIGTERM"); + if (mainloop == NULL){ + cl_log(LOG_ERR, "logd_term_action: invalid arguments"); + return FALSE; + } + + stop_reading = TRUE; + + while(log_iter != NULL) { + client = log_iter->data; + log_iter = log_iter->next; + + cl_log(LOG_DEBUG, "logd_term_action:" + " waiting for %d messages to be read for process %s", + (int)client->logchan->send_queue->current_qlen, + client->app_name); + + client->logchan->ops->waitout(client->logchan); + } + + cl_log(LOG_DEBUG, "logd_term_action" + ": waiting for %d messages to be read by write process" + , (int)chanspair[WRITE_PROC_CHAN]->send_queue->current_qlen); + chanspair[WRITE_PROC_CHAN]->ops->waitout(chanspair[WRITE_PROC_CHAN]); + + cl_log(LOG_DEBUG, "logd_term_action: sending SIGTERM to write process"); + if (CL_KILL(write_process_pid, SIGTERM) >= 0){ + + pid_t pid; + pid = wait4(write_process_pid, NULL, 0, NULL); + if (pid < 0){ + cl_log(LOG_ERR, "wait4 for write process failed"); + } + + } + + g_main_quit(mainloop); + + return TRUE; +} + +/* + * Handle SIGHUP to re-open log files + */ +static gboolean +logd_hup_action(int sig, gpointer userdata) +{ + cl_log_close_log_files(); + if (write_process_pid) + /* do we want to propagate the HUP, + * or do we assume that it was a killall anyways? */ + CL_KILL(write_process_pid, SIGHUP); + else + cl_log(LOG_INFO, "SIGHUP received, re-opened log files"); + return TRUE; +} + +static void +read_msg_process(IPC_Channel* chan) +{ + GHashTable* conn_cmd_attrs; + IPC_WaitConnection* conn_cmd = NULL; + char path[] = "path"; + char socketpath[] = HA_LOGDAEMON_IPC; + GMainLoop* mainloop; + + + + mainloop = g_main_new(FALSE); + + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGTERM, + logd_term_action,mainloop, NULL); + + conn_cmd_attrs = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(conn_cmd_attrs, path, socketpath); + + conn_cmd = ipc_wait_conn_constructor(IPC_ANYTYPE, conn_cmd_attrs); + g_hash_table_destroy(conn_cmd_attrs); + + if (conn_cmd == NULL){ + fprintf(stderr, "ERROR: create waiting connection failed"); + exit(1); + } + + /*Create a source to handle new connect rquests for command*/ + G_main_add_IPC_WaitConnection( G_PRIORITY_HIGH, conn_cmd, NULL, FALSE + , on_connect_cmd, chan, NULL); + chan->ops->set_high_flow_callback(chan, logd_suspend_clients, NULL); + chan->ops->set_low_flow_callback(chan, logd_resume_clients, NULL); + chan->high_flow_mark = chan->send_queue->max_qlen; + chan->low_flow_mark = (chan->send_queue->max_qlen*3)/4; + + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, chan, FALSE,NULL,NULL,NULL); + + G_main_add_SignalHandler(G_PRIORITY_DEFAULT, SIGHUP, + logd_hup_action, mainloop, NULL); + g_main_run(mainloop); + + return; +} + +static gboolean +direct_log(IPC_Channel* ch, gpointer user_data) +{ + IPC_Message* ipcmsg; + GMainLoop* loop; + int pri = LOG_DEBUG + 1; + + loop =(GMainLoop*)user_data; + + while(ch->ops->is_message_pending(ch)){ + if (ch->ch_status == IPC_DISCONNECT){ + cl_log(LOG_ERR, "read channel is disconnected:" + "something very wrong happened"); + return FALSE; + } + + ipcmsg = getIPCmsg(ch); + if (ipcmsg == NULL){ + return TRUE; + } + + if( ipcmsg->msg_body + && ipcmsg->msg_len > 0 ){ + LogDaemonMsgHdr *logmsghdr; + LogDaemonMsgHdr copy; + char *msgtext; + + logmsghdr = (LogDaemonMsgHdr*) ipcmsg->msg_body; + /* this copy nonsense is here because apparently ia64 + * complained about "unaligned memory access. */ +#define COPYFIELD(copy, msg, field) memcpy(((u_char*)©.field), ((u_char*)&msg->field), sizeof(copy.field)) + COPYFIELD(copy, logmsghdr, use_pri_str); + COPYFIELD(copy, logmsghdr, entity); + COPYFIELD(copy, logmsghdr, entity_pid); + COPYFIELD(copy, logmsghdr, timestamp); + COPYFIELD(copy, logmsghdr, priority); + /* Don't want to copy the following message text */ + + msgtext = (char *)logmsghdr + sizeof(LogDaemonMsgHdr); + cl_direct_log(copy.priority, msgtext + , copy.use_pri_str + , copy.entity, copy.entity_pid + , copy.timestamp); + + if (copy.priority < pri) + pri = copy.priority; + + (void)logd_log; +/* + if (verbose){ + logd_log("%s[%d]: %s %s\n", + logmsg->entity[0]=='\0'? + "unknown": copy.entity, + copy.entity_pid, + ha_timestamp(copy.timestamp), + msgtext); + } + */ + if (ipcmsg->msg_done){ + ipcmsg->msg_done(ipcmsg); + } + } + } + /* current message backlog processed, + * about to return to mainloop, + * fflush and potentially fsync stuff */ + cl_log_do_fflush(pri <= LOG_ERR); + + if(needs_shutdown) { + cl_log(LOG_INFO, "Exiting write process"); + g_main_quit(loop); + return FALSE; + } + return TRUE; +} + +static gboolean +logd_term_write_action(int sig, gpointer userdata) +{ + /* as a side-effect, the log message makes sure we enter direct_log() + * one last time (so we always exit) + */ + needs_shutdown = TRUE; + cl_log(LOG_INFO, "logd_term_write_action: received SIGTERM"); + cl_log(LOG_DEBUG, "Writing out %d messages then quitting", + (int)chanspair[WRITE_PROC_CHAN]->recv_queue->current_qlen); + + direct_log(chanspair[WRITE_PROC_CHAN], userdata); + + return TRUE; +} + +static void +write_msg_process(IPC_Channel* readchan) +{ + + GMainLoop* mainloop; + IPC_Channel* ch = readchan; + + + mainloop = g_main_new(FALSE); + + G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, + ch, FALSE, + direct_log, mainloop, NULL); + + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGTERM, + logd_term_write_action, mainloop, NULL); + + G_main_add_SignalHandler(G_PRIORITY_DEFAULT, SIGHUP, + logd_hup_action, mainloop, NULL); + + g_main_run(mainloop); + +} + + + + + + +static void +usage(void) +{ + printf("usage: \n" + "%s [options]\n\n" + "options: \n" + "-d make the program a daemon\n" + "-k stop the logging daemon if it is already running\n" + "-s return logging daemon status \n" + "-c use this config file\n" + "-v verbosely print debug messages" + "-h print out this message\n\n", + cmdname); + + return; +} +int +main(int argc, char** argv, char** envp) +{ + + int c; + gboolean daemonize = FALSE; + gboolean stop_logd = FALSE; + gboolean ask_status= FALSE; + const char* cfgfile = NULL; + pid_t pid; + + cmdname = argv[0]; + while ((c = getopt(argc, argv, "c:dksvh")) != -1){ + + switch(c){ + + case 'd': /* daemonize */ + daemonize = TRUE; + break; + case 'k': /* stop */ + stop_logd = TRUE; + break; + case 's': /* status */ + ask_status = TRUE; + break; + case 'c': /* config file*/ + cfgfile = optarg; + break; + case 'v': + verbose = TRUE; + break; + case 'h': /*help message */ + default: + usage(); + exit(1); + } + + } + + set_ipc_time_debug_flag(FALSE); + cl_log_set_uselogd(FALSE); + + if (!cfgfile && access(DEFAULT_CFG_FILE, F_OK) == 0) { + cfgfile = DEFAULT_CFG_FILE; + } + + + /* default one set to "logd" + * by setting facility, we enable syslog + */ + cl_log_enable_stderr(TRUE); + cl_log_set_entity(logd_config.entity); + cl_log_set_facility(logd_config.log_facility); + + + if (ask_status){ + long pid; + + if( (pid = cl_read_pidfile(LOGD_PIDFILE)) > 0 ){ + printf("logging daemon is running [pid = %ld].\n", pid); + exit(LSB_EXIT_OK); + }else{ + if (pid == - LSB_STATUS_VAR_PID) { + printf("logging daemon is stopped: %s exists.\n" + , LOGD_PIDFILE); + }else{ + printf("logging daemon is stopped.\n"); + } + } + exit(-pid); + + } + if (stop_logd){ + logd_stop(); + exit(LSB_EXIT_OK); + } + + logd_make_daemon(daemonize); + + + if (ipc_channel_pair(chanspair) != IPC_OK){ + cl_perror("cannot create channel pair IPC"); + return -1; + } + + + if (cfgfile && !parse_config(cfgfile)) { + FILE* f; + if ((f = fopen(cfgfile, "r")) != NULL){ + fclose(f); + cl_log(LOG_ERR, "Config file [%s] is incorrect." + , cfgfile); + exit(LSB_EXIT_NOTCONFIGED); + } + } + + if (strlen(logd_config.debugfile) > 0) { + cl_log_set_debugfile(logd_config.debugfile); + } + if (strlen(logd_config.logfile) > 0) { + cl_log_set_logfile(logd_config.logfile); + } + cl_log_set_syslogprefix(logd_config.syslogprefix); + cl_log_set_entity(logd_config.entity); + cl_log_set_facility(logd_config.log_facility); + + cl_log(LOG_INFO, "logd started with %s.", + cfgfile ? cfgfile : "default configuration"); + + if (cl_enable_coredumps(TRUE) < 0){ + cl_log(LOG_ERR, "enabling core dump failed"); + } + cl_cdtocoredir(); + + + + + chanspair[WRITE_PROC_CHAN]->ops->set_recv_qlen(chanspair[WRITE_PROC_CHAN], + LOGD_QUEUE_LEN); + + chanspair[READ_PROC_CHAN]->ops->set_send_qlen(chanspair[READ_PROC_CHAN], + LOGD_QUEUE_LEN); + + if (init_set_proc_title(argc, argv, envp) < 0) { + cl_log(LOG_ERR, "Allocation of proc title failed."); + return -1; + } + + switch(pid = fork()){ + case -1: + cl_perror("Can't fork child process!"); + return -1; + case 0: + /*child*/ + cl_log_use_buffered_io(1); + set_proc_title("ha_logd: write process"); + write_msg_process(chanspair[WRITE_PROC_CHAN]); + break; + default: + /*parent*/ + set_proc_title("ha_logd: read process"); + write_process_pid = pid; + /* we don't expect to log anything in the parent. */ + cl_log_close_log_files(); + + read_msg_process(chanspair[READ_PROC_CHAN]); + break; + } + return 0; +} + + + + diff --git a/logd/ha_logger.1 b/logd/ha_logger.1 new file mode 100644 index 0000000..db7f939 --- /dev/null +++ b/logd/ha_logger.1 @@ -0,0 +1,38 @@ +.TH HA_LOGGER 1 "5th Nov 2004" +.SH NAME +.B ha_logger +\- Log a message to files/syslog through the HA Logging Daemon +.SH SYNOPSIS +.B ha_logger +.RI "[-D ha-log/ha-debug] [-t tag] [message ...]" +.P +.SH DESCRIPTION +.B ha_logger +is used to log a message to files/syslog through the HA Logging Daemon +.PP +.IP "\fB-D\fP \fIha-log/ha-debug\fP" +Log the message to different files. Ha-log will log the message to +the log file and the debug file, while ha-debug will log the message +to the debug file only. +.IP "\fB-t\fP \fItag\fP" +Mark every line in the log with the specified +.IP \fBmessage\fP +The message you want to log on. +.PP +.SH SEE ALSO +ha_logd(8) heartbeat(8) + +.SH DOCUMENTATION +More information may be found at +.UR http://linux-ha.org/wiki +http://linux-ha.org/wiki +.UE + +.SH AUTHORS +.nf + Guochun Shi + Alan Robertson + Lars Marowsky-Bree +.fi + + diff --git a/logd/ha_logger.c b/logd/ha_logger.c new file mode 100644 index 0000000..5e2f9ef --- /dev/null +++ b/logd/ha_logger.c @@ -0,0 +1,142 @@ +/* + * ha_logger.c utility to log a message to the logging daemon + * + * Copyright (C) 2004 Guochun Shi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EXIT_OK 0 +#define EXIT_FAIL 1 + +int LogToDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); +void cl_log(int priority, const char * fmt, ...) G_GNUC_PRINTF(2,3); +static void +usage(void) +{ + printf("usage: " + "ha_logger [-t tag] [-D ] [message]\n"); + return; +} +#define BUFSIZE 1024 +int +main(int argc, char** argv) +{ + int priority; + char* entity = NULL; + int c; + char buf[BUFSIZE]; + const char* logtype = "ha-log"; + + + while (( c =getopt(argc, argv,"t:D:h")) != -1){ + switch(c){ + + case 't': + entity = optarg; + break; + case 'D': + logtype=optarg; + break; + case 'h': + usage(); + exit(1); + default: + usage(); + exit(1); + } + + } + + if(!cl_log_test_logd()){ + fprintf(stderr, "logd is not running"); + return EXIT_FAIL; + } + + argc -=optind; + argv += optind; + + if (entity != NULL){ + cl_log_set_entity(entity); + } + + if (strcmp(logtype, "ha-log") == 0){ + priority = LOG_INFO; + } else if (strcmp(logtype, "ha-debug") == 0){ + priority = LOG_DEBUG; + }else{ + goto err_exit; + } + + if (argc > 0){ + register char *p; + + for (p = *argv; *argv; argv++, p = *argv) { + while (strlen(p) > BUFSIZE-1) { + memcpy(buf, p, BUFSIZE-1); + *(buf+BUFSIZE-1) = '\0'; + if (LogToDaemon(priority,buf, + BUFSIZE,FALSE) != HA_OK){ + return EXIT_FAIL; + } + p += BUFSIZE-1; + } + if (LogToDaemon(priority,p, + strnlen(p, BUFSIZE),FALSE) != HA_OK){ + return EXIT_FAIL; + } + } + return EXIT_OK; + }else { + while (fgets(buf, sizeof(buf), stdin) != NULL) { + /* glibc is buggy and adds an additional newline, + so we have to remove it here until glibc is fixed */ + int len = strlen(buf); + + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + if (LogToDaemon(priority, buf,strlen(buf), FALSE) == HA_OK){ + continue; + }else { + return EXIT_FAIL; + } + } + + return EXIT_OK; + } + + err_exit: + usage(); + return(1); + +} + diff --git a/logd/logd.cf b/logd/logd.cf new file mode 100644 index 0000000..cd8ccc3 --- /dev/null +++ b/logd/logd.cf @@ -0,0 +1,66 @@ +# File to write debug messages to +# Default: /var/log/ha-debug +#debugfile /var/log/ha-debug + +# +# +# File to write other messages to +# Default: /var/log/ha-log +#logfile /var/log/ha-log + +# +# +# Octal file permission to create the log files with +# Default: 0644 +#logmode 0640 + + +# +# +# Facility to use for syslog()/logger +# (set to 'none' to disable syslog logging) +# Default: daemon +#logfacility daemon + + +# Entity to be shown at beginning of a message +# generated by the logging daemon itself +# Default: "logd" +#entity logd + + +# Entity to be shown at beginning of _every_ message +# passed to syslog (not to log files). +# +# Intended for easier filtering, or safe blacklisting. +# You can filter on logfacility and this prefix. +# +# Message format changes like this: +# -Nov 18 11:30:31 soda logtest: [21366]: info: total message dropped: 0 +# +Nov 18 11:30:31 soda common-prefix: logtest[21366]: info: total message dropped: 0 +# +# Default: none (disabled) +#syslogprefix linux-ha + + +# Do we register to apphbd +# Default: no +#useapphbd no + +# There are two processes running for logging daemon +# 1. parent process which reads messages from all client channels +# and writes them to the child process +# +# 2. the child process which reads messages from the parent process through IPC +# and writes them to syslog/disk + + +# set the send queue length from the parent process to the child process +# +#sendqlen 256 + +# set the recv queue length in child process +# +#recvqlen 256 + + diff --git a/logd/logd.in b/logd/logd.in new file mode 100755 index 0000000..41d60c5 --- /dev/null +++ b/logd/logd.in @@ -0,0 +1,101 @@ +#!/bin/sh +# +# +# logd Start logd (non-blocking log service) +# +# Author: Dejan Muhamedagic +# (After the heartbeat init script) +# License: GNU General Public License (GPL) +# +# This script works correctly under SuSE, Debian, +# Conectiva, Red Hat and a few others. Please let me know if it +# doesn't work under your distribution, and we'll fix it. +# We don't hate anyone, and like for everyone to use +# our software, no matter what OS or distribution you're using. +# +# chkconfig: 2345 19 21 +# description: Startup script logd service. +# processname: ha_logd +# pidfile: @localstatedir@/run/logd.pid +# config: @sysconfdir@/logd.cf +# +### BEGIN INIT INFO +# Description: ha_logd is a non-blocking logging daemon. +# It can log messages either to a file or through syslog +# daemon. +# Short-Description: ha_logd logging daemon +# Provides: ha_logd +# Required-Start: $network $syslog $remote_fs +# Required-Stop: $network $syslog $remote_fs +# X-Start-Before: heartbeat openais corosync +# Default-Start: 3 5 +# Default-Stop: 0 1 6 +### END INIT INFO + +LOGD_CFG=@sysconfdir@/logd.cf +LOGD_OPT="" +[ -f "$LOGD_CFG" ] && LOGD_OPT="-c $LOGD_CFG" +LOGD_BIN="@libdir@/@HB_PKG@/ha_logd" + +if [ ! -f $LOGD_BIN ]; then + echo -n "ha_logd not installed." + exit 5 +fi + +StartLogd() { + echo -n "Starting ha_logd: " + $LOGD_BIN -s >/dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "logd is already running" + return 0 + fi + + $LOGD_BIN -d $LOGD_OPT >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "starting logd failed" + exit 1 + fi + echo "ok" + exit 0 +} + +StopLogd() { + echo -n "Stopping ha_logd: " + + $LOGD_BIN -s >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "logd is already stopped" + return 0 + fi + + $LOGD_BIN -k >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "stopping logd failed" + exit 1 + fi + echo "stopped" + exit 0 +} + +StatusLogd() { + $LOGD_BIN -s + exit $? +} + +case "$1" in + start) StartLogd ;; + status) StatusLogd ;; + stop) StopLogd ;; + restart) + sleeptime=1 + $0 stop && sleep $sleeptime && $0 start + echo + ;; + try-restart) + $0 status && $0 restart + ;; + *) + echo "Usage: $0 {start|stop|status|restart}" + exit 1 +esac + diff --git a/logd/logd.service.in b/logd/logd.service.in new file mode 100644 index 0000000..9783547 --- /dev/null +++ b/logd/logd.service.in @@ -0,0 +1,13 @@ +[Unit] +Description=ha_logd logging daemon +Before=pacemaker.service +PartOf=pacemaker.service + +[Service] +ExecStart=@libdir@/@HB_PKG@/ha_logd -c @sysconfdir@/logd.cf +ExecStartPre=/bin/rm -f @HA_VARRUNDIR@/logd.pid +ExecStopPost=/bin/rm -f @HA_VARRUNDIR@/logd.pid +PIDFile=@HA_VARRUNDIR@/logd.pid + +[Install] +WantedBy=multi-user.target diff --git a/logd/logtest.c b/logd/logtest.c new file mode 100644 index 0000000..11e4014 --- /dev/null +++ b/logd/logtest.c @@ -0,0 +1,129 @@ +/* + * ha_logger.c utility to log a message to the logging daemon + * + * Copyright (C) 2004 Guochun Shi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EXIT_OK 0 +#define EXIT_FAIL 1 +#define MAXMSGSIZE (1048*4) + +int LogToDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); +extern IPC_Channel * get_log_chan(void); + +static GMainLoop* loop; + +static gboolean +send_log_msg(gpointer data) +{ + static int count = 0; + char msgstring[MAXMSGSIZE]; + int priority; + static int dropmsg = 0; + long maxcount = (long) data; + IPC_Channel* chan = get_log_chan(); + + + if (chan == NULL){ + cl_log(LOG_ERR, "logging channel is NULL"); + g_main_quit(loop); + return FALSE; + + } + if (count >= maxcount){ + cl_log(LOG_INFO, "total message dropped: %d", dropmsg); + g_main_quit(loop); + return FALSE; + } + + if (chan->send_queue->current_qlen + == chan->send_queue->max_qlen){ + return TRUE; + } + + + priority = LOG_INFO; + msgstring[0]=0; + + snprintf(msgstring, sizeof(msgstring),"Message %d", count++); + fprintf(stderr, "sending %s\n", msgstring); + if (LogToDaemon(priority, msgstring,MAXMSGSIZE, FALSE) != HA_OK){ + printf("sending out messge %d failed\n", count); + dropmsg++; + } + + + + return TRUE; +} + + +static void +usage(char* prog) +{ + printf("Usage:%s \n", prog); + return; +} + +int +main(int argc, char** argv) +{ + + long maxcount; + + if (argc < 2){ + usage(argv[0]); + return 1; + } + + maxcount = atoi(argv[1]); + + cl_log_set_entity("logtest"); + cl_log_set_facility(HA_LOG_FACILITY); + cl_log_set_uselogd(TRUE); + + if(!cl_log_test_logd()){ + return EXIT_FAIL; + } + + cl_log_set_logd_channel_source(NULL, NULL); + + g_idle_add(send_log_msg, (gpointer)maxcount); + + + loop = g_main_loop_new(NULL, FALSE); + g_main_run(loop); + return(1); + +} + diff --git a/lrm/Makefile.am b/lrm/Makefile.am new file mode 100644 index 0000000..78a92c4 --- /dev/null +++ b/lrm/Makefile.am @@ -0,0 +1,20 @@ +# Author: Sun Jiang Dong +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = lrmd admin test diff --git a/lrm/admin/Makefile.am b/lrm/admin/Makefile.am new file mode 100644 index 0000000..a92cd72 --- /dev/null +++ b/lrm/admin/Makefile.am @@ -0,0 +1,40 @@ +# +# Author: Sun Jiang Dong +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +halibdir = $(libdir)/@HB_PKG@ +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la $(GLIBLIB) +LRM_DIR = lrm +sbin_PROGRAMS = lrmadmin +sbin_SCRIPTS = cibsecret +lrmadmin_SOURCES = lrmadmin.c +lrmadmin_LDFLAGS = $(COMMONLIBS) +lrmadmin_LDADD = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la +lrmadmin_DEPENDENCIES = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la + +if BUILD_HELP +man8_MANS = $(sbin_PROGRAMS:%=%.8) +%.8: % + echo Creating $@ + chmod a+x $< + help2man --output $@ --no-info --section 8 --name "Part of the Linux-HA project" $(top_builddir)/lrm/admin/$< +endif diff --git a/lrm/admin/cibsecret.in b/lrm/admin/cibsecret.in new file mode 100755 index 0000000..5255cdd --- /dev/null +++ b/lrm/admin/cibsecret.in @@ -0,0 +1,350 @@ +#!/bin/sh + +# Copyright (C) 2011 Dejan Muhamedagic +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# WARNING: +# +# The CIB secrets interface and implementation is still being +# discussed, it may change + +# +# cibsecret: manage the secrets directory /var/lib/heartbeat/lrm/secrets +# +# secrets are ascii files, holding just one value per file: +# /var/lib/heartbeat/lrm/secrets// +# +# NB: this program depends on utillib.sh +# + +. @OCF_ROOT_DIR@/lib/heartbeat/ocf-shellfuncs + +HA_NOARCHBIN=@datadir@/@PACKAGE_NAME@ + +. $HA_NOARCHBIN/utillib.sh + +LRM_CIBSECRETS=$HA_VARLIB/lrm/secrets + +PROG=`basename $0` +SSH_OPTS="-o StrictHostKeyChecking=no" + +usage() { + cat< + +-C: don't read/write the CIB + +command: set | delete | stash | unstash | get | check | sync + + set + get + check + stash (if not -C) + unstash (if not -C) + delete + sync + +stash/unstash: move the parameter from/to the CIB (if you already + have the parameter set in the CIB). + +set/delete: add/remove a parameter from the local file. + +get: display the parameter from the local file. + +check: verify MD5 hash of the parameter from the local file and the CIB. + +sync: copy $LRM_CIBSECRETS to other nodes. + +Examples: + + $PROG set ipmi_node1 passwd SecreT_PASS + $PROG stash ipmi_node1 passwd + $PROG get ipmi_node1 passwd + $PROG check ipmi_node1 passwd + $PROG sync +EOF + exit $1 +} +fatal() { + echo "ERROR: $*" + exit 1 +} +warn() { + echo "WARNING: $*" +} +info() { + echo "INFO: $*" +} + +check_env() { + which md5sum >/dev/null 2>&1 || + fatal "please install md5sum to run $PROG" + if which pssh >/dev/null 2>&1; then + rsh=pssh_fun + rcp=pscp_fun + elif which pdsh >/dev/null 2>&1; then + rsh=pdsh_fun + rcp=pdcp_fun + elif which ssh >/dev/null 2>&1; then + rsh=ssh_fun + rcp=scp_fun + else + fatal "please install pssh, pdsh, or ssh to run $PROG" + fi + ps -ef | grep '[c]rmd' >/dev/null || + fatal "pacemaker not running? $PROG needs pacemaker" +} + +get_other_nodes() { + crm_node -l | awk '{print $2}' | grep -v `uname -n` +} +check_down_nodes() { + local n down_nodes + down_nodes=`(for n; do echo $n; done) | sort | uniq -u` + if [ -n "$down_nodes" ]; then + if [ `echo $down_nodes | wc -w` = 1 ]; then + warn "node $down_nodes is down" + warn "you'll need to update it using $PROG sync later" + else + warn "nodes `echo $down_nodes` are down" + warn "you'll need to update them using $PROG sync later" + fi + fi +} + +pssh_fun() { + pssh -qi -H "$nodes" -x "$SSH_OPTS" $* +} +pscp_fun() { + pscp -q -H "$nodes" -x "-pr" -x "$SSH_OPTS" $* +} +pdsh_fun() { + local pdsh_nodes=`echo $nodes | tr ' ' ','` + export PDSH_SSH_ARGS_APPEND="$SSH_OPTS" + pdsh -w $pdsh_nodes $* +} +pdcp_fun() { + local pdsh_nodes=`echo $nodes | tr ' ' ','` + export PDSH_SSH_ARGS_APPEND="$SSH_OPTS" + pdcp -pr -w $pdsh_nodes $* +} +ssh_fun() { + local h + for h in $nodes; do + ssh $SSH_OPTS $h $* || return + done +} +scp_fun() { + local h src="$1" dest=$2 + for h in $nodes; do + scp -pr -q $SSH_OPTS $src $h:$dest || return + done +} +# TODO: this procedure should be replaced with csync2 +# provided that csync2 has already been configured +sync_files() { + local crm_nodes=`get_other_nodes` + local nodes=`get_live_nodes $crm_nodes` + check_down_nodes $nodes $crm_nodes + [ "$nodes" = "" ] && { + info "no other nodes live" + return + } + info "syncing $LRM_CIBSECRETS to `echo $nodes` ..." + $rsh rm -rf $LRM_CIBSECRETS && + $rsh mkdir -p `dirname $LRM_CIBSECRETS` && + $rcp $LRM_CIBSECRETS `dirname $LRM_CIBSECRETS` +} +sync_one() { + local f=$1 f_all="$1 $1.sign" + local crm_nodes=`get_other_nodes` + local nodes=`get_live_nodes $crm_nodes` + check_down_nodes $nodes $crm_nodes + [ "$nodes" = "" ] && { + info "no other nodes live" + return + } + info "syncing $f to `echo $nodes` ..." + $rsh mkdir -p `dirname $f` && + if [ -f "$f" ]; then + $rcp "$f_all" `dirname $f` + else + $rsh rm -f $f_all + fi +} + +is_secret() { + # assume that the secret is in the CIB if we cannot talk to + # cib + [ "$NO_CRM" ] || + test "$1" = "$MAGIC" +} +check_cib_rsc() { + local rsc=$1 output + output=`$NO_CRM crm_resource -r $rsc -W >/dev/null 2>&1` || + fatal "resource $rsc doesn't exist: $output" +} +get_cib_param() { + local rsc=$1 param=$2 + check_cib_rsc $rsc + $NO_CRM crm_resource -r $rsc -g $param 2>/dev/null +} +set_cib_param() { + local rsc=$1 param=$2 value=$3 + check_cib_rsc $rsc + $NO_CRM crm_resource -r $rsc -p $param -v "$value" 2>/dev/null +} +remove_cib_param() { + local rsc=$1 param=$2 + check_cib_rsc $rsc + $NO_CRM crm_resource -r $rsc -d $param 2>/dev/null +} + +localfiles() { + local cmd=$1 + local rsc=$2 param=$3 value=$4 + local local_file=$LRM_CIBSECRETS/$rsc/$param + case $cmd in + "get") + cat $local_file 2>/dev/null + true + ;; + "getsum") + cat $local_file.sign 2>/dev/null + true + ;; + "set") + local md5sum + md5sum=`printf $value | md5sum` || + fatal "md5sum failed to produce hash for resource $rsc parameter $param" + md5sum=`echo $md5sum | awk '{print $1}'` + mkdir -p `dirname $local_file` && + echo $value > $local_file && + echo $md5sum > $local_file.sign && + sync_one $local_file + ;; + "remove") + rm -f $local_file + sync_one $local_file + ;; + *) + # not reached, this is local interface + ;; + esac +} +get_local_param() { + local rsc=$1 param=$2 + localfiles get $rsc $param +} +set_local_param() { + local rsc=$1 param=$2 value=$3 + localfiles set $rsc $param $value +} +remove_local_param() { + local rsc=$1 param=$2 + localfiles remove $rsc $param +} + +cibsecret_set() { + local value=$1 + + if [ -z "$NO_CRM" ]; then + [ "$current" -a "$current" != "$MAGIC" -a "$current" != "$value" ] && + fatal "CIB value <$current> different for $rsc parameter $param; please delete it first" + fi + set_local_param $rsc $param $value && + set_cib_param $rsc $param "$MAGIC" +} + +cibsecret_check() { + local md5sum local_md5sum + is_secret "$current" || + fatal "resource $rsc parameter $param not set as secret, nothing to check" + local_md5sum=`localfiles getsum $rsc $param` + [ "$local_md5sum" ] || + fatal "no MD5 hash for resource $rsc parameter $param" + md5sum=`printf "$current_local" | md5sum | awk '{print $1}'` + [ "$md5sum" = "$local_md5sum" ] || + fatal "MD5 hash mismatch for resource $rsc parameter $param" +} + +cibsecret_get() { + cibsecret_check + echo "$current_local" +} + +cibsecret_delete() { + remove_local_param $rsc $param && + remove_cib_param $rsc $param +} + +cibsecret_stash() { + [ "$NO_CRM" ] && + fatal "no access to Pacemaker, stash not supported" + [ "$current" = "" ] && + fatal "nothing to stash for resource $rsc parameter $param" + is_secret "$current" && + fatal "resource $rsc parameter $param already set as secret, nothing to stash" + cibsecret_set "$current" +} + +cibsecret_unstash() { + [ "$NO_CRM" ] && + fatal "no access to Pacemaker, unstash not supported" + [ "$current_local" = "" ] && + fatal "nothing to unstash for resource $rsc parameter $param" + is_secret "$current" || + warn "resource $rsc parameter $param not set as secret, but we have local value so proceeding anyway" + remove_local_param $rsc $param && + set_cib_param $rsc $param $current_local +} + +cibsecret_sync() { + sync_files +} + +check_env + +MAGIC="lrm://" +umask 0077 + +if [ "$1" = "-C" ]; then + NO_CRM=':' + shift 1 +fi + +cmd=$1 +rsc=$2 +param=$3 +value=$4 + +case "$cmd" in + set) [ $# -ne 4 ] && usage 1;; + get) [ $# -ne 3 ] && usage 1;; + check) [ $# -ne 3 ] && usage 1;; + stash) [ $# -ne 3 ] && usage 1;; + unstash) [ $# -ne 3 ] && usage 1;; + delete) [ $# -ne 3 ] && usage 1;; + sync) [ $# -ne 1 ] && usage 1;; + *) usage 1; +esac + +# we'll need these two often +current=`get_cib_param $rsc $param` +current_local=`get_local_param $rsc $param` + +cibsecret_$cmd $value diff --git a/lrm/admin/lrmadmin.c b/lrm/admin/lrmadmin.c new file mode 100644 index 0000000..27f37bf --- /dev/null +++ b/lrm/admin/lrmadmin.c @@ -0,0 +1,1129 @@ +/* File: lrmadmin.c + * Description: A adminstration tool for Local Resource Manager + * + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + * Todo: security verification + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include + +#include +#include +#include +#include +#include +#ifndef __USE_GNU +#define __USE_GNU +/* For strnlen protype */ +#include +#undef __USE_GNU +#else +#include +#endif +#include +#ifdef HAVE_GETOPT_H +#include +#endif /* HAVE_GETOPT_H */ +#include +#include +#include +#include +#include +#include +#include + +static const char *optstring = "A:D:X:dE:F:dg:p:M:O:P:c:S:LI:CT:n:hv"; + +#ifdef HAVE_GETOPT_H +static struct option long_options[] = { + {"daemon", 0, NULL, 'd'}, + {"executera", 1, NULL, 'E'}, + {"flush", 1, NULL, 'F'}, + {"state", 1, NULL, 'S'}, + {"listall", 0, NULL, 'L'}, + {"information", 1, NULL, 'I'}, + {"add", 1, NULL, 'A'}, + {"delete", 1, NULL, 'D'}, + {"fail", 1, NULL, 'X'}, + {"raclass_supported", 1, NULL, 'C'}, + {"ratype_supported", 1, NULL, 'T'}, + {"all_type_metadata", 1, NULL, 'O'}, + {"metadata", 1, NULL, 'M'}, + {"provider", 1, NULL, 'P'}, + {"set_lrmd_param", 1, NULL, 'p'}, + {"get_lrmd_param", 1, NULL, 'g'}, + {"help", 0, NULL, 'h'}, + {"version", 0, NULL, 'v'}, + {NULL, 0, NULL, 0} +}; +#endif /* HAVE_GETOPT_H */ + +static GMainLoop *mainloop; +static const char *lrmadmin_name = "lrmadmin"; +static const char *fake_name; +/* 20 is the length limit for a argv[x] */ +static const int ARGVI_MAX_LEN = 48; + +typedef enum { + ERROR_OPTION = -1, + NULL_OP, + DAEMON_OP, + EXECUTE_RA, + FLUSH, + RSC_STATE, + LIST_ALLRSC, + INF_RSC, + SET_PARAM, + GET_PARAM, + ADD_RSC, + DEL_RSC, + FAIL_RSC, + RACLASS_SUPPORTED, + RATYPE_SUPPORTED, + RA_METADATA, + RA_PROVIDER, + ALL_RA_METADATA, + HELP +} lrmadmin_cmd_t; + +#define nullcheck(p) ((p) ? (p) : "") +static const char * status_msg[6] = { + "pending", /* LRM_OP_PENDING */ + "succeed", /* LRM_OP_DONE */ + "cancelled", /* LRM_OP_CANCELLED */ + "timeout", /* LRM_OP_TIMEOUT */ + "not Supported", /* LRM_OP_NOTSUPPORTED */ + "failed due to an error" /* LRM_OP_ERROR */ +}; + +static const char * rc_msg[] = { + "unknown error", + "no ra", + "ok", + "unknown error", + "invalid parameter", + "unimplement feature", + "insufficient priority", + "not installed", + "not configured", + "not running", + "running master", + "failed master", + "invalid rc", + /* For status command only */ + "daemon dead1", + "daemon dead2", + "daemon stopped", + "status unknow" +}; + + +static gboolean QUIT_GETOPT = FALSE; +static lrmadmin_cmd_t lrmadmin_cmd = NULL_OP; +static gboolean ASYN_OPS = FALSE; +static int call_id = 0; +static int TIMEOUT = -1; /* the unit is ms */ + +static const char *simple_help_screen = +"lrmadmin -d,--daemon\n" +" -A,--add []\n" +" -D,--delete \n" +" -F,--flush \n" +" -X,--fail [ []]\n" +" -E,--execute []\n" +" -S,--state [-n ]\n" +" -L,--listall\n" +" -I,--information \n" +" -C,--raclass_supported\n" +" -T,--ratype_supported \n" +" -O,--all metadata of this class \n" +" -M,--metadata \n" +" -P,--provider \n" +" -p,--set_lrmd_param \n" +" -g,--get_lrmd_param \n" +" -v,--version\n" +" -h,--help\n"; + +#define OPTION_OBSCURE_CHECK \ + if ( lrmadmin_cmd != NULL_OP ) { \ + cl_log(LOG_ERR,"Obscure options."); \ + return -1; \ + } + +/* the begin of the internal used function list */ +static int resource_operation(ll_lrm_t * lrmd, char *rsc_id, + int argc, int optind, char * argv[]); +static int add_resource(ll_lrm_t * lrmd, char *rsc_id, + int argc, int optind, char * argv[]); +static int fail_resource(ll_lrm_t * lrmd, char *rsc_id, int optc, char *opts[]); +static int get_param(ll_lrm_t * lrmd, int argc, int optind, char * argv[]); +static int set_param(ll_lrm_t * lrmd, int argc, int optind, char * argv[]); +static int transfer_cmd_params(int amount, int start, char * argv[], + const char * class, GHashTable ** params_ht); +static void g_print_stringitem_and_free(gpointer data, gpointer user_data); +static void g_print_rainfo_item_and_free(gpointer data, gpointer user_data); +static void g_print_ops(gpointer data, gpointer user_data); +static void g_get_rsc_description(gpointer data, gpointer user_data); +static void g_print_meta(gpointer key, gpointer value, gpointer user_data); + +static void print_rsc_inf(lrm_rsc_t * lrmrsc); +static char * params_hashtable_to_str(const char * class, GHashTable * ht); +static void free_stritem_of_hashtable(gpointer key, gpointer value, + gpointer user_data); +static void ocf_params_hash_to_str(gpointer key, gpointer value, + gpointer user_data); +static void normal_params_hash_to_str(gpointer key, gpointer value, + gpointer user_data); +static lrm_rsc_t * get_lrm_rsc(ll_lrm_t * lrmd, char * rscid); + +static int ra_metadata(ll_lrm_t * lrmd, int argc, int optind, char * argv[]); +static int ra_provider(ll_lrm_t * lrmd, int argc, int optind, char * argv[]); +static gboolean lrmd_output_dispatch(IPC_Channel* notused, gpointer user_data); +static gboolean lrm_op_timeout(gpointer data); + +/* the end of the internal used function list */ + +static void lrm_op_done_callback(lrm_op_t* op); + +static int ret_value; +int main(int argc, char **argv) +{ + int option_char; + char rscid_arg_tmp[RID_LEN]; + ll_lrm_t* lrmd; + lrm_rsc_t * lrm_rsc; + GList *raclass_list = NULL, + *ratype_list = NULL, + *rscid_list; + GHashTable *all_meta = NULL; + char raclass[20]; + const char * login_name = lrmadmin_name; + + /* Prevent getopt_long to print error message on stderr itself */ + /*opterr = 0; */ + + if (argc == 1) { + printf("%s",simple_help_screen); + return 0; + } + + cl_log_set_entity(lrmadmin_name); + cl_log_enable_stderr(TRUE); + cl_log_set_facility(LOG_USER); + + memset(rscid_arg_tmp, '\0', RID_LEN); + memset(raclass, '\0', 20); + do { +#ifdef HAVE_GETOPT_H + option_char = getopt_long (argc, argv, optstring, + long_options, NULL); +#else + option_char = getopt (argc, argv, optstring); +#endif + + if (option_char == -1) { + break; + } + + switch (option_char) { + case 'd': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = DAEMON_OP; + QUIT_GETOPT = TRUE; + break; + + case 'A': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = ADD_RSC; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'D': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = DEL_RSC; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'X': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = FAIL_RSC; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'C': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RACLASS_SUPPORTED; + break; + + case 'T': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RATYPE_SUPPORTED; + if (optarg) { + strncpy(raclass, optarg, 19); + } + break; + + case 'O': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = ALL_RA_METADATA; + if (optarg) { + strncpy(raclass, optarg, 19); + } + break; + + case 'F': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = FLUSH; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'E': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = EXECUTE_RA; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'M': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RA_METADATA; + break; + + case 'P': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RA_PROVIDER; + break; + + case 'S': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = RSC_STATE; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'L': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = LIST_ALLRSC; + break; + + case 'I': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = INF_RSC; + strncpy(rscid_arg_tmp, optarg, RID_LEN-1); + break; + + case 'p': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = SET_PARAM; + break; + + case 'g': + OPTION_OBSCURE_CHECK + lrmadmin_cmd = GET_PARAM; + break; + + case 'n': + if (optarg) { + fake_name = optarg; + } + break; + + case 'v': + printf("%s\n",GLUE_VERSION); + return 0; + case 'h': + OPTION_OBSCURE_CHECK + printf("%s",simple_help_screen); + return 0; + + case '?': + /* cl_log(LOG_ERR,"There is a unrecognized + option %s", optarg); + */ + printf("%s", simple_help_screen); + return -1; + + default: + cl_log(LOG_ERR,"getopt returned character" + " code %c.", option_char); + return -1; + } + } while (!QUIT_GETOPT); + + lrmd = ll_lrm_new("lrm"); + + if (NULL == lrmd) { + cl_log(LOG_ERR,"ll_lrm_new returned NULL."); + return -2; + } + + lrmd->lrm_ops->set_lrm_callback(lrmd, lrm_op_done_callback); + + if (fake_name != NULL) { + login_name = fake_name; + } + if (lrmd->lrm_ops->signon(lrmd, login_name) != 1) { /* != HA_OK */ + printf("lrmd is not running.\n"); + if (lrmadmin_cmd == DAEMON_OP) { + return LSB_STATUS_STOPPED; + } else { + cl_log(LOG_WARNING,"Can't connect to lrmd!"); + return -2; + } + } + + if (lrmadmin_cmd == DAEMON_OP) { + printf("lrmd is stopped.\n"); + lrmd->lrm_ops->signoff(lrmd); + return 0; + } + + switch (lrmadmin_cmd) { + case EXECUTE_RA: + call_id = resource_operation(lrmd, rscid_arg_tmp, argc, optind, argv); + if (call_id < 0) { + if ( call_id == -2 ) { + cl_log(LOG_ERR, "Failed to operate " + "resource %s due to parameter error." + , argv[optind]); + ret_value = -3; + } + if ( call_id == -1 ) { + cl_log(LOG_WARNING, "Failed! No such " + "resource %s.", argv[optind]); + ret_value = -2; + } + else { + cl_log(LOG_ERR, "Failed to operate " + "resource %s due to unknown error." + , argv[optind]); + ret_value = -3; + } + ASYN_OPS = FALSE; + } else { + /* Return value: HA_OK = 1 Or HA_FAIL = 0 */ + if ( call_id == 0 ) { + cl_log(LOG_ERR, "Resource operation " + "failed." ); + ret_value = -3; + ASYN_OPS = FALSE; + } else { + ASYN_OPS = TRUE; + } + } + break; + + case RA_METADATA: + ra_metadata(lrmd, argc, optind, argv); + ASYN_OPS = FALSE; + break; + case RA_PROVIDER: + ra_provider(lrmd, argc, optind, argv); + ASYN_OPS = FALSE; + break; + + case SET_PARAM: + set_param(lrmd, argc, optind, argv); + ASYN_OPS = FALSE; + break; + + case GET_PARAM: + get_param(lrmd, argc, optind, argv); + ASYN_OPS = FALSE; + break; + + case ADD_RSC: + if (add_resource(lrmd, rscid_arg_tmp, argc, optind, argv) == 0) { + printf("Succeeded in adding this resource.\n"); + } else { + printf("Failed to add this resource.\n"); + ret_value = -3; + } + ASYN_OPS = FALSE; + break; + + case DEL_RSC: + /* Return value: HA_OK = 1 Or HA_FAIL = 0 */ + if (lrmd->lrm_ops->delete_rsc(lrmd, rscid_arg_tmp)==1) { + printf("Succeeded in deleting this resource.\n"); + } else { + printf("Failed to delete this resource.\n"); + ret_value = -3; + } + ASYN_OPS = FALSE; + break; + + case FAIL_RSC: + /* Return value: HA_OK = 1 Or HA_FAIL = 0 */ + if (fail_resource(lrmd, rscid_arg_tmp, + argc-optind, argv+optind) == 1) + { + printf("Succeeded in failing the resource.\n"); + } else { + printf("Failed to fail the resource.\n"); + ret_value = -3; + } + ASYN_OPS = FALSE; + break; + + case FLUSH: + lrm_rsc = get_lrm_rsc(lrmd, rscid_arg_tmp); + if (!(lrm_rsc)) { + ret_value = -3; + } else { + /* Return value: HA_OK = 1 Or HA_FAIL = 0 */ + if (lrm_rsc->ops->flush_ops(lrm_rsc) == 1 ) { + printf("Succeeded in flushing.\n"); + } else { + printf("Failed to flush.\n"); + ret_value = -3; + } + lrm_free_rsc(lrm_rsc); + } + + ASYN_OPS = FALSE; + break; + + case RACLASS_SUPPORTED: + raclass_list = lrmd->lrm_ops-> + get_rsc_class_supported(lrmd); + printf("There are %d RA classes supported:\n", + g_list_length(raclass_list)); + if (raclass_list) { + g_list_foreach(raclass_list, g_print_stringitem_and_free, + NULL); + g_list_free(raclass_list); + ret_value = LSB_EXIT_OK; + } else { + printf("No RA classes found!\n"); + ret_value = -3; + } + + ASYN_OPS = FALSE; + break; + + case RATYPE_SUPPORTED: + ratype_list = lrmd->lrm_ops-> + get_rsc_type_supported(lrmd, raclass); + printf("There are %d RAs:\n", g_list_length(ratype_list)); + if (ratype_list) { + g_list_foreach(ratype_list, g_print_rainfo_item_and_free, + NULL); + g_list_free(ratype_list); + } + + ASYN_OPS = FALSE; + break; + case ALL_RA_METADATA: + all_meta = lrmd->lrm_ops->get_all_type_metadata(lrmd, raclass); + if (all_meta) { + g_hash_table_foreach(all_meta, g_print_meta, NULL); + g_hash_table_destroy(all_meta); + } + ASYN_OPS = FALSE; + break; + case LIST_ALLRSC: + rscid_list = lrmd->lrm_ops->get_all_rscs(lrmd); + if (rscid_list) { + g_list_foreach(rscid_list, g_get_rsc_description + , lrmd); + g_list_free(rscid_list); + } else + printf("Currently no resources are managed by " + "LRM.\n"); + + ASYN_OPS = FALSE; + break; + + case INF_RSC: + lrm_rsc = get_lrm_rsc(lrmd, rscid_arg_tmp); + if (!(lrm_rsc)) { + ret_value = -3; + } else { + print_rsc_inf(lrm_rsc); + lrm_free_rsc(lrm_rsc); + } + + ASYN_OPS = FALSE; + break; + + case RSC_STATE: + lrm_rsc = get_lrm_rsc(lrmd, rscid_arg_tmp); + if (!(lrm_rsc)) { + ret_value = -3; + } else { + state_flag_t cur_state = LRM_RSC_IDLE; + GList * ops_queue; + ops_queue = lrm_rsc->ops->get_cur_state(lrm_rsc, + &cur_state); + printf("resource state:%s\n", + cur_state==LRM_RSC_IDLE? + "LRM_RSC_IDLE":"LRM_RSC_BUSY"); + + printf("The resource %d operations' " + "information:\n" + , g_list_length(ops_queue)); + if (ops_queue) { + g_list_foreach(ops_queue, + g_print_ops, + NULL); + lrm_free_op_list(ops_queue); + } + lrm_free_rsc(lrm_rsc); + } + + ASYN_OPS = FALSE; + break; + + + default: + fprintf(stderr, "Option %c is not supported yet.\n", + option_char); + ret_value = -1; + ASYN_OPS = FALSE; + break; + } + + if (ASYN_OPS) { + G_main_add_IPC_Channel(G_PRIORITY_LOW, lrmd->lrm_ops->ipcchan(lrmd), + FALSE, lrmd_output_dispatch, lrmd, NULL); + if (TIMEOUT > 0) { + Gmain_timeout_add(TIMEOUT, lrm_op_timeout, &ret_value); + } + + mainloop = g_main_new(FALSE); + printf( "Waiting for lrmd to callback...\n"); + g_main_run(mainloop); + } + + lrmd->lrm_ops->signoff(lrmd); + return ret_value; +} + +static gboolean +lrm_op_timeout(gpointer data) +{ + int * idata = data; + + printf("ERROR: This operation has timed out - no result from lrmd.\n"); + + *idata = -5; + g_main_quit(mainloop); + return FALSE; +} + +static gboolean +lrmd_output_dispatch(IPC_Channel* notused, gpointer user_data) +{ + ll_lrm_t *lrm = (ll_lrm_t*)user_data; + lrm->lrm_ops->rcvmsg(lrm, FALSE); + + g_main_quit(mainloop); + return TRUE; +} + +static void +lrm_op_done_callback(lrm_op_t* op) +{ + if (!op) { + cl_log(LOG_ERR, "In callback function, op is NULL pointer."); + ret_value = -3; + return; + } + + printf("----------------operation--------------\n"); + printf("type:%s\n", op->op_type); + if ( (0 == STRNCMP_CONST(op->op_type, "status") + || 0 == STRNCMP_CONST(op->op_type, "monitor")) && (op->rc == 7) ) { + printf("operation status:%s\n", status_msg[LRM_OP_DONE-LRM_OP_PENDING]); + printf("op_status: %d\n", LRM_OP_DONE); + } else { + printf("operation status:%s\n", status_msg[(op->op_status + - LRM_OP_PENDING) % DIMOF(status_msg)]); + printf("op_status: %d\n", op->op_status); + } + printf("return code: %d\n", op->rc); + printf("output data: \n%s\n", (op->output ? op->output : "[null]")); + printf("---------------------------------------\n\n"); + ret_value = op->rc; +} + +static int +resource_operation(ll_lrm_t * lrmd, char *rsc_id, int argc, int optind, char * argv[]) +{ + GHashTable * params_ht = NULL; + lrm_op_t op = lrm_zero_op; + lrm_rsc_t * lrm_rsc; + int call_id; + + if ((argc - optind) < 3) { + cl_log(LOG_ERR,"Not enough parameters."); + return -2; + } + + lrm_rsc = lrmd->lrm_ops->get_rsc(lrmd, rsc_id); + if (!lrm_rsc) { + return -1; + } + + op.op_type = argv[optind]; + op.timeout = atoi(argv[optind+1]); + + /* When op.timeout!=0, plus additional 1s. Or lrmadmin may time out before + the normal operation result returned from lrmd. This may be redudant, + but harmless. */ + if (0 < op.timeout ) { + TIMEOUT = op.timeout + 1000; + } + op.interval = atoi(argv[optind+2]); + op.user_data = NULL; + op.user_data_len = 0; + if (0 == strcmp(argv[optind+3], "EVERYTIME")) { + op.target_rc = EVERYTIME; + } + else + if (0 == strcmp(argv[optind+3], "CHANGED")) { + op.target_rc = CHANGED; + } + else { + op.target_rc = atoi(argv[optind+3]); + } + + if ((argc - optind) > 3) { + if (0 > transfer_cmd_params(argc, optind+4, argv, + lrm_rsc->class, ¶ms_ht) ) { + return -2; + } + } + op.params = params_ht; + + call_id = lrm_rsc->ops->perform_op(lrm_rsc, &op); + lrm_free_rsc(lrm_rsc); + if (params_ht) { + g_hash_table_foreach(params_ht, free_stritem_of_hashtable, NULL); + g_hash_table_destroy(params_ht); + } + return call_id; +} +static int +ra_metadata(ll_lrm_t * lrmd, int argc, int optind, char * argv[]) +{ + const char * class = argv[optind-1]; + const char * type = argv[optind]; + const char * provider = argv[optind+1]; + char* metadata; + + if(argc < 5) { + cl_log(LOG_ERR,"Not enough parameters."); + return -2; + } + + if (0 == strncmp(provider,"NULL",strlen("NULL"))) { + provider=NULL; + } + + metadata = lrmd->lrm_ops->get_rsc_type_metadata(lrmd, class, type, provider); + if (NULL!=metadata) { + printf ("%s\n", metadata); + g_free (metadata); + } + return 0; +} + +static int +ra_provider(ll_lrm_t * lrmd, int argc, int optind, char * argv[]) +{ + const char * class = argv[optind-1]; + const char * type = argv[optind]; + GList* providers = NULL; + GList* provider = NULL; + + if(argc < 4) { + cl_log(LOG_ERR,"Not enough parameters."); + return -2; + } + + providers = lrmd->lrm_ops->get_rsc_provider_supported(lrmd,class,type); + + while (NULL != (provider = g_list_first(providers))) { + printf("%s\n",(char*)provider->data); + g_free(provider->data); + providers = g_list_remove(providers, provider->data); + } + g_list_free(providers); + return 0; +} + +static int +add_resource(ll_lrm_t * lrmd, char *rsc_id, int argc, int optind, char * argv[]) +{ + const char * class = argv[optind]; + const char * type = argv[optind+1]; + const char * provider = argv[optind+2]; + GHashTable * params_ht = NULL; + int tmp_ret; + + if ((argc - optind) < 3) { + cl_log(LOG_ERR,"Not enough parameters."); + return -2; + } + + if (0 == strncmp(provider, "NULL", strlen("NULL"))) { + provider=NULL; + } + + /* delete Hashtable */ + if ((argc - optind) > 3) { + if ( 0 > transfer_cmd_params(argc, optind+3, argv, class, + ¶ms_ht) ) { + return -1; + } + } + + tmp_ret = lrmd->lrm_ops->add_rsc(lrmd, rsc_id, class, + type, provider, params_ht); + + /*delete params_ht*/ + if (params_ht) { + g_hash_table_foreach(params_ht, free_stritem_of_hashtable, NULL); + g_hash_table_destroy(params_ht); + } + + return (tmp_ret ? 0 : -1); /* tmp_ret is HA_OK=1 or HA_FAIL=0 */ +} + +static int +fail_resource(ll_lrm_t * lrmd, char *rsc_id, int optc, char *opts[]) +{ + int fail_rc = 0; + const char * reason = NULL; + + if (optc > 2) { + cl_log(LOG_ERR,"Bad usage."); + return -2; + } + + if (optc >= 1) + fail_rc = atoi(opts[0]); + if (optc == 2) + reason = opts[1]; + + return lrmd->lrm_ops->fail_rsc(lrmd, rsc_id, fail_rc, reason); +} + +static int +get_param(ll_lrm_t * lrmd, int argc, int optind, char * argv[]) +{ + const char *name = argv[optind-1]; + char *value; + + if ((argc - optind) != 0) { + cl_log(LOG_ERR,"Bad usage."); + return -2; + } + value = lrmd->lrm_ops->get_lrmd_param(lrmd, name); + printf("%s: %s\n", name, value); + return 0; +} + +static int +set_param(ll_lrm_t * lrmd, int argc, int optind, char * argv[]) +{ + const char *name = argv[optind-1]; + const char *value = argv[optind]; + + if ((argc - optind) != 1) { + cl_log(LOG_ERR,"Bad usage."); + return -2; + } + return lrmd->lrm_ops->set_lrmd_param(lrmd, name, value); +} + +static int +transfer_cmd_params(int amount, int start, char * argv[], const char * class, +GHashTable ** params_ht) +{ + int i, len_tmp; + char * delimit, * key, * value; + char buffer[21]; + + if (amount < start) { + return -1; + } + + if ( strncmp("ocf", class, strlen("ocf"))==0 + || strncmp("stonith", class, strlen("stonith"))==0) { + *params_ht = g_hash_table_new(g_str_hash, g_str_equal); + + for (i=start; ilen+1); + strncpy(params_str, gstr_tmp->str, gstr_tmp->len+1); + g_string_free(gstr_tmp, TRUE); + } else if ( strncmp("lsb", class, strlen("lsb")) == 0 + || strncmp("heartbeat", class, strlen("heartbeat")) == 0 ) { + ht_size = g_hash_table_size(ht); + if (ht_size == 0) { + return NULL; + } + tmp_str = g_new(gchar, ht_size*ARGVI_MAX_LEN); + memset(tmp_str, ' ', ht_size*ARGVI_MAX_LEN); + tmp_str[ht_size*ARGVI_MAX_LEN-1] = '\0'; + g_hash_table_foreach(ht, normal_params_hash_to_str, &tmp_str); + gstr_tmp = g_string_new(""); + for (i=0; i< ht_size; i++) { + gstr_tmp = g_string_append(gstr_tmp + , tmp_str + i*ARGVI_MAX_LEN ); + gstr_tmp = g_string_append(gstr_tmp, " "); + } + params_str = g_new(gchar, gstr_tmp->len+1); + strncpy(params_str, gstr_tmp->str, gstr_tmp->len+1); + g_string_free(gstr_tmp, TRUE); + } else { + fprintf(stderr, "Not supported resource agent class.\n"); + } + + return params_str; +} + +static void +g_print_stringitem_and_free(gpointer data, gpointer user_data) +{ + printf("%s\n", (char*)data); + g_free(data); +} + +static void +g_print_rainfo_item_and_free(gpointer data, gpointer user_data) +{ + printf("%s\n", (char *)data); + g_free(data); +} + + +static void +g_print_ops(gpointer data, gpointer user_data) +{ + lrm_op_t* op = (lrm_op_t*)data; + GString * param_gstr; + time_t run_at=0, rcchange_at=0; + + if (NULL == op) { + cl_log(LOG_ERR, "%s:%d: op==NULL" + , __FUNCTION__, __LINE__); + return; + } + + param_gstr = g_string_new(""); + g_hash_table_foreach(op->params, ocf_params_hash_to_str, ¶m_gstr); + + if( op->t_run ) + run_at=(time_t)op->t_run; + if( op->t_rcchange ) + rcchange_at=(time_t)op->t_rcchange; + printf(" operation '%s' [call_id=%d]:\n" + " start_delay=%d, interval=%d, timeout=%d, app_name=%s\n" + " rc=%d (%s), op_status=%d (%s)\n" + , nullcheck(op->op_type), op->call_id + , op->start_delay, op->interval, op->timeout + , nullcheck(op->app_name), op->rc + , rc_msg[(op->rc-EXECRA_EXEC_UNKNOWN_ERROR) % DIMOF(rc_msg)] + , op->op_status + , status_msg[(op->op_status-LRM_OP_PENDING) % DIMOF(status_msg)] + ); + if( op->t_run || op->t_rcchange ) + printf(" run at: %s" + " last rc change at: %s" + " queue time: %lums, exec time: %lums\n" + , op->t_run ? ctime(&run_at) : "N/A\n" + , op->t_rcchange ? ctime(&rcchange_at) : "N/A\n" + , op->queue_time, op->exec_time + ); + printf(" parameters: %s\n", param_gstr->str); + g_string_free(param_gstr, TRUE); +} + +static void +g_get_rsc_description(gpointer data, gpointer user_data) +{ + ll_lrm_t* lrmd = (ll_lrm_t *)user_data; + lrm_rsc_t * lrm_rsc; + char rsc_id_tmp[RID_LEN]; + + if (!(user_data)) { + return; + } + + memset(rsc_id_tmp, '\0', RID_LEN); + strncpy(rsc_id_tmp, data, RID_LEN-1); + + lrm_rsc = lrmd->lrm_ops->get_rsc(lrmd, rsc_id_tmp); + if (lrm_rsc) { + print_rsc_inf(lrm_rsc); + lrm_free_rsc(lrm_rsc); + } else + cl_log(LOG_ERR, "Invalid resource id: %s.", + rsc_id_tmp); + + g_free(data); +} +static void +g_print_meta(gpointer key, gpointer value, gpointer user_data) +{ + printf("%s\n", (const char*)key); + printf("%s\n", (const char*)value); +} +static void +print_rsc_inf(lrm_rsc_t * lrm_rsc) +{ + char rscid_str_tmp[RID_LEN]; + char * tmp = NULL; + + if (!lrm_rsc) { + return; + } + + rscid_str_tmp[RID_LEN-1] = '\0'; + strncpy(rscid_str_tmp, lrm_rsc->id, RID_LEN-1); + printf("\nResource ID:%s\n", rscid_str_tmp); + printf("Resource agent class:%s\n", lrm_rsc->class); + printf("Resource agent type:%s\n", lrm_rsc->type); + printf("Resource agent provider:%s\n" + , lrm_rsc->provider?lrm_rsc->provider:"default"); + + if (lrm_rsc->params) { + tmp = params_hashtable_to_str(lrm_rsc->class, + lrm_rsc->params); + printf("Resource agent parameters:%s\n" + , (tmp == NULL) ? "No parameter" : tmp); + if (tmp != NULL) { + g_free(tmp); + } + } +} + +static void +free_stritem_of_hashtable(gpointer key, gpointer value, gpointer user_data) +{ + /*printf("key=%s value=%s\n", (char *)key, (char *)value);*/ + g_free(key); + g_free(value); +} + +static void +ocf_params_hash_to_str(gpointer key, gpointer value, gpointer user_data) +{ + GString * gstr_tmp = *(GString **)user_data; + gstr_tmp = g_string_append(gstr_tmp, (char*)key); + gstr_tmp = g_string_append(gstr_tmp, "="); + gstr_tmp = g_string_append(gstr_tmp, (char *)value); + gstr_tmp = g_string_append(gstr_tmp, " "); +} + +static void +normal_params_hash_to_str(gpointer key, gpointer value, gpointer user_data) +{ + gint key_int; + + gchar * str_tmp = *(gchar **) user_data; + if (str_tmp == NULL ) { + return; + } + + key_int = atoi((char *)key) - 1; + if( key_int < 0 ) { + return; + } + strncpy(str_tmp + key_int * ARGVI_MAX_LEN, (char*)value, + ARGVI_MAX_LEN - 1); +} + +static lrm_rsc_t * +get_lrm_rsc(ll_lrm_t * lrmd, char * rscid) +{ + char uuid_str_tmp[RID_LEN]; + lrm_rsc_t * lrm_rsc; + lrm_rsc = lrmd->lrm_ops->get_rsc(lrmd, rscid); + if (!(lrm_rsc)) { + uuid_str_tmp[RID_LEN-1] = '\0'; + strncpy(uuid_str_tmp, rscid, RID_LEN-1); + cl_log(LOG_ERR,"Resource %s does not exist.", uuid_str_tmp); + } + return lrm_rsc; +} + diff --git a/lrm/admin/lrmadmin.txt b/lrm/admin/lrmadmin.txt new file mode 100644 index 0000000..739aa70 --- /dev/null +++ b/lrm/admin/lrmadmin.txt @@ -0,0 +1,60 @@ +# LRM Admin Command-line Interface +# It's a draft +NAME + lrmadmin - Local Resource Manager Commander-line Daministrator Tools + +SYNOPSIS +lrmadmin {-d|--daemon} + {-A|--add} [] + {-D|--delete} + {-F|--flush} + {-E|--execute} [] + {-M|--monitor} -s + [] + {-M|--monitor} {-g|-c} + {-S|--status} + {-L|--listall} + {-I|--information} + {-R|--rasupported} + {-h|--help} + +Detailed Explanation for Options + +Lrmd daemon options + {-d|--daemon} +# -s The status of lrmd: running or not running +# -r Reset lrmd (?) + +Resource options + {-A|--add} [] + Add a resource. + + {-D|--delete} + Delete a resource + + {-E|--execute} [] + Let resource agent to performance the operation + + {-F|--flush} + Clear all pending operation on the resource agnency + + {-M|--monitor} {-s|-g} + -s rscname Set a monitors on the resource agent + -g Get the information about the monitors on the + resource agent + + {-S|--status} + Get the status of current resource agent + + {-L|--listall} + List all available resource agent + + {-I|--information} + List the information about a resource + + {-R|--rasupported} + List the support types of resource agent such as OCF and etc. + +Other options + {-h|--help} + Display the help screen diff --git a/lrm/lrmd/Makefile.am b/lrm/lrmd/Makefile.am new file mode 100644 index 0000000..3680928 --- /dev/null +++ b/lrm/lrmd/Makefile.am @@ -0,0 +1,42 @@ +# +# Author: Sun Jiang Dong +# Copyright (c) 2002 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl \ + -I$(top_builddir)/linux-ha -I$(top_srcdir)/linux-ha \ + -I$(top_builddir) -I$(top_srcdir) + +halibdir = $(libdir)/@HB_PKG@ + +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la \ + $(GLIBLIB) +# $(top_builddir)/lib/apphb/libapphb.la + +halib_PROGRAMS = lrmd + +lrmd_SOURCES = lrmd.c audit.c cib_secrets.c lrmd_fdecl.h lrmd.h + +lrmd_LDFLAGS = $(top_builddir)/lib/lrm/liblrm.la \ + $(COMMONLIBS) @LIBLTDL@ \ + $(top_builddir)/lib/pils/libpils.la + +noinst_HEADERS = lrmd_fdecl.h lrmd.h + +# make lrmd's owner as hacluster:haclient? diff --git a/lrm/lrmd/audit.c b/lrm/lrmd/audit.c new file mode 100644 index 0000000..ec92dad --- /dev/null +++ b/lrm/lrmd/audit.c @@ -0,0 +1,191 @@ +/* + * Audit lrmd global data structures + * + * Author: Dejan Muhamedagic + * Copyright (c) 2007 Novell GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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 +#ifdef ENABLE_APPHB +# include +#endif + +#include +#include +#include +#include + +#ifdef DOLRMAUDITS + +extern GHashTable* clients; +extern GHashTable* resources; + +#define ptr_bad(level,p,item,text) \ + lrmd_log(level,"LRMAUDIT: 0x%lx unallocated pointer for: %s(%s)", \ + (unsigned long)p,item,text); +#define ptr_null(level,item,text) \ + lrmd_log(level,"LRMAUDIT: pointer null for: %s(%s)", \ + item,text); + +/* NB: this macro contains return */ +#define ret_on_null(p,item,text) do { \ + if( !p ) { \ + ptr_bad(LOG_INFO,p,item,text); \ + return; \ + } \ +} while(0) +#define log_on_null(p,item,text) do { \ + if( !p ) { \ + ptr_null(LOG_INFO,item,text); \ + } \ +} while(0) + +void +lrmd_audit(const char *function, int line) +{ + lrmd_log(LOG_DEBUG, "LRMAUDIT: in %s:%d",function,line); +#ifdef LRMAUDIT_CLIENTS + audit_clients(); +#endif +#ifdef LRMAUDIT_RESOURCES + audit_resources(); +#endif +} + +void +audit_clients() +{ + g_hash_table_foreach(clients, on_client, NULL); +} + +void +audit_resources() +{ + g_hash_table_foreach(resources, on_resource, NULL); +} + +void +audit_ops(GList* rsc_ops, lrmd_rsc_t* rsc, const char *desc) +{ + GList *oplist; + + for( oplist = g_list_first(rsc_ops); + oplist; oplist = g_list_next(oplist) ) + { + on_op(oplist->data, rsc, desc); + } +} + +void +on_client(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_client_t * client = (lrmd_client_t*)value; + + ret_on_null(client,"","client"); + log_on_null(client->app_name,"","app_name"); + log_on_null(client->ch_cmd,client->app_name,"ch_cmd"); + log_on_null(client->ch_cbk,client->app_name,"ch_cbk"); + log_on_null(client->g_src,client->app_name,"g_src"); + log_on_null(client->g_src_cbk,client->app_name,"g_src_cbk"); +} + +void +on_resource(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_rsc_t* rsc = (lrmd_rsc_t*)value; + + ret_on_null(rsc,"","rsc"); + ret_on_null(rsc->id,"","id"); + log_on_null(rsc->type,rsc->id,"type"); + log_on_null(rsc->class,rsc->id,"class"); + log_on_null(rsc->provider,rsc->id,"provider"); + /*log_on_null(rsc->params,rsc->id,"params");*/ + log_on_null(rsc->last_op_table,rsc->id,"last_op_table"); + log_on_null(rsc->last_op_done,rsc->id,"last_op_done"); + audit_ops(rsc->op_list,rsc,"op_list"); + audit_ops(rsc->repeat_op_list,rsc,"repeat_op_list"); +} + +void +on_op(lrmd_op_t *op, lrmd_rsc_t* rsc, const char *desc) +{ + ret_on_null(op,rsc->id,desc); + log_on_null(op->rsc_id,rsc->id,"rsc_id"); + if( strcmp(op->rsc_id,rsc->id) ) { + lrmd_log(LOG_ERR,"LRMAUDIT: rsc %s, op %s " + "op->rsc_id does not match rsc->id", + rsc->id,small_op_info(op)); + } + log_on_null(op->msg,small_op_info(op),"msg"); + if( op->rapop ) { + if( op->rapop->lrmd_op != op ) { + lrmd_log(LOG_ERR, + "LRMAUDIT: rsc %s, op %s: rapop->lrmd_op does not match op", + rsc->id,small_op_info(op)); + } + if( strcmp(op->rapop->rsc_id,op->rsc_id) ) { + lrmd_log(LOG_ERR, + "LRMAUDIT: rsc %s, op %s rapop->rsc_id does not match op->rsc_id", + rsc->id,small_op_info(op)); + } + on_ra_pipe_op(op->rapop,op,"rapop"); + } +} + +void +on_ra_pipe_op(ra_pipe_op_t *rapop, lrmd_op_t *op, const char *desc) +{ + ret_on_null(rapop,small_op_info(op),desc); + log_on_null(rapop->ra_stdout_gsource,small_op_info(op),"ra_stdout_gsource"); + log_on_null(rapop->ra_stderr_gsource,small_op_info(op),"ra_stderr_gsource"); + log_on_null(rapop->rsc_id,small_op_info(op),"rsc_id"); + log_on_null(rapop->op_type,small_op_info(op),"op_type"); + log_on_null(rapop->rsc_class,small_op_info(op),"rsc_class"); + if( strcmp(op->rsc_id,rapop->rsc_id) ) { + lrmd_log(LOG_ERR,"LRMAUDIT: %s: rapop->rsc_id " + "does not match op_rsc->id", + small_op_info(op)); + } +} + +#endif /*DOLRMAUDITS*/ diff --git a/lrm/lrmd/cib_secrets.c b/lrm/lrmd/cib_secrets.c new file mode 100644 index 0000000..612ffdb --- /dev/null +++ b/lrm/lrmd/cib_secrets.c @@ -0,0 +1,205 @@ +/* + * cib_secrets.c + * + * Author: Dejan Muhamedagic + * Copyright (c) 2011 SUSE, Attachmate + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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 + +int replace_secret_params(char *rsc_id, GHashTable* params); +static int is_magic_value(char *p); +static int check_md5_hash(char *hash, char *value); +static void add_secret_params(gpointer key, gpointer value, gpointer user_data); +static char *read_local_file(char *local_file); + +#define MAGIC "lrm://" + +static int +is_magic_value(char *p) +{ + return !strcmp(p, MAGIC); +} + +#define MD5LEN 16 +static int +check_md5_hash(char *hash, char *value) +{ + int i; + char hash2[2*MD5LEN+1]; + unsigned char binary[MD5LEN+1]; + + MD5((unsigned char *)value, strlen(value), binary); + for (i = 0; i < MD5LEN; i++) + sprintf(hash2+2*i, "%02x", binary[i]); + hash2[2*i] = '\0'; + lrmd_debug2(LOG_DEBUG + , "%s:%d: hash: %s, calculated hash: %s" + , __FUNCTION__, __LINE__, hash, hash2); + return !strcmp(hash, hash2); +} + +static char * +read_local_file(char *local_file) +{ + FILE *fp = fopen(local_file, "r"); + char buf[MAX_VALUE_LEN+1]; + char *p; + + if (!fp) { + if (errno != ENOENT) { + cl_perror("%s:%d: cannot open %s" + , __FUNCTION__, __LINE__, local_file); + } + return NULL; + } + if (!fgets(buf, MAX_VALUE_LEN, fp)) { + cl_perror("%s:%d: cannot read %s" + , __FUNCTION__, __LINE__, local_file); + return NULL; + } + /* strip white space */ + for (p = buf+strlen(buf)-1; p >= buf && isspace(*p); p--) + ; + *(p+1) = '\0'; + return g_strdup(buf); +} + +/* + * returns 0 on success or no replacements necessary + * returns -1 if replacement failed for whatever reasone + */ + +int +replace_secret_params(char *rsc_id, GHashTable* params) +{ + char local_file[FILENAME_MAX+1], *start_pname; + char hash_file[FILENAME_MAX+1], *hash; + GList *secret_params = NULL, *l; + char *key, *pvalue, *secret_value; + int rc = 0; + + /* secret_params could be cached with the resource; + * there are also parameters sent with operations + * which cannot be cached + */ + g_hash_table_foreach(params, add_secret_params, &secret_params); + if (!secret_params) /* none found? */ + return 0; + + lrmd_debug(LOG_DEBUG + , "%s:%d: replace secret parameters for resource %s" + , __FUNCTION__, __LINE__, rsc_id); + if (snprintf(local_file, FILENAME_MAX, + LRM_CIBSECRETS "/%s/", rsc_id) > FILENAME_MAX) { + lrmd_log(LOG_ERR + , "%s:%d: filename size exceeded for resource %s" + , __FUNCTION__, __LINE__, rsc_id); + return -1; + } + start_pname = local_file + strlen(local_file); + + for (l = g_list_first(secret_params); l; l = g_list_next(l)) { + key = (char *)(l->data); + pvalue = g_hash_table_lookup(params, key); + if (!pvalue) { /* this cannot really happen */ + lrmd_log(LOG_ERR + , "%s:%d: odd, no parameter %s for rsc %s found now" + , __FUNCTION__, __LINE__, key, rsc_id); + continue; + } + if ((strlen(key) + strlen(local_file)) >= FILENAME_MAX-2) { + lrmd_log(LOG_ERR + , "%s:%d: parameter name %s too big" + , __FUNCTION__, __LINE__, key); + rc = -1; + continue; + } + strcpy(start_pname, key); + secret_value = read_local_file(local_file); + if (!secret_value) { + lrmd_log(LOG_ERR + , "%s:%d: secret for rsc %s parameter %s " + "not found in " LRM_CIBSECRETS + , __FUNCTION__, __LINE__, rsc_id, key); + rc = -1; + continue; + } + strcpy(hash_file, local_file); + if (strlen(hash_file) + 5 > FILENAME_MAX) { + lrmd_log(LOG_ERR + , "%s:%d: cannot build such a long name " + "for the sign file: %s.sign" + , __FUNCTION__, __LINE__, hash_file); + } else { + strncat(hash_file, ".sign", 5); + hash = read_local_file(hash_file); + if (!check_md5_hash(hash, secret_value)) { + lrmd_log(LOG_ERR + , "%s:%d: md5 sum for rsc %s parameter %s " + "does not match" + , __FUNCTION__, __LINE__, rsc_id, key); + g_free(secret_value); + g_free(hash); + rc = -1; + continue; + } + g_free(hash); + } + g_hash_table_replace(params, g_strdup(key), secret_value); + } + g_list_free(secret_params); + return rc; +} + +static void +add_secret_params(gpointer key, gpointer value, gpointer user_data) +{ + GList **lp = (GList **)user_data; + + if (is_magic_value((char *)value)) + *lp = g_list_append(*lp, (char *)key); +} diff --git a/lrm/lrmd/lrmd.c b/lrm/lrmd/lrmd.c new file mode 100644 index 0000000..385096b --- /dev/null +++ b/lrm/lrmd/lrmd.c @@ -0,0 +1,4053 @@ +/* + * Local Resource Manager Daemon + * + * Author: Huang Zhen + * Partly contributed by Andrew Beekhof + * Copyright (c) 2004 International Business Machines + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#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 +#ifdef ENABLE_APPHB +# include +#endif +/* #include */ + +#include +#include +#include + +#include +#include + +static gboolean in_alloc_dump = FALSE; + +ProcTrack_ops ManagedChildTrackOps = { + on_ra_proc_finished, + on_ra_proc_registered, + on_ra_proc_query_name +}; + +/* msg dispatch table */ +typedef int (*msg_handler)(lrmd_client_t* client, struct ha_msg* msg); +struct msg_map +{ + const char *msg_type; + int reply_time; + msg_handler handler; + int min_priv; /* minimum privileges required */ +}; + +/* + * two ways to handle replies: + * REPLY_NOW: pack whatever the handler returned and send it + * NO_MSG: the handler will send the reply itself + */ +#define REPLY_NOW 0 +#define NO_MSG 1 +#define send_msg_now(p) \ + (p->reply_time==REPLY_NOW) + +struct msg_map msg_maps[] = { + {REGISTER, REPLY_NOW, on_msg_register, 0}, + {GETRSCCLASSES, NO_MSG, on_msg_get_rsc_classes, 0}, + {GETRSCTYPES, NO_MSG, on_msg_get_rsc_types, 0}, + {GETPROVIDERS, NO_MSG, on_msg_get_rsc_providers, 0}, + {ADDRSC, REPLY_NOW, on_msg_add_rsc, PRIV_ADMIN}, + {GETRSC, NO_MSG, on_msg_get_rsc, PRIV_ADMIN}, + {GETLASTOP, NO_MSG, on_msg_get_last_op, PRIV_ADMIN}, + {GETALLRCSES, NO_MSG, on_msg_get_all, PRIV_ADMIN}, + {DELRSC, REPLY_NOW, on_msg_del_rsc, PRIV_ADMIN}, + {FAILRSC, REPLY_NOW, on_msg_fail_rsc, PRIV_ADMIN}, + {PERFORMOP, REPLY_NOW, on_msg_perform_op, PRIV_ADMIN}, + {FLUSHOPS, REPLY_NOW, on_msg_flush_all, PRIV_ADMIN}, + {CANCELOP, REPLY_NOW, on_msg_cancel_op, PRIV_ADMIN}, + {GETRSCSTATE, NO_MSG, on_msg_get_state, PRIV_ADMIN}, + {GETRSCMETA, NO_MSG, on_msg_get_metadata, 0}, + {SETLRMDPARAM, REPLY_NOW, on_msg_set_lrmd_param, PRIV_ADMIN}, + {GETLRMDPARAM, NO_MSG, on_msg_get_lrmd_param, 0}, +}; +#define MSG_NR sizeof(msg_maps)/sizeof(struct msg_map) + +GHashTable* clients = NULL; /* a GHashTable indexed by pid */ +GHashTable* resources = NULL; /* a GHashTable indexed by rsc_id */ + +static GMainLoop* mainloop = NULL; +static int call_id = 1; +static const char* lrm_system_name = "lrmd"; +static GHashTable * RAExecFuncs = NULL; +static GList* ra_class_list = NULL; +static gboolean shutdown_in_progress = FALSE; +static unsigned long apphb_interval = 2000; /* Millisecond */ +static gboolean reg_to_apphbd = FALSE; +static int max_child_count = 4; +static int retry_interval = 1000; /* Millisecond */ +static int child_count = 0; +static IPC_Auth * auth = NULL; + +static struct { + int opcount; + int clientcount; + int rsccount; +}lrm_objectstats; + +/* define indexes into logmsg_ctrl_defs */ +#define OP_STAYED_TOO_LONG 0 +static struct logspam logmsg_ctrl_defs[] = { + { "operation stayed too long in the queue", + 10, 60, 120, /* max 10 messages in 60s, then delay for 120s */ + "configuration advice: reduce operation contention " + "either by increasing lrmd max_children or by increasing intervals " + "of monitor operations" + }, +}; + +#define set_fd_opts(fd,opts) do { \ + int flag; \ + if ((flag = fcntl(fd, F_GETFL)) >= 0) { \ + if (fcntl(fd, F_SETFL, flag|opts) < 0) { \ + cl_perror("%s::%d: fcntl", __FUNCTION__ \ + , __LINE__); \ + } \ + } else { \ + cl_perror("%s::%d: fcntl", __FUNCTION__, __LINE__); \ + } \ + } while(0) + +static ra_pipe_op_t * +ra_pipe_op_new(int child_stdout, int child_stderr, lrmd_op_t * lrmd_op) +{ + ra_pipe_op_t * rapop; + lrmd_rsc_t* rsc = NULL; + + if ( NULL == lrmd_op ) { + lrmd_log(LOG_WARNING + , "%s:%d: lrmd_op==NULL, no need to malloc ra_pipe_op" + , __FUNCTION__, __LINE__); + return NULL; + } + rapop = calloc(sizeof(ra_pipe_op_t), 1); + if ( rapop == NULL) { + lrmd_log(LOG_ERR, "%s:%d out of memory" + , __FUNCTION__, __LINE__); + return NULL; + } + rapop->first_line_read = FALSE; + + /* + * No any obviouse proof of lrmd hang in pipe read yet. + * Bug 475 may be a duplicate of bug 499. + * Anyway, via test, it's proved that NOBLOCK read will + * obviously reduce the RA execution time (bug 553). + */ + /* Let the read operation be NONBLOCK */ + set_fd_opts(child_stdout,O_NONBLOCK); + set_fd_opts(child_stderr,O_NONBLOCK); + + /* there's so much code duplication here */ + rapop->ra_stdout_fd = child_stdout; + if (rapop->ra_stdout_fd <= STDERR_FILENO) { + lrmd_log(LOG_ERR, "%s: invalid stdout fd [%d]" + , __FUNCTION__, rapop->ra_stdout_fd); + } + rapop->ra_stdout_gsource = G_main_add_fd(G_PRIORITY_HIGH + , child_stdout, FALSE, handle_pipe_ra_stdout + , rapop, destroy_pipe_ra_stdout); + + rapop->ra_stderr_fd = child_stderr; + if (rapop->ra_stderr_fd <= STDERR_FILENO) { + lrmd_log(LOG_ERR, "%s: invalid stderr fd [%d]" + , __FUNCTION__, rapop->ra_stderr_fd); + } + rapop->ra_stderr_gsource = G_main_add_fd(G_PRIORITY_HIGH + , child_stderr, FALSE, handle_pipe_ra_stderr + , rapop, destroy_pipe_ra_stderr); + + rapop->lrmd_op = lrmd_op; + + rapop->op_type = strdup(ha_msg_value(lrmd_op->msg, F_LRM_OP)); + rapop->rsc_id = strdup(lrmd_op->rsc_id); + rsc = lookup_rsc(lrmd_op->rsc_id); + if (rsc == NULL) { + lrmd_debug(LOG_WARNING + , "%s::%d: the rsc (id=%s) does not exist" + , __FUNCTION__, __LINE__, lrmd_op->rsc_id); + rapop->rsc_class = NULL; + } else { + rapop->rsc_class = strdup(rsc->class); + } + + return rapop; +} + +static void +ra_pipe_op_destroy(ra_pipe_op_t * rapop) +{ + CHECK_ALLOCATED(rapop, "ra_pipe_op", ); + + if ( NULL != rapop->ra_stdout_gsource) { + G_main_del_fd(rapop->ra_stdout_gsource); + rapop->ra_stdout_gsource = NULL; + } + + if ( NULL != rapop->ra_stderr_gsource) { + G_main_del_fd(rapop->ra_stderr_gsource); + rapop->ra_stderr_gsource = NULL; + } + + if (rapop->ra_stdout_fd >= STDERR_FILENO) { + close(rapop->ra_stdout_fd); + rapop->ra_stdout_fd = -1; + }else if (rapop->ra_stdout_fd >= 0) { + lrmd_log(LOG_ERR, "%s: invalid stdout fd %d" + , __FUNCTION__, rapop->ra_stdout_fd); + } + if (rapop->ra_stderr_fd >= STDERR_FILENO) { + close(rapop->ra_stderr_fd); + rapop->ra_stderr_fd = -1; + }else if (rapop->ra_stderr_fd >= 0) { + lrmd_log(LOG_ERR, "%s: invalid stderr fd %d" + , __FUNCTION__, rapop->ra_stderr_fd); + } + rapop->first_line_read = FALSE; + + free(rapop->rsc_id); + free(rapop->op_type); + rapop->op_type = NULL; + free(rapop->rsc_class); + rapop->rsc_class = NULL; + + if (rapop->lrmd_op != NULL) { + rapop->lrmd_op->rapop = NULL; + rapop->lrmd_op = NULL; + } + + free(rapop); +} + +static void +lrmd_op_destroy(lrmd_op_t* op) +{ + CHECK_ALLOCATED(op, "op", ); + --lrm_objectstats.opcount; + + if (op->exec_pid > 1) { + lrmd_log(LOG_CRIT + , "%s: lingering operation process %d, op %s" + , __FUNCTION__, op->exec_pid, small_op_info(op)); + return; + } + lrmd_debug2(LOG_DEBUG, "%s: free the %s with address %p" + ,__FUNCTION__, op_info(op), op); + ha_msg_del(op->msg); + op->msg = NULL; + if( op->rsc_id ) { + free(op->rsc_id); + op->rsc_id = NULL; + } + op->exec_pid = 0; + if ( op->rapop != NULL ) { + op->rapop->lrmd_op = NULL; + op->rapop = NULL; + } + op->first_line_ra_stdout[0] = EOS; + + if( op->repeat_timeout_tag ) { + Gmain_timeout_remove(op->repeat_timeout_tag); + } + free(op); +} + +static lrmd_op_t* +lrmd_op_new(void) +{ + lrmd_op_t* op = (lrmd_op_t*)calloc(sizeof(lrmd_op_t),1); + + if (op == NULL) { + lrmd_log(LOG_ERR, "lrmd_op_new(): out of memory when " + "calloc a lrmd_op_t."); + return NULL; + } + op->rsc_id = NULL; + op->msg = NULL; + op->exec_pid = -1; + op->repeat_timeout_tag = 0; + op->rapop = NULL; + op->first_line_ra_stdout[0] = EOS; + op->t_recv = time_longclock(); + op->t_perform = zero_longclock; + op->t_done = zero_longclock; + op->t_rcchange = zero_longclock; + op->t_lastlogmsg = zero_longclock; + + memset(op->killseq, 0, sizeof(op->killseq)); + ++lrm_objectstats.opcount; + return op; +} + +static lrmd_op_t* +lrmd_op_copy(const lrmd_op_t* op) +{ + lrmd_op_t* ret; + + ret = lrmd_op_new(); + if (NULL == ret) { + return NULL; + } + /* Do a "shallow" copy */ + *ret = *op; + /* + * Some things, like timer ids and child pids are duplicated here + * but can be destroyed in one copy, but kept intact + * in the other, to later be destroyed. + * This isn't a complete disaster, since the timer ids aren't + * pointers, but it's still untidy at the least. + * Be sure and care of this situation when using this function. + */ + /* Do a "deep" copy of the message structure */ + ret->rapop = NULL; + ret->msg = ha_msg_copy(op->msg); + ret->rsc_id = strdup(op->rsc_id); + ret->rapop = NULL; + ret->first_line_ra_stdout[0] = EOS; + ret->repeat_timeout_tag = 0; + ret->exec_pid = -1; + ret->t_recv = op->t_recv; + ret->t_perform = op->t_perform; + ret->t_done = op->t_done; + ret->t_rcchange = op->t_rcchange; + ret->is_copy = TRUE; + ret->is_cancelled = FALSE; + ret->weight = op->weight; + return ret; +} + +static +const char * +op_status_to_str(int op_status) +{ + static char whatwasthat[25]; + switch (op_status) { + case LRM_OP_DONE: + return "LRM_OP_DONE"; + case LRM_OP_CANCELLED: + return "LRM_OP_CANCELLED"; + case LRM_OP_TIMEOUT: + return "LRM_OP_TIMEOUT"; + case LRM_OP_NOTSUPPORTED: + return "LRM_OP_NOTSUPPORTED"; + case -1: + return "N/A (-1)"; + default: + break; + } + snprintf(whatwasthat, sizeof(whatwasthat), "UNDEFINED STATUS: %d?", op_status); + return whatwasthat; +} +static +const char * +op_target_rc_to_str(int target) +{ + static char whatwasthat[25]; + switch (target) { + case EVERYTIME: + return "EVERYTIME"; + case CHANGED: + return "CHANGED"; + default: + break; + } + snprintf(whatwasthat, sizeof(whatwasthat) + ,"UNDEFINED TARGET_RC: %d", target); + return whatwasthat; +} + +/* + * We need a separate function to dump out operations for + * debugging. Then we wouldn't have to have the code for this + * inline. In particular, we could then call this from on_op_done() + * which would shorten and simplify that code - which could use + * the help :-) + */ + + +/* Debug oriented funtions */ +static gboolean debug_level_adjust(int nsig, gpointer user_data); + +static void +lrmd_op_dump(const lrmd_op_t* op, const char * text) +{ + int op_status = -1; + int target_rc = -1; + const char * pidstat; + longclock_t now = time_longclock(); + + CHECK_ALLOCATED(op, "op", ); + if (op->exec_pid < 1 + || ((kill(op->exec_pid, 0) < 0) && ESRCH == errno)) { + pidstat = "not running"; + }else{ + pidstat = "running"; + } + ha_msg_value_int(op->msg, F_LRM_OPSTATUS, &op_status); + ha_msg_value_int(op->msg, F_LRM_TARGETRC, &target_rc); + lrmd_debug(LOG_DEBUG + , "%s: lrmd_op: %s status: %s, target_rc=%s, client pid %d call_id" + ": %d, child pid: %d (%s) %s %s" + , text, op_info(op), op_status_to_str(op_status) + , op_target_rc_to_str(target_rc) + , op->client_id, op->call_id, op->exec_pid, pidstat + , (op->is_copy ? "copy" : "original") + , (op->is_cancelled ? "cancelled" : "")); + lrmd_debug(LOG_DEBUG + , "%s: lrmd_op2: rt_tag: %d, interval: %d, delay: %d" + , text, op->repeat_timeout_tag + , op->interval, op->delay); + lrmd_debug(LOG_DEBUG + , "%s: lrmd_op3: t_recv: %ldms, t_add: %ldms" + ", t_perform: %ldms, t_done: %ldms, t_rcchange: %ldms" + , text, tm2age(op->t_recv), tm2age(op->t_addtolist) + , tm2age(op->t_perform), tm2age(op->t_done), tm2age(op->t_rcchange)); + lrmd_rsc_dump(op->rsc_id, text); +} + + +static void +lrmd_client_destroy(lrmd_client_t* client) +{ + CHECK_ALLOCATED(client, "client", ); + + --lrm_objectstats.clientcount; + /* + * Delete direct references to this client + * and repeating operations it might have scheduled + */ + unregister_client(client); + if (client->app_name) { + free(client->app_name); + client->app_name = NULL; + } + free(client); +} + +static lrmd_client_t* +lrmd_client_new(void) +{ + lrmd_client_t* client; + client = calloc(sizeof(lrmd_client_t), 1); + if (client == NULL) { + lrmd_log(LOG_ERR, "lrmd_client_new(): out of memory when " + "calloc lrmd_client_t."); + return NULL; + } + client->g_src = NULL; + client->g_src_cbk = NULL; + ++lrm_objectstats.clientcount; + return client; +} +static void +lrmd_client_dump(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_client_t * client = (lrmd_client_t*)value; + CHECK_ALLOCATED(client, "client", ); + if(!client) { + return; + } + + lrmd_debug(LOG_DEBUG, "client name: %s, client pid: %d" + ", client uid: %d, gid: %d, last request: %s" + ", last op in: %s, lastop out: %s" + ", last op rc: %s" + , lrm_str(client->app_name) + , client->pid + , client->uid, client->gid + , client->lastrequest + , ctime(&client->lastreqstart) + , ctime(&client->lastreqend) + , ctime(&client->lastrcsent) + ); + if (!client->ch_cmd) { + lrmd_debug(LOG_DEBUG, "NULL client ch_cmd in %s()", __FUNCTION__); + }else{ + lrmd_debug(LOG_DEBUG + , "Command channel status: %d, read queue addr: %p, write queue addr: %p" + , client->ch_cmd->ch_status + , client->ch_cmd->recv_queue + , client->ch_cmd->send_queue ); + + if (client->ch_cmd->recv_queue && client->ch_cmd->send_queue) { + lrmd_debug(LOG_DEBUG, "read Qlen: %ld, write Qlen: %ld" + , (long)client->ch_cmd->recv_queue->current_qlen + , (long)client->ch_cmd->send_queue->current_qlen); + } + } + if (!client->ch_cbk) { + lrmd_debug(LOG_DEBUG, "NULL client ch_cbk in %s()", __FUNCTION__); + }else{ + lrmd_debug(LOG_DEBUG + , "Callback channel status: %d, read Qlen: %ld, write Qlen: %ld" + , client->ch_cbk->ch_status + , (long)client->ch_cbk->recv_queue->current_qlen + , (long)client->ch_cbk->send_queue->current_qlen); + } +} +static void +lrmd_dump_all_clients(void) +{ + static gboolean incall = FALSE; + + if (incall) { + return; + } + + incall = TRUE; + + lrmd_debug(LOG_DEBUG, "%d clients connected to lrmd" + , g_hash_table_size(clients)); + + g_hash_table_foreach(clients, lrmd_client_dump, NULL); + incall = FALSE; +} + +static void +lrmd_rsc_destroy(lrmd_rsc_t* rsc) +{ + LRMAUDIT(); + CHECK_ALLOCATED(rsc, "resource", ); + --lrm_objectstats.rsccount; + if( rsc->op_list || rsc->repeat_op_list ) { + lrmd_log(LOG_ERR, "%s: refusing to remove resource %s" + " which is still holding operations" + , __FUNCTION__, lrm_str(rsc->id)); + return; + } else { + lrmd_debug(LOG_DEBUG, "%s: removing resource %s" + , __FUNCTION__, lrm_str(rsc->id)); + } + g_hash_table_remove(resources, rsc->id); + if (rsc->id) { + free(rsc->id); + rsc->id = NULL; + } + if (rsc->type) { + free(rsc->type); + rsc->type = NULL; + } + if (rsc->class) { + free(rsc->class); + rsc->class = NULL; + } + if (rsc->provider) { + free(rsc->provider); + rsc->provider = NULL; + } + if (NULL != rsc->params) { + free_str_table(rsc->params); + rsc->params = NULL; + } + if (rsc->last_op_table) { + g_hash_table_foreach_remove(rsc->last_op_table + , free_str_hash_pair, NULL); + g_hash_table_destroy(rsc->last_op_table); + rsc->last_op_table = NULL; + } + if (rsc->last_op_done) { + lrmd_op_destroy(rsc->last_op_done); + rsc->last_op_done = NULL; + } + + if (rsc->delay_timeout > 0) { + Gmain_timeout_remove(rsc->delay_timeout); + rsc->delay_timeout = (guint)0; + } + + free(rsc); + LRMAUDIT(); +} + +static lrmd_rsc_t* +lrmd_rsc_new(const char * id, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc; + rsc = (lrmd_rsc_t *)calloc(sizeof(lrmd_rsc_t),1); + if (rsc == NULL) { + lrmd_log(LOG_ERR, "%s: out of memory when calloc " + "a lrmd_rsc_t", __FUNCTION__); + return NULL; + } + rsc->delay_timeout = (guint)0; + if (id) { + rsc->id = strdup(id); + } + if (msg) { + rsc->type = strdup(ha_msg_value(msg, F_LRM_RTYPE)); + rsc->class = strdup(ha_msg_value(msg, F_LRM_RCLASS)); + if (NULL == ha_msg_value(msg, F_LRM_RPROVIDER)) { + lrmd_log(LOG_NOTICE, "%s(): No %s field in message" + , __FUNCTION__, F_LRM_RPROVIDER); + }else{ + rsc->provider = strdup(ha_msg_value(msg, F_LRM_RPROVIDER)); + if (rsc->provider == NULL) { + goto errout; + } + } + if (rsc->id == NULL + || rsc->type == NULL + || rsc->class == NULL) { + goto errout; + } + } + g_hash_table_insert(resources, strdup(id), rsc); + ++lrm_objectstats.rsccount; + return rsc; +errout: + lrmd_rsc_destroy(rsc); /* violated property */ /* Or so BEAM thinks :-) */ + rsc = NULL; + return rsc; +} + +static void +dump_op(gpointer key, gpointer val, gpointer data) +{ + lrmd_op_t* lrmd_op = (lrmd_op_t*) val; + + lrmd_op_dump(lrmd_op, "rsc->last_op_table"); +} +static void +dump_op_table(gpointer key, gpointer val, gpointer data) +{ + GHashTable* table = (GHashTable*) val; + + g_hash_table_foreach(table, dump_op, data); +} +static void +lrmd_rsc_dump(char* rsc_id, const char * text) +{ + static gboolean incall = FALSE; + GList* oplist; + lrmd_rsc_t* rsc=NULL; + + if( rsc_id ) { + rsc = lookup_rsc(rsc_id); + } else { + lrmd_debug(LOG_INFO + , "%s:%d: the rsc_id is NULL" + , __FUNCTION__, __LINE__); + return; + } + CHECK_ALLOCATED(rsc, "rsc", ); + if(!rsc) { + return; + } + + /* Avoid infinite recursion loops... */ + if (incall) { + return; + } + incall = TRUE; + /* TODO: Dump params and last_op_table FIXME */ + + lrmd_debug(LOG_DEBUG, "%s: BEGIN resource dump", text); + lrmd_debug(LOG_DEBUG, "%s: resource %s/%s/%s/%s" + , text + , lrm_str(rsc->id) + , lrm_str(rsc->type) + , lrm_str(rsc->class) + , lrm_str(rsc->provider)); + + lrmd_debug(LOG_DEBUG, "%s: rsc->op_list...", text); + for(oplist = g_list_first(rsc->op_list); oplist; + oplist = g_list_next(oplist)) { + lrmd_op_dump(oplist->data, "rsc->op_list"); + } + + lrmd_debug(LOG_DEBUG, "%s: rsc->repeat_op_list...", text); + for(oplist = g_list_first(rsc->repeat_op_list); oplist; + oplist=g_list_next(oplist)) { + lrmd_op_dump(oplist->data, "rsc->repeat_op_list"); + } + + if (rsc->last_op_done != NULL) { + lrmd_debug(LOG_DEBUG, "%s: rsc->last_op_done...", text); + lrmd_op_dump(rsc->last_op_done, "rsc->last_op_done"); + } + else { + lrmd_debug(LOG_DEBUG, "%s: rsc->last_op_done==NULL", text); + } + if (rsc->last_op_table) { + g_hash_table_foreach(rsc->last_op_table,dump_op_table,NULL); + } + else { + lrmd_debug(LOG_DEBUG, "%s: rsc->last_op_table==NULL", text); + } + lrmd_debug(LOG_DEBUG, "%s: END resource dump", text); + incall = FALSE; +}; +static void +dump_id_rsc_pair(gpointer key, gpointer value, gpointer user_data) +{ + char* rid = (char*)key; + char* text = (char*)user_data; + lrmd_rsc_dump(rid,text); +} +static void +lrmd_dump_all_resources(void) +{ + static gboolean incall = FALSE; + char text[]= "lrmd_dump_all_resources"; + if (incall) { + return; + } + incall = TRUE; + + lrmd_debug(LOG_DEBUG, "%d resources are managed by lrmd" + , g_hash_table_size(resources)); + g_hash_table_foreach(resources, dump_id_rsc_pair, text); + incall = FALSE; +} + + +#if 0 +static void +lrm_debug_running_op(lrmd_op_t* op, const char * text) +{ + char cmd[256]; + lrmd_op_dump(op, text); + CHECK_ALLOCATED(op, "op", ); + if (op->exec_pid >= 1) { + /* This really ought to use our logger + * So... it might not get forwarded to the central machine + * if you're testing with CTS -- FIXME!!! + */ + snprintf(cmd, sizeof(cmd) + , "ps -l -f -s %d | logger -p daemon.info -t 'T/O PS:'" + , op->exec_pid); + lrmd_debug(LOG_DEBUG, "Running [%s]", cmd); + if (system(cmd) != 0) { + lrmd_log(LOG_ERR, "Running [%s] failed", cmd); + } + snprintf(cmd, sizeof(cmd) + , "ps axww | logger -p daemon.info -t 't/o ps:'"); + lrmd_debug(LOG_DEBUG, "Running [%s]", cmd); + if (system(cmd) != 0) { + lrmd_log(LOG_ERR, "Running [%s] failed", cmd); + } + } +} +#endif +int +main(int argc, char ** argv) +{ + int req_restart = TRUE; + int req_status = FALSE; + int req_stop = FALSE; + + int argerr = 0; + int flag; + + while ((flag = getopt(argc, argv, OPTARGS)) != EOF) { + switch(flag) { + case 'h': /* Help message */ + usage(lrm_system_name, LSB_EXIT_OK); + break; + case 'v': /* Debug mode, more logs*/ + ++debug_level; + break; + case 's': /* Status */ + req_status = TRUE; + break; + case 'k': /* Stop (kill) */ + req_stop = TRUE; + break; + case 'r': /* Restart */ + req_restart = TRUE; + break; + /* Register to apphbd then monitored by it */ + case 'm': + reg_to_apphbd = TRUE; + break; + case 'i': /* Get apphb interval */ + if (optarg) { + apphb_interval = atoi(optarg); + } + break; + default: + ++argerr; + break; + } + } + + if (optind > argc) { + ++argerr; + } + + if (argerr) { + usage(lrm_system_name, LSB_EXIT_GENERIC); + } + + cl_log_set_entity(lrm_system_name); + cl_log_enable_stderr(debug_level?TRUE:FALSE); + cl_log_set_facility(HA_LOG_FACILITY); + + /* Use logd if it's enabled by heartbeat */ + cl_inherit_logging_environment(0); + + if (req_status){ + return init_status(PID_FILE, lrm_system_name); + } + + if (req_stop){ + return init_stop(PID_FILE); + } + + if (req_restart) { + init_stop(PID_FILE); + } + + return init_start(); +} + +int +init_status(const char *pid_file, const char *client_name) +{ + long pid = cl_read_pidfile(pid_file); + + if (pid > 0) { + fprintf(stderr, "%s is running [pid: %ld]\n" + , client_name, pid); + return LSB_STATUS_OK; + } + fprintf(stderr, "%s is stopped.\n", client_name); + return LSB_STATUS_STOPPED; +} + +int +init_stop(const char *pid_file) +{ + long pid; + int rc = LSB_EXIT_OK; + + + + if (pid_file == NULL) { + lrmd_log(LOG_ERR, "No pid file specified to kill process"); + return LSB_EXIT_GENERIC; + } + pid = cl_read_pidfile(pid_file); + + if (pid > 0) { + if (CL_KILL((pid_t)pid, SIGTERM) < 0) { + rc = (errno == EPERM + ? LSB_EXIT_EPERM : LSB_EXIT_GENERIC); + fprintf(stderr, "Cannot kill pid %ld\n", pid); + }else{ + lrmd_log(LOG_INFO, + "Signal sent to pid=%ld," + " waiting for process to exit", + pid); + + while (CL_PID_EXISTS(pid)) { + sleep(1); + } + } + } + return rc; +} + +static const char usagemsg[] = "[-srkhv]\n\ts: status\n\tr: restart" + "\n\tk: kill\n\tm: register to apphbd\n\ti: the interval of apphb\n\t" + "h: help\n\tv: debug\n"; + +void +usage(const char* cmd, int exit_status) +{ + FILE* stream; + + stream = exit_status ? stderr : stdout; + + fprintf(stream, "usage: %s %s", cmd, usagemsg); + fflush(stream); + + exit(exit_status); +} +/* + * In design, the lrmd should not know the meaning of operation type + * and the meaning of rc. This function is just for logging. + */ +static void +warning_on_active_rsc(gpointer key, gpointer value, gpointer user_data) +{ + int op_status, rc; + const char* op_type; + + lrmd_rsc_t* rsc = (lrmd_rsc_t*)value; + if (rsc->last_op_done != NULL) { + if (HA_OK != ha_msg_value_int(rsc->last_op_done->msg + , F_LRM_OPSTATUS, &op_status)) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in UNKNOWN status." \ + "(last op done is damaged..)" + ,rsc->id); + return; + } + op_type = ha_msg_value(rsc->last_op_done->msg, F_LRM_OP); + if (op_status != LRM_OP_DONE) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in UNKNOWN status." \ + "(last op %s finished without LRM_OP_DONE status.)" + ,rsc->id, op_type); + return; + } + if (HA_OK != ha_msg_value_int(rsc->last_op_done->msg + , F_LRM_RC, &rc)) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in UNKNOWN status." \ + "(last op done is damaged..)" + ,rsc->id); + return; + } + if((rc == 0) && + (STRNCMP_CONST(op_type,"start") ==0 + ||STRNCMP_CONST(op_type,"monitor") ==0 + ||STRNCMP_CONST(op_type,"status") ==0)) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in RUNNING status." \ + "(last op %s finished with rc 0.)" + ,rsc->id, op_type); + return; + } + if ((rc !=0 ) && + (STRNCMP_CONST(op_type,"start") ==0 + ||STRNCMP_CONST(op_type,"stop") ==0)) { + lrmd_debug(LOG_WARNING + ,"resource %s is left in UNKNOWN status." \ + "(last op %s finished with rc %d.)" + ,rsc->id, op_type, rc); + return; + } + } +} + +static gboolean +lrm_shutdown(void) +{ + lrmd_log(LOG_INFO,"lrmd is shutting down"); + if (mainloop != NULL && g_main_is_running(mainloop)) { + g_hash_table_foreach(resources, warning_on_active_rsc, NULL); + g_main_quit(mainloop); + }else { + exit(LSB_EXIT_OK); + } + return FALSE; +} +static void +has_pending_op(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_rsc_t* rsc = (lrmd_rsc_t*)value; + int* result = (int*)user_data; + if (rsc->op_list != NULL) { + *result = TRUE; + } +} +static gboolean +can_shutdown() +{ + int has_ops = FALSE; + g_hash_table_foreach(resources, has_pending_op, &has_ops); + + return !has_ops; +} +gboolean +sigterm_action(int nsig, gpointer user_data) +{ + shutdown_in_progress = TRUE; + + if (can_shutdown()) { + lrm_shutdown(); + } else { + lrmd_log(LOG_INFO, "sigterm_action: shutdown postponed, some operations are still running"); + } + return TRUE; +} + +static void +register_pid(gboolean do_fork, + gboolean (*shutdown)(int nsig, gpointer userdata)) +{ + int j; + + umask(022); + + for (j=0; j < 3; ++j) { + close(j); + (void)open("/dev/null", j == 0 ? O_RDONLY : O_WRONLY); + } + CL_IGNORE_SIG(SIGINT); + CL_IGNORE_SIG(SIGHUP); + CL_DEFAULT_SIG(SIGPIPE); + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGTERM + , shutdown, NULL, NULL); + cl_signal_set_interrupt(SIGTERM, 1); + cl_signal_set_interrupt(SIGCHLD, 1); + /* At least they are harmless, I think. ;-) */ + cl_signal_set_interrupt(SIGINT, 0); + cl_signal_set_interrupt(SIGHUP, 0); +} + +static int +init_using_apphb(void) +{ +#ifdef ENABLE_APPHB + char lrmd_instance[40]; + + if (reg_to_apphbd == FALSE) { + return -1; + } + + snprintf(lrmd_instance, sizeof(lrmd_instance), "%s_%ld" + , lrm_system_name, (long)getpid()); + if (apphb_register(lrm_system_name, lrmd_instance) != 0) { + lrmd_log(LOG_ERR, "Failed when trying to register to apphbd."); + lrmd_log(LOG_ERR, "Maybe apphbd is not running. Quit."); + return -1; + } + lrmd_log(LOG_INFO, "Registered to apphbd."); + + apphb_setinterval(apphb_interval); + apphb_setwarn(apphb_interval*APPHB_WARNTIME_FACTOR); + + Gmain_timeout_add(apphb_interval - APPHB_INTVL_DETLA, emit_apphb, NULL); +#endif + return 0; +} + +static gboolean +emit_apphb(gpointer data) +{ +#ifdef ENABLE_APPHB + if (reg_to_apphbd == FALSE) { + return FALSE; + } + + if (apphb_hb() != 0) { + lrmd_log(LOG_ERR, "emit_apphb: Failed to emit an apphb."); + reg_to_apphbd = FALSE; + return FALSE; + }; +#endif + return TRUE; +} + +static void +calc_max_children() +{ +#ifdef _SC_NPROCESSORS_ONLN + int nprocs; + + nprocs = sysconf(_SC_NPROCESSORS_ONLN); + if( nprocs < 1 ) { + lrmd_log(LOG_WARNING, "%s: couldn't get the number of processors" + , __FUNCTION__); + } else { + if( nprocs/2 > max_child_count ) { + max_child_count = nprocs/2; + } + lrmd_log(LOG_INFO, "max-children set to %d " + "(%d processors online)", max_child_count, nprocs); + return; + } +#else + lrmd_log(LOG_WARNING, "%s: cannot get the number of processors " + "on this platform", __FUNCTION__); +#endif + lrmd_log(LOG_INFO, "max-children set to %d", max_child_count); +} + +/* main loop of the daemon*/ +int +init_start () +{ + DIR* dir = NULL; + PILPluginUniv * PluginLoadingSystem = NULL; + struct dirent* subdir; + char* dot = NULL; + char* ra_name = NULL; + int len; + IPC_WaitConnection* conn_cmd = NULL; + IPC_WaitConnection* conn_cbk = NULL; + + GHashTable* conn_cmd_attrs; + GHashTable* conn_cbk_attrs; + + char path[] = IPC_PATH_ATTR; + char cmd_path[] = LRM_CMDPATH; + char cbk_path[] = LRM_CALLBACKPATH; + + PILGenericIfMgmtRqst RegisterRqsts[]= { + {"RAExec", &RAExecFuncs, NULL, NULL, NULL}, + { NULL, NULL, NULL, NULL, NULL} }; + + if( getenv("LRMD_MAX_CHILDREN") ) { + set_lrmd_param("max-children", getenv("LRMD_MAX_CHILDREN")); + } else { + calc_max_children(); + } + + qsort(msg_maps, MSG_NR, sizeof(struct msg_map), msg_type_cmp); + + if (cl_lock_pidfile(PID_FILE) < 0) { + lrmd_log(LOG_ERR, "already running: [pid %d].", cl_read_pidfile(PID_FILE)); + lrmd_log(LOG_ERR, "Startup aborted (already running). Shutting down."); + exit(100); + } + + register_pid(FALSE, sigterm_action); + + /* load RA plugins */ + PluginLoadingSystem = NewPILPluginUniv (HA_PLUGIN_DIR); + PILLoadPlugin(PluginLoadingSystem, "InterfaceMgr", "generic", + &RegisterRqsts); + + /* + * FIXME!!! + * Much of the code through the end of the next loop is + * unnecessary - The plugin system will do this for you quite + * nicely. And, it does it portably, too... + */ + + dir = opendir(LRM_PLUGIN_DIR); + if (NULL == dir) { + lrmd_log(LOG_ERR, "main: can not open RA plugin dir "LRM_PLUGIN_DIR); + lrmd_log(LOG_ERR, "Startup aborted (no RA plugin). Shutting down."); + exit(100); + } + + while ( NULL != (subdir = readdir(dir))) { + /* skip . and .. */ + if ( '.' == subdir->d_name[0]) { + continue; + } + /* skip the other type files */ + if (NULL == strstr(subdir->d_name, ".so")) { + continue; + } + /* remove the ".so" */ + dot = strchr(subdir->d_name,'.'); + if (NULL != dot) { + len = (int)(dot - subdir->d_name); + ra_name = g_strndup(subdir->d_name,len); + } + else { + ra_name = g_strdup(subdir->d_name); + } + PILLoadPlugin(PluginLoadingSystem , "RAExec", ra_name, NULL); + ra_class_list = g_list_append(ra_class_list,ra_name); + } + closedir(dir); dir = NULL; /* Don't forget to close 'dir' */ + + /* + *create the waiting connections + *one for register the client, + *the other is for create the callback channel + */ + + /*Create a waiting connection to accept command connect from client*/ + conn_cmd_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(conn_cmd_attrs, path, cmd_path); + conn_cmd = ipc_wait_conn_constructor(IPC_ANYTYPE, conn_cmd_attrs); + g_hash_table_destroy(conn_cmd_attrs); + if (NULL == conn_cmd) { + lrmd_log(LOG_ERR, + "main: can not create wait connection for command."); + lrmd_log(LOG_ERR, "Startup aborted (can't create comm channel). Shutting down."); + + exit(100); + } + + /*Create a source to handle new connect rquests for command*/ + G_main_add_IPC_WaitConnection( G_PRIORITY_HIGH, conn_cmd, NULL, FALSE, + on_connect_cmd, conn_cmd, NULL); + + /* auth is static, but used when clients register */ + auth = ipc_str_to_auth(ADMIN_UIDS, strlen(ADMIN_UIDS), "", 0); + + /* + * Create a waiting connection to accept the callback connect from client + */ + conn_cbk_attrs = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(conn_cbk_attrs, path, cbk_path); + conn_cbk = ipc_wait_conn_constructor( IPC_ANYTYPE, conn_cbk_attrs); + g_hash_table_destroy(conn_cbk_attrs); + + if (NULL == conn_cbk) { + lrmd_log(LOG_ERR, + "main: can not create wait connection for callback."); + lrmd_log(LOG_ERR, "Startup aborted (can't create comm channel). Shutting down."); + exit(100); + } + + /*Create a source to handle new connect rquests for callback*/ + G_main_add_IPC_WaitConnection( G_PRIORITY_HIGH, conn_cbk, NULL, FALSE, + on_connect_cbk, conn_cbk, NULL); + + /* our child signal handling involves calls with + * unpredictable timing; so we raise the limit to + * reduce the number of warnings + */ + set_sigchld_proctrack(G_PRIORITY_HIGH,10*DEFAULT_MAXDISPATCHTIME); + + lrmd_log(LOG_INFO, "enabling coredumps"); + /* Although lrmd can count on the parent to enable coredump, still + * set it here for test, when start manually. + */ + cl_cdtocoredir(); + cl_enable_coredumps(TRUE); + + /* Allow us to always take a "secure" core dump + * We might have STONITH logins and passwords, etc. in our address + * space - so we need to make sure it's only readable by root. + * Calling this function accomplishes that. + */ + cl_set_all_coredump_signal_handlers(); + if( drop_privs(0, 0) ) { /* become "nobody" */ + lrmd_log(LOG_WARNING,"%s: failed to drop privileges: %s" + , __FUNCTION__, strerror(errno)); + } + + /* + * Add the signal handler for SIGUSR1, SIGUSR2. + * They are used to change the debug level. + */ + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGUSR1, + debug_level_adjust, NULL, NULL); + G_main_add_SignalHandler(G_PRIORITY_HIGH, SIGUSR2, + debug_level_adjust, NULL, NULL); + + /* + * alloc memory for client table and resource table + */ + clients = g_hash_table_new(g_int_hash, g_int_equal); + if (clients == NULL) { + cl_log(LOG_ERR, "can not new hash table clients"); + exit(100); + } + resources = g_hash_table_new_full(g_str_hash + , g_str_equal, free, NULL); + if (resources == NULL) { + cl_log(LOG_ERR, "can not new hash table resources"); + exit(100); + } + + /*Create the mainloop and run it*/ + mainloop = g_main_new(FALSE); + lrmd_debug(LOG_DEBUG, "main: run the loop..."); + lrmd_log(LOG_INFO, "Started."); + + /* apphb initializing */ + init_using_apphb(); + emit_apphb(NULL); /* Avoid warning */ + + g_main_run(mainloop); + + emit_apphb(NULL); + if (reg_to_apphbd == TRUE) { +#ifdef ENABLE_APPHB + apphb_unregister(); +#endif + reg_to_apphbd = FALSE; + } + + if( return_to_orig_privs() ) { + cl_perror("%s: failed to raise privileges", __FUNCTION__); + } + conn_cmd->ops->destroy(conn_cmd); + conn_cmd = NULL; + + conn_cbk->ops->destroy(conn_cbk); + conn_cbk = NULL; + + ipc_destroy_auth(auth); + if (cl_unlock_pidfile(PID_FILE) == 0) { + lrmd_debug(LOG_DEBUG, "[%s] stopped", lrm_system_name); + } + return 0; +} + +/* + *GLoop Message Handlers + */ +gboolean +on_connect_cmd (IPC_Channel* ch, gpointer user_data) +{ + lrmd_client_t* client = NULL; + + /* check paremeters */ + if (NULL == ch) { + lrmd_log(LOG_ERR, "on_connect_cmd: channel is null"); + return TRUE; + } + /* create new client */ + /* the register will be finished in on_msg_register */ + client = lrmd_client_new(); + if (client == NULL) { + return TRUE; + } + client->app_name = NULL; + client->ch_cmd = ch; + client->g_src = G_main_add_IPC_Channel(G_PRIORITY_DEFAULT, + ch, FALSE, on_receive_cmd, (gpointer)client, + on_remove_client); + + + return TRUE; +} + +gboolean +on_connect_cbk (IPC_Channel* ch, gpointer user_data) +{ + /*client connect for create the second channel for call back*/ + pid_t pid; + const char* type = NULL; + struct ha_msg* msg = NULL; + lrmd_client_t* client = NULL; + + if (NULL == ch) { + lrmd_log(LOG_ERR, "on_connect_cbk: channel is null"); + return TRUE; + } + + /* Isn't this kind of a tight timing assumption ?? + * This operation is non-blocking -- IIRC + * Maybe this should be moved to the input dispatch function + * for this channel when we make a GSource from it. + * FIXME + */ + + /*get the message, ends up in socket_waitin */ + msg = msgfromIPC_noauth(ch); + if (NULL == msg) { + lrmd_log(LOG_ERR, "on_connect_cbk: can not receive msg"); + return TRUE; + } + + /*check if it is a register message*/ + type = ha_msg_value(msg, F_LRM_TYPE); + if (0 != STRNCMP_CONST(type, REGISTER)) { + lrmd_log(LOG_ERR, "on_connect_cbk: received a message which is " + "not known by lrmd."); + ha_msg_del(msg); + send_ret_msg(ch, HA_FAIL); + return TRUE; + } + + /*get the pid of client */ + if (HA_OK != ha_msg_value_int(msg, F_LRM_PID, &pid)) { + lrmd_log(LOG_ERR, "on_connect_cbk: can not get pid from the " + "message."); + ha_msg_del(msg); + send_ret_msg(ch, HA_FAIL); + return TRUE; + } + ha_msg_del(msg); + + /*get the client in the client list*/ + client = lookup_client(pid); + if (NULL == client) { + lrmd_log(LOG_ERR, "on_connect_cbk: donnot find the client " + "[pid:%d] in internal client list. ", pid); + send_ret_msg(ch, HA_FAIL); + return TRUE; + } + if (client->ch_cbk != NULL) { + client->ch_cbk->ops->destroy(client->ch_cbk); + client->ch_cbk = NULL; + } + client->g_src_cbk = G_main_add_IPC_Channel(G_PRIORITY_DEFAULT + , ch, FALSE,NULL,NULL,NULL); + + /*fill the channel of callback field*/ + client->ch_cbk = ch; + send_ret_msg(ch, HA_OK); + return TRUE; +} + +int +msg_type_cmp(const void *p1, const void *p2) +{ + + return strncmp( + ((const struct msg_map *)p1)->msg_type, + ((const struct msg_map *)p2)->msg_type, + MAX_MSGTYPELEN); +} + +gboolean +on_receive_cmd (IPC_Channel* ch, gpointer user_data) +{ + struct msg_map *msgmap_p, in_type; + lrmd_client_t* client = NULL; + struct ha_msg* msg = NULL; + char *msg_s; + int ret = FALSE; + + client = (lrmd_client_t*)user_data; + + if (IPC_DISCONNECT == ch->ch_status) { + lrmd_debug(LOG_DEBUG, + "on_receive_cmd: the IPC to client [pid:%d] disconnected." + , client->pid); + return FALSE; + } + + if (!ch->ops->is_message_pending(ch)) { + lrmd_debug(LOG_DEBUG, "on_receive_cmd: no pending message in IPC " + "channel."); + return TRUE; + } + + + /*get the message */ + msg = msgfromIPC(ch, 0); + if (NULL == msg) { + lrmd_log(LOG_ERR, "on_receive_cmd: can not receive messages."); + return TRUE; + } + + if (TRUE == shutdown_in_progress ) { + send_ret_msg(ch,HA_FAIL); + ha_msg_del(msg); + lrmd_log(LOG_INFO, "%s: new requests denied," \ + " we're about to shutdown", __FUNCTION__); + return TRUE; + } + + /*dispatch the message*/ + in_type.msg_type = ha_msg_value(msg, F_LRM_TYPE); + if( !in_type.msg_type ) { + LOG_FAILED_TO_GET_FIELD(F_LRM_TYPE); + ha_msg_del(msg); + return TRUE; + } + msg_s = msg2string(msg); + if( msg_s ) { + lrmd_debug2(LOG_DEBUG,"dumping request: %s",msg_s); + free(msg_s); + } + + if (!(msgmap_p = bsearch(&in_type, msg_maps, + MSG_NR, sizeof(struct msg_map), msg_type_cmp) + )) { + + lrmd_log(LOG_ERR, "on_receive_cmd: received an unknown msg"); + } else { + if( !client->app_name && msgmap_p->handler != on_msg_register ) { + ha_msg_del(msg); + lrmd_log(LOG_ERR, "%s: the client needs to register first", __FUNCTION__); + return FALSE; + } + + if( client->priv_lvl < msgmap_p->min_priv ) { + ha_msg_del(msg); + lrmd_log(LOG_ERR, "%s: insufficient privileges for %s (pid %d)" + , __FUNCTION__ + , client->app_name, client->pid); + return FALSE; + } + strncpy(client->lastrequest, in_type.msg_type, sizeof(client->lastrequest)); + client->lastrequest[sizeof(client->lastrequest)-1]='\0'; + client->lastreqstart = time(NULL); + /*call the handler of the message*/ + ret = msgmap_p->handler(client, msg); + client->lastreqend = time(NULL); + + /*return rc to client if need*/ + if (send_msg_now(msgmap_p)) { + send_ret_msg(ch, ret); + client->lastrcsent = time(NULL); + } + } + + /*delete the msg*/ + ha_msg_del(msg); + + return ret; +} +static void +remove_repeat_op_from_client(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_rsc_t* rsc = (lrmd_rsc_t*)value; + pid_t pid = GPOINTER_TO_UINT(user_data); /* pointer cast as int */ + + (void)flush_all(&(rsc->repeat_op_list),pid); +} + +/* Remove all direct pointer references to 'client' before destroying it */ +static int +unregister_client(lrmd_client_t* client) +{ + CHECK_ALLOCATED(client, "client", HA_FAIL); + + if (NULL == lookup_client(client->pid)) { + lrmd_log(LOG_ERR,"%s: can not find client %s [pid %d] when try " + "to unregister it." + , __FUNCTION__ + , client->app_name, client->pid); + return HA_FAIL; + } + + /* Search all resources for repeating ops this client owns */ + g_hash_table_foreach(resources + , remove_repeat_op_from_client, GUINT_TO_POINTER(client->pid)); + + /* Remove from clients */ + g_hash_table_remove(clients, (gpointer)&client->pid); + + lrmd_debug(LOG_DEBUG, "%s: client %s [pid:%d] is unregistered" + , __FUNCTION__ + , client->app_name + , client->pid); + return HA_OK; +} + +void +on_remove_client (gpointer user_data) +{ + lrmd_client_t* client = (lrmd_client_t*) user_data; + + CHECK_ALLOCATED(client, "client", ); + if (client->g_src != NULL) { + G_main_del_IPC_Channel(client->g_src); + } + if (client->g_src_cbk != NULL) { + G_main_del_IPC_Channel(client->g_src_cbk); + } + lrmd_client_destroy(client); + +} + + +/* This function called when its time to run a repeating operation now */ +/* Move op from repeat queue to running queue */ +gboolean +on_repeat_op_readytorun(gpointer data) +{ + lrmd_op_t* op = NULL; + lrmd_rsc_t* rsc = NULL; + + LRMAUDIT(); + op = (lrmd_op_t*)data; + CHECK_ALLOCATED(op, "op", FALSE ); + + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "%s: exec_pid is 0 (internal error)" + , __FUNCTION__); + return FALSE; + } + + lrmd_debug2(LOG_DEBUG + , "%s: remove operation %s from the repeat operation list and " + "add it to the operation list" + , __FUNCTION__, op_info(op)); + + if( op->rsc_id ) { + rsc = lookup_rsc(op->rsc_id); + } else { + lrmd_debug(LOG_INFO + , "%s: the rsc_id in op %s is NULL" + , __FUNCTION__, op_info(op)); + return FALSE; + } + + rsc->repeat_op_list = g_list_remove(rsc->repeat_op_list, op); + if (op->repeat_timeout_tag != 0) { + op->repeat_timeout_tag = (guint)0; + } + + op->exec_pid = -1; + + if (!shutdown_in_progress) { + add_op_to_runlist(rsc,op); + } + perform_op(rsc); + + LRMAUDIT(); + return FALSE; +} + +/*LRM Message Handlers*/ +int +on_msg_register(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_client_t* exist = NULL; + const char* app_name = NULL; + + CHECK_ALLOCATED(msg, "register message", HA_FAIL); + + app_name = ha_msg_value(msg, F_LRM_APP); + if (NULL == app_name) { + lrmd_log(LOG_ERR, "on_msg_register: no app_name in " + "the ha message."); + return HA_FAIL; + } + client->app_name = strdup(app_name); + + return_on_no_int_value(msg, F_LRM_PID, &client->pid); + return_on_no_int_value(msg, F_LRM_GID, (int *)&client->gid); + return_on_no_int_value(msg, F_LRM_UID, (int *)&client->uid); + + exist = lookup_client(client->pid); + if (NULL != exist) { + g_hash_table_remove(clients, (gpointer)&client->pid); + on_remove_client(exist); + lrmd_log(LOG_NOTICE, + "on_msg_register: the client [pid:%d] already exists in " + "internal client list, let remove it at first." + , client->pid); + } + + /* everybody can connect, but only certain UIDs can perform + * administrative actions + */ + if( client->ch_cmd->ops->verify_auth(client->ch_cmd, auth) == IPC_OK ) + client->priv_lvl = PRIV_ADMIN; + else + client->priv_lvl = 0; + + g_hash_table_insert(clients, (gpointer)&client->pid, client); + lrmd_debug(LOG_DEBUG, "on_msg_register:client %s [%d] registered" + , client->app_name + , client->pid); + + return HA_OK; +} + +int +on_msg_get_rsc_classes(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + lrmd_debug2(LOG_DEBUG + , "on_msg_get_rsc_classes:client [%d] wants to get rsc classes" + , client->pid); + + ret = create_lrm_ret(HA_OK, 4); + CHECK_RETURN_OF_CREATE_LRM_RET; + + cl_msg_add_list(ret,F_LRM_RCLASS,ra_class_list); + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_rsc_classes: cannot send the ret mesage"); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_get_rsc_types(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + struct RAExecOps * RAExec = NULL; + GList* types = NULL; + GList* type; + const char* rclass = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + ret = create_lrm_ret(HA_OK,5); + CHECK_RETURN_OF_CREATE_LRM_RET; + + rclass = ha_msg_value(msg, F_LRM_RCLASS); + if (rclass == NULL) { + lrmd_log(LOG_ERR, "on_msg_get_rsc_types: cannot get the " + "resource class field from the message."); + send_ret_msg(client->ch_cmd, HA_FAIL); + return HA_FAIL; + } + + lrmd_debug2(LOG_DEBUG, "on_msg_get_rsc_types: the client [pid:%d] " + "wants to get resource types of resource class %s" + , client->pid, rclass); + + RAExec = g_hash_table_lookup(RAExecFuncs,rclass); + + if (NULL == RAExec) { + lrmd_log(LOG_NOTICE, "on_msg_get_rsc_types: can not find this " + "RA class %s.", rclass); + } else { + if (0 <= RAExec->get_resource_list(&types) && types != NULL) { + cl_msg_add_list(ret, F_LRM_RTYPES, types); + while (NULL != (type = g_list_first(types))) { + types = g_list_remove_link(types, type); + g_free(type->data); + g_list_free_1(type); + } + g_list_free(types); + } + } + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_rsc_types: can not send the ret message."); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_get_rsc_providers(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + struct RAExecOps * RAExec = NULL; + GList* providers = NULL; + GList* provider = NULL; + const char* rclass = NULL; + const char* rtype = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + ret = create_lrm_ret(HA_OK,5); + CHECK_RETURN_OF_CREATE_LRM_RET; + + rclass = ha_msg_value(msg, F_LRM_RCLASS); + rtype = ha_msg_value(msg, F_LRM_RTYPE); + if( !rclass || !rtype ) { + lrmd_log(LOG_NOTICE + , "%s: could not retrieve resource class or type" + , __FUNCTION__); + send_ret_msg(client->ch_cmd, HA_FAIL); + return HA_FAIL; + } + + lrmd_debug2(LOG_DEBUG + , "%s: the client [%d] wants to get rsc privider of %s::%s" + , __FUNCTION__ + , client->pid + , rclass + , rtype); + + RAExec = g_hash_table_lookup(RAExecFuncs, rclass); + + if (NULL == RAExec) { + lrmd_log(LOG_NOTICE + , "%s: can not find the class %s." + , __FUNCTION__ + , rclass); + } + else { + if (0 <= RAExec->get_provider_list(rtype, &providers)) { + if (providers != NULL) { + cl_msg_add_list(ret, F_LRM_RPROVIDERS, providers); + } + while (NULL != (provider = g_list_first(providers))) { + providers = g_list_remove_link(providers, provider); + g_free(provider->data); + g_list_free_1(provider); + } + g_list_free(providers); + } + } + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_rsc_providers: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_get_metadata(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + struct RAExecOps * RAExec = NULL; + const char* rtype = NULL; + const char* rclass = NULL; + const char* provider = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + rtype = ha_msg_value(msg, F_LRM_RTYPE); + rclass = ha_msg_value(msg, F_LRM_RCLASS); + provider = ha_msg_value(msg, F_LRM_RPROVIDER); + + lrmd_debug2(LOG_DEBUG + , "%s: the client [pid:%d] wants to get rsc metadata of %s::%s::%s." + , __FUNCTION__ + , client->pid + , lrm_str(rclass) + , lrm_str(provider) + , lrm_str(rtype)); + + ret = create_lrm_ret(HA_OK, 5); + CHECK_RETURN_OF_CREATE_LRM_RET; + + RAExec = g_hash_table_lookup(RAExecFuncs,rclass); + if (NULL == RAExec) { + lrmd_log(LOG_NOTICE + , "%s: can not find the class %s." + , __FUNCTION__ + , rclass); + } + else { + char* meta = RAExec->get_resource_meta(rtype,provider); + if (NULL != meta && strlen(meta) > 0) { + if (HA_OK != ha_msg_add(ret,F_LRM_METADATA, meta)) { + LOG_FAILED_TO_ADD_FIELD("metadata"); + } + g_free(meta); + } + else { + lrmd_log(LOG_WARNING + , "%s: empty metadata for %s::%s::%s." + , __FUNCTION__ + , lrm_str(rclass) + , lrm_str(provider) + , lrm_str(rtype)); + ha_msg_mod_int(ret, F_LRM_RET, HA_FAIL); + } + } + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_metadata: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} +static void +add_rid_to_msg(gpointer key, gpointer value, gpointer user_data) +{ + char* rid = (char*)key; + struct ha_msg* msg = (struct ha_msg*)user_data; + if (HA_OK != cl_msg_list_add_string(msg,F_LRM_RID,rid)) { + LOG_FAILED_TO_ADD_FIELD("resource id"); + } +} +int +on_msg_get_all(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + lrmd_debug2(LOG_DEBUG + , "on_msg_get_all:client [%d] want to get all rsc information." + , client->pid); + + ret = create_lrm_ret(HA_OK, g_hash_table_size(resources) + 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + + g_hash_table_foreach(resources, add_rid_to_msg, ret); + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, "on_msg_get_all: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} +int +on_msg_get_rsc(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + lrmd_rsc_t* rsc = NULL; + const char* id = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + id = ha_msg_value(msg, F_LRM_RID); + + lrmd_debug2(LOG_DEBUG + , "on_msg_get_rsc: the client [pid:%d] wants to get " + "the information of the resource [rsc_id: %s]" + , client->pid, lrmd_nullcheck(id)); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_debug2(LOG_DEBUG + , "on_msg_get_rsc: no rsc with id %s." + , lrmd_nullcheck(id)); + ret = create_lrm_ret(HA_FAIL, 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + } + else { + ret = create_lrm_ret(HA_OK, 5); + CHECK_RETURN_OF_CREATE_LRM_RET; + + if (HA_OK != ha_msg_add(ret, F_LRM_RID, rsc->id) + || HA_OK != ha_msg_add(ret, F_LRM_RTYPE, rsc->type) + || HA_OK != ha_msg_add(ret, F_LRM_RCLASS, rsc->class)) { + ha_msg_del(ret); + lrmd_log(LOG_ERR, + "on_msg_get_rsc: failed to add fields to msg."); + return HA_FAIL; + } + if( rsc->provider ) { + if (HA_OK != ha_msg_add(ret, F_LRM_RPROVIDER, + rsc->provider)) { + ha_msg_del(ret); + LOG_FAILED_TO_ADD_FIELD("provider"); + return HA_FAIL; + } + } + + if ( rsc->params && + HA_OK!=ha_msg_add_str_table(ret,F_LRM_PARAM,rsc->params)) { + ha_msg_del(ret); + LOG_FAILED_TO_ADD_FIELD("parameter"); + return HA_FAIL; + } + + } + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, "on_msg_get_rsc: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_get_last_op(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + const char* op_type = NULL; + lrmd_rsc_t* rsc = NULL; + const char* rid = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + rid = ha_msg_value(msg, F_LRM_RID); + op_type = ha_msg_value(msg, F_LRM_OP); + + lrmd_debug2(LOG_DEBUG + , "on_msg_get_last_op:client %s[%d] want to get the information " + "regarding last %s op on %s" + , client->app_name, client->pid + , lrmd_nullcheck(op_type), lrmd_nullcheck(rid)); + + rsc = lookup_rsc_by_msg(msg); + if (NULL != rsc && NULL != op_type) { + GHashTable* table = g_hash_table_lookup(rsc->last_op_table + , client->app_name); + if (NULL != table ) { + lrmd_op_t* op = g_hash_table_lookup(table, op_type); + if (NULL != op) { + lrmd_debug(LOG_DEBUG + , "%s: will return op %s" + , __FUNCTION__ + , op_type); + + ret = op_to_msg(op); + if (NULL == ret) { + lrmd_log(LOG_ERR + , "%s: can't create a message with op_to_msg." + , __FUNCTION__); + + } else + if (HA_OK != ha_msg_add_int(ret + , F_LRM_OPCNT, 1)) { + LOG_FAILED_TO_ADD_FIELD("operation count"); + } + } + } + } + + if (NULL == ret) { + lrmd_log(LOG_ERR + , "%s: return ha_msg ret is null, will re-create it again." + , __FUNCTION__); + ret = create_lrm_ret(HA_OK, 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + + if (HA_OK != ha_msg_add_int(ret, F_LRM_OPCNT, 0)) { + LOG_FAILED_TO_ADD_FIELD("operation count"); + } + + } + + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, "on_msg_get_last_op: can not send the ret msg"); + } + ha_msg_del(ret); + + return HA_OK; +} + +int +on_msg_del_rsc(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc = NULL; + const char* id = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + id = ha_msg_value(msg, F_LRM_RID); + lrmd_debug2(LOG_DEBUG + , "%s: client [%d] wants to delete rsc %s" + , __FUNCTION__, client->pid, lrmd_nullcheck(id)); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, "%s: no rsc with id %s.",__FUNCTION__,id); + return -1; + } + LRMAUDIT(); + (void)flush_all(&(rsc->repeat_op_list),0); + if( flush_all(&(rsc->op_list),0) ) { + set_rsc_removal_pending(rsc); + lrmd_log(LOG_INFO, "resource %s busy, removal pending", rsc->id); + LRMAUDIT(); + return HA_RSCBUSY; /* resource is busy, removal delayed */ + } + lrmd_rsc_destroy(rsc); + LRMAUDIT(); + return HA_OK; +} + +static int +prepare_failmsg(struct ha_msg* msg, int fail_rc, const char *fail_reason) +{ + call_id++; /* use the next id */ + if (HA_OK != ha_msg_mod(msg,F_LRM_OP,ASYNC_OP_NAME) + || HA_OK != ha_msg_add(msg,F_LRM_FAIL_REASON,fail_reason) + || HA_OK != ha_msg_mod_int(msg,F_LRM_ASYNCMON_RC,fail_rc) + || HA_OK != ha_msg_mod_int(msg,F_LRM_RC,fail_rc) + || HA_OK != ha_msg_mod_int(msg,F_LRM_OPSTATUS,(int)LRM_OP_DONE) + || HA_OK != ha_msg_mod_int(msg,F_LRM_CALLID,call_id) + || HA_OK != ha_msg_mod_int(msg,F_LRM_TIMEOUT,0) + || HA_OK != ha_msg_mod_int(msg,F_LRM_INTERVAL,0) + || HA_OK != ha_msg_mod_int(msg,F_LRM_TARGETRC,EVERYTIME) + || HA_OK != ha_msg_mod_int(msg,F_LRM_DELAY,0) + ) { + lrmd_log(LOG_ERR,"%s:%d: cannot add field to a message" + , __FUNCTION__, __LINE__); + return 1; + } + return 0; +} + +static void +async_notify(gpointer key, gpointer val, gpointer data) +{ + struct ha_msg* msg = (struct ha_msg*)data; + lrmd_client_t* client; + + client = lookup_client_by_name((char *)key); + if (!client) { + lrmd_log(LOG_INFO, + "%s: client %s not found, probably signed out", __FUNCTION__, (char *)key); + } else { + send_msg(msg, client); + } +} + +int +on_msg_fail_rsc(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc; + const char* id; + int fail_rc = -1; + const char *fail_reason; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + id = ha_msg_value(msg, F_LRM_RID); + lrmd_debug2(LOG_DEBUG + , "%s: client [%d] wants to fail rsc %s" + , __FUNCTION__, client->pid, lrmd_nullcheck(id)); + + rsc = lookup_rsc_by_msg(msg); + if (!rsc) { + lrmd_log(LOG_ERR, "%s: no resource with id %s." + , __FUNCTION__, lrmd_nullcheck(id)); + return HA_FAIL; + } + fail_reason = ha_msg_value(msg,F_LRM_FAIL_REASON); + if (!fail_reason || *fail_reason == '\0') { + fail_reason = DEFAULT_FAIL_REASON; + } + if (HA_OK != ha_msg_value_int(msg,F_LRM_ASYNCMON_RC,&fail_rc) || fail_rc <= 0) { + fail_rc = DEFAULT_FAIL_RC; + } + if (prepare_failmsg(msg,fail_rc,fail_reason)) + return HA_FAIL; + lrmd_log(LOG_WARNING + , "received asynchronous failure for rsc %s (rc: %d, reason: %s)" + , lrmd_nullcheck(id), fail_rc, fail_reason); + /* notify all clients from last_op table about the failure */ + if (rsc->last_op_table) { + g_hash_table_foreach(rsc->last_op_table,async_notify,msg); + } else { + lrmd_log(LOG_INFO + , "rsc to be failed %s had no operations so far", lrmd_nullcheck(id)); + send_msg(msg, client); + } + return HA_OK; +} + +static gboolean +free_str_hash_pair(gpointer key, gpointer value, gpointer user_data) +{ + GHashTable* table = (GHashTable*) value; + free(key); + g_hash_table_foreach_remove(table, free_str_op_pair, NULL); + g_hash_table_destroy(table); + return TRUE; +} + +static gboolean +free_str_op_pair(gpointer key, gpointer value, gpointer user_data) +{ + lrmd_op_t* op = (lrmd_op_t*)value; + + if (NULL == op) { + lrmd_log(LOG_ERR, "%s(): NULL op in op_pair(%s)" , __FUNCTION__ + , (const char *)key); + }else{ + lrmd_op_destroy(op); + } + return TRUE; +} + +int +on_msg_add_rsc(lrmd_client_t* client, struct ha_msg* msg) +{ + GList* node; + gboolean ra_type_exist = FALSE; + char* class = NULL; + lrmd_rsc_t* rsc = NULL; + const char* id = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + return_on_no_value(msg, F_LRM_RID,id); + + lrmd_debug(LOG_DEBUG + , "on_msg_add_rsc:client [%d] adds resource %s" + , client->pid, lrmd_nullcheck(id)); + + if (RID_LEN <= strlen(id)) { + lrmd_log(LOG_ERR, "on_msg_add_rsc: rsc_id is too long."); + return HA_FAIL; + } + + if (NULL != lookup_rsc(id)) { + lrmd_log(LOG_ERR, "on_msg_add_rsc: same id resource exists."); + return HA_FAIL; + } + + LRMAUDIT(); + rsc = lrmd_rsc_new(id, msg); + if (rsc == NULL) { + return HA_FAIL; + } + + ra_type_exist = FALSE; + for(node=g_list_first(ra_class_list); NULL!=node; node=g_list_next(node)){ + class = (char*)node->data; + if (0 == strncmp(class, rsc->class, MAX_CLASSNAMELEN)) { + ra_type_exist = TRUE; + break; + } + } + if (!ra_type_exist) { + lrmd_log(LOG_ERR + , "on_msg_add_rsc: RA class [%s] does not exist." + , rsc->class); + lrmd_rsc_destroy(rsc); + rsc = NULL; + LRMAUDIT(); + return HA_FAIL; + } + + rsc->last_op_done = NULL; + rsc->params = ha_msg_value_str_table(msg,F_LRM_PARAM); + rsc->last_op_table = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(resources, strdup(rsc->id), rsc); + + LRMAUDIT(); + return HA_OK; +} + +static int +cancel_op(GList** listp,int cancel_op_id) +{ + GList* node = NULL; + lrmd_op_t* op = NULL; + int rc = HA_FAIL; + + for( node = g_list_first(*listp) + ; node; node = g_list_next(node) ) { + op = (lrmd_op_t*)node->data; + if( op->call_id == cancel_op_id ) { + lrmd_log(LOG_INFO + ,"%s: %s cancelled" + , __FUNCTION__, op_info(op)); + rc = flush_op(op); + if( rc != HA_RSCBUSY && rc != HA_FAIL ) { + notify_client(op); /* send notification now */ + *listp = g_list_remove(*listp, op); + remove_op_history(op); + lrmd_op_destroy(op); + } + return rc; + } + } + return rc; +} + +int +on_msg_cancel_op(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc = NULL; + int cancel_op_id = 0; + int op_cancelled = HA_OK; + + LRMAUDIT(); + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, + "%s: no resource with such id.", __FUNCTION__); + return HA_FAIL; + } + + return_on_no_int_value(msg, F_LRM_CALLID, &cancel_op_id); + + lrmd_debug2(LOG_DEBUG + , "%s:client [pid:%d] cancel the operation [callid:%d]" + , __FUNCTION__ + , client->pid + , cancel_op_id); + + if( cancel_op(&(rsc->repeat_op_list), cancel_op_id) != HA_OK ) { + op_cancelled = cancel_op(&(rsc->op_list), cancel_op_id); + } + if( op_cancelled == HA_FAIL ) { + lrmd_log(LOG_INFO, "%s: no operation with id %d", + __FUNCTION__, cancel_op_id); + } else if( op_cancelled == HA_RSCBUSY ) { + lrmd_log(LOG_INFO, "%s: operation %d running, cancel pending", + __FUNCTION__, cancel_op_id); + } else { + lrmd_debug(LOG_DEBUG, "%s: operation %d cancelled", + __FUNCTION__, cancel_op_id); + } + LRMAUDIT(); + return op_cancelled; +} + +static gboolean +flush_all(GList** listp, int client_pid) +{ + GList* node = NULL; + lrmd_op_t* op = NULL; + gboolean rsc_busy = FALSE; + + node = g_list_first(*listp); + while( node ) { + op = (lrmd_op_t*)node->data; + if (client_pid && op->client_id != client_pid) { + node = g_list_next(node); + continue; /* not the client's operation */ + } + if( flush_op(op) == HA_RSCBUSY ) { + rsc_busy = TRUE; + node = g_list_next(node); + } else if (!client_pid || op->client_id == client_pid) { + node = *listp = g_list_remove(*listp, op); + remove_op_history(op); + lrmd_op_destroy(op); + } else { + node = g_list_next(node); + } + } + return rsc_busy; +} + +int +on_msg_flush_all(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc = NULL; + const char* id = NULL; + + LRMAUDIT(); + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + return_on_no_value(msg, F_LRM_RID,id); + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, + "%s: no resource with id %s.", __FUNCTION__,id); + LRMAUDIT(); + return -1; + } + + /* when a flush request arrived, flush all pending ops */ + lrmd_debug2(LOG_DEBUG + , "%s:client [%d] flush operations" + , __FUNCTION__, client->pid); + (void)flush_all(&(rsc->repeat_op_list),0); + if( flush_all(&(rsc->op_list),0) ) { + set_rsc_flushing_ops(rsc); /* resource busy */ + lrmd_log(LOG_INFO, "resource %s busy, all flush pending", rsc->id); + LRMAUDIT(); + return HA_RSCBUSY; + } + LRMAUDIT(); + return HA_OK; +} + +int +on_msg_perform_op(lrmd_client_t* client, struct ha_msg* msg) +{ + lrmd_rsc_t* rsc = NULL; + lrmd_op_t* op; + const char* id = NULL; + int timeout = 0; + int interval = 0; + int delay = 0; + + LRMAUDIT(); + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + return_on_no_value(msg, F_LRM_RID,id); + return_on_no_int_value(msg, F_LRM_INTERVAL, &interval); + return_on_no_int_value(msg, F_LRM_TIMEOUT, &timeout); + return_on_no_int_value(msg, F_LRM_DELAY, &delay); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, + "%s: no resource with such id.", __FUNCTION__); + return -1; + } + if( rsc_frozen(rsc) ) { + lrmd_log(LOG_NOTICE, "%s: resource %s is frozen, " + "no ops can run.", __FUNCTION__, rsc->id); + return -1; + } + + call_id++; + if( !(rsc->id) ) { + lrmd_debug(LOG_ERR + , "%s:%d: the resource id is NULL" + , __FUNCTION__, __LINE__); + return -1; + } + if (HA_OK != ha_msg_add_int(msg, F_LRM_CALLID, call_id)) { + LOG_FAILED_TO_ADD_FIELD("callid"); + return -1; + } + if (HA_OK !=ha_msg_mod(msg, F_LRM_APP, client->app_name)) { + LOG_FAILED_TO_ADD_FIELD("app_name"); + return -1; + } + + op = lrmd_op_new(); + if (op == NULL) { + return -1; + } + op->call_id = call_id; + op->client_id = client->pid; + op->rsc_id = strdup(rsc->id); + op->interval = interval; + op->delay = delay; + op->weight = no_child_count(rsc) ? 0 : 1; + + op->msg = ha_msg_copy(msg); + + if( ha_msg_value_int(msg,F_LRM_COPYPARAMS,&op->copyparams) == HA_OK + && op->copyparams ) { + lrmd_debug(LOG_DEBUG + , "%s:%d: copying parameters for rsc %s" + , __FUNCTION__, __LINE__,rsc->id); + if (rsc->params) { + free_str_table(rsc->params); + } + rsc->params = ha_msg_value_str_table(msg, F_LRM_PARAM); + } + + lrmd_debug2(LOG_DEBUG + , "%s: client [%d] want to add an operation %s on resource %s." + , __FUNCTION__ + , client->pid + , op_info(op) + , NULL!=op->rsc_id ? op->rsc_id : "#EMPTY#"); + + if ( 0 < op->delay ) { + op->repeat_timeout_tag = Gmain_timeout_add(op->delay + ,on_repeat_op_readytorun, op); + rsc->repeat_op_list = + g_list_append (rsc->repeat_op_list, op); + lrmd_debug(LOG_DEBUG + , "%s: an operation %s is added to the repeat " + "operation list for delay execution" + , __FUNCTION__ + , op_info(op)); + } else { + lrmd_debug(LOG_DEBUG + , "%s: add an operation %s to the operation list." + , __FUNCTION__ + , op_info(op)); + add_op_to_runlist(rsc,op); + } + + perform_op(rsc); + + LRMAUDIT(); + return call_id; +} + +static void +send_last_op(gpointer key, gpointer value, gpointer user_data) +{ + IPC_Channel* ch = NULL; + lrmd_op_t* op = NULL; + struct ha_msg* msg = NULL; + + ch = (IPC_Channel*)user_data; + op = (lrmd_op_t*)value; + msg = op_to_msg(op); + if (msg == NULL) { + lrmd_log(LOG_ERR, "send_last_op: failed to convert an operation " + "information to a ha_msg."); + return; + } + if (HA_OK != msg2ipcchan(msg, ch)) { + lrmd_log(LOG_ERR, "send_last_op: can not send a message."); + } + ha_msg_del(msg); +} + +int +on_msg_get_state(lrmd_client_t* client, struct ha_msg* msg) +{ + int op_count = 0; + lrmd_rsc_t* rsc = NULL; + GList* node; + struct ha_msg* ret = NULL; + lrmd_op_t* op = NULL; + struct ha_msg* op_msg = NULL; + const char* id = NULL; + GHashTable* last_ops = NULL; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + id = ha_msg_value(msg,F_LRM_RID); + lrmd_debug2(LOG_DEBUG + , "%s: client [%d] want to get the state of resource %s" + , __FUNCTION__, client->pid, lrmd_nullcheck(id)); + + rsc = lookup_rsc_by_msg(msg); + if (NULL == rsc) { + lrmd_log(LOG_ERR, "on_msg_get_state: no resource with id %s." + , lrmd_nullcheck(id)); + send_ret_msg(client->ch_cmd, HA_FAIL); + return HA_FAIL; + } + + ret = ha_msg_new(5); + if (NULL == ret) { + lrmd_log(LOG_ERR, "on_msg_get_state: can't create a ha_msg."); + return HA_FAIL; + } + /* add the F_LRM_STATE field */ + if (HA_OK != ha_msg_add_int(ret, F_LRM_STATE + , rsc->op_list ? LRM_RSC_BUSY : LRM_RSC_IDLE)) { + LOG_FAILED_TO_ADD_FIELD("state"); + ha_msg_del(ret); + return HA_FAIL; + } + lrmd_debug(LOG_DEBUG + , "on_msg_get_state:state of rsc %s is %s" + , lrmd_nullcheck(id) + , rsc->op_list ? "LRM_RSC_BUSY" : "LRM_RSC_IDLE" ); + /* calculate the count of ops being returned */ + last_ops = g_hash_table_lookup(rsc->last_op_table, client->app_name); + if (last_ops == NULL) { + op_count = g_list_length(rsc->op_list) + + g_list_length(rsc->repeat_op_list); + } + else { + op_count = g_hash_table_size(last_ops) + + g_list_length(rsc->op_list) + + g_list_length(rsc->repeat_op_list); + } + /* add the count of ops being returned */ + if (HA_OK != ha_msg_add_int(ret, F_LRM_OPCNT, op_count)) { + LOG_FAILED_TO_ADD_FIELD("operation count"); + ha_msg_del(ret); + return HA_FAIL; + } + /* send the first message to client */ + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_state: can not send the ret message."); + ha_msg_del(ret); + return HA_FAIL; + } + ha_msg_del(ret); + + /* send the ops in last ops table */ + if(last_ops != NULL) { + g_hash_table_foreach(last_ops, send_last_op, client->ch_cmd); + } + + /* send the ops in op list */ + for(node = g_list_first(rsc->op_list) + ; NULL != node; node = g_list_next(node)){ + op = (lrmd_op_t*)node->data; + op_msg = op_to_msg(op); + if (NULL == op_msg) { + lrmd_log(LOG_ERR, + "on_msg_get_state: failed to make a message " + "from a operation: %s", op_info(op)); + continue; + } + if (HA_OK != msg2ipcchan(op_msg, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_state: failed to send a message."); + } + ha_msg_del(op_msg); + } + + /* send the ops in repeat op list */ + for(node = g_list_first(rsc->repeat_op_list) + ; NULL != node; node = g_list_next(node)){ + op = (lrmd_op_t*)node->data; + op_msg = op_to_msg(op); + if (NULL == op_msg) { + lrmd_log(LOG_ERR, + "on_msg_get_state: failed to make a message " + "from a operation: %s", op_info(op)); + continue; + } + if (HA_OK != msg2ipcchan(op_msg, client->ch_cmd)) { + lrmd_log(LOG_ERR, + "on_msg_get_state: failed to send a message."); + } + ha_msg_del(op_msg); + } + return HA_OK; +} + +#define safe_len(s) (s ? strlen(s) : 0) + +static char * +lrm_concat(const char *prefix, const char *suffix, char join) +{ + int len = 2; + char *new_str = NULL; + len += safe_len(prefix); + len += safe_len(suffix); + + new_str = malloc(sizeof(char)*len); + if (NULL == new_str) { + lrmd_log(LOG_ERR,"%s:%d: malloc failed" + , __FUNCTION__, __LINE__); + return NULL; + } + + memset(new_str, 0, len); + sprintf(new_str, "%s%c%s", prefix?prefix:"", join, suffix?suffix:""); + new_str[len-1] = 0; + return new_str; +} + +/* /////////////////////op functions////////////////////// */ + +#define mk_op_id(op,id) do { \ + const char *op_type = ha_msg_value(op->msg, F_LRM_OP); \ + const char *op_interval = ha_msg_value(op->msg, F_LRM_INTERVAL); \ + id = lrm_concat(op_type, op_interval, '_'); \ +} while(0) + +/* find the last operation for the client + * replace it with the new one (if requested) + */ +static void +replace_last_op(lrmd_client_t* client, lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + char *op_hash_key; + GHashTable *client_last_op; + lrmd_op_t *old_op, *new_op; + + if (!client || !rsc || !op) + return; + client_last_op = g_hash_table_lookup(rsc->last_op_table, client->app_name); + if (!client_last_op) { + lrmd_debug2(LOG_DEBUG + , "%s: new last op table for client %s" + , __FUNCTION__, client->app_name); + client_last_op = g_hash_table_new_full( g_str_hash + , g_str_equal, free, NULL); + g_hash_table_insert(rsc->last_op_table + , (gpointer)strdup(client->app_name) + , (gpointer)client_last_op); + } + mk_op_id(op,op_hash_key); + old_op = (lrmd_op_t*)g_hash_table_lookup(client_last_op, op_hash_key); + + /* make a copy of op and insert it into client_last_op */ + if (!(new_op = lrmd_op_copy(op))) { + lrmd_log(LOG_ERR, "%s:%d out of memory" + , __FUNCTION__, __LINE__); + } + if (old_op) { + lrmd_debug2(LOG_DEBUG + , "%s: replace last op %s for client %s" + , __FUNCTION__, op_hash_key, client->app_name); + g_hash_table_replace(client_last_op,op_hash_key,(gpointer)new_op); + lrmd_op_destroy(old_op); + } else { + lrmd_debug2(LOG_DEBUG + , "%s: add last op %s for client %s" + , __FUNCTION__, op_hash_key, client->app_name); + g_hash_table_insert(client_last_op,op_hash_key,(gpointer)new_op); + } +} + +static int +record_op_completion(lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + lrmd_client_t* client; + + LRMAUDIT(); + /*save the op in the last op finished*/ + if (rsc->last_op_done != NULL) { + lrmd_op_destroy(rsc->last_op_done); + } + if (!(rsc->last_op_done = lrmd_op_copy(op))) { + lrmd_log(LOG_ERR, "%s:%d out of memory" + , __FUNCTION__, __LINE__); + return 1; + } + rsc->last_op_done->repeat_timeout_tag = (guint)0; + + client = lookup_client(op->client_id); + if (!client) { + lrmd_log(LOG_INFO, "%s: cannot record %s: the client is gone" + , __FUNCTION__, small_op_info(op)); + LRMAUDIT(); + return 1; + } + /* insert (or replace) the new op in last_op_table for the client */ + replace_last_op(client,rsc,op); + LRMAUDIT(); + return 0; +} + +static void +to_repeatlist(lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + lrmd_op_t *repeat_op; + + if (!(repeat_op = lrmd_op_copy(op))) { + lrmd_log(LOG_ERR, "%s:%d out of memory" + , __FUNCTION__, __LINE__); + } + reset_timestamps(repeat_op); + repeat_op->is_copy = FALSE; + repeat_op->repeat_timeout_tag = + Gmain_timeout_add(op->interval, + on_repeat_op_readytorun, repeat_op); + rsc->repeat_op_list = + g_list_append (rsc->repeat_op_list, repeat_op); + lrmd_debug2(LOG_DEBUG + , "%s: repeat %s is added to repeat op list to wait" + , __FUNCTION__, op_info(op)); +} + +static void +remove_op_history(lrmd_op_t* op) +{ + lrmd_client_t* client = lookup_client(op->client_id); + lrmd_rsc_t* rsc = NULL; + char *op_id, *last_op_id; + lrmd_op_t* old_op = NULL; + GHashTable* client_last_op = NULL; + + LRMAUDIT(); + if( !(rsc = lookup_rsc(op->rsc_id)) ) { + return; + } + lrmd_debug2(LOG_DEBUG, "%s: remove history of the op %s" + ,__FUNCTION__, op_info(op)); + mk_op_id(op,op_id); + if (rsc->last_op_done != NULL ) { + mk_op_id(rsc->last_op_done,last_op_id); + if( !strcmp(op_id,last_op_id) ) { + lrmd_debug2(LOG_DEBUG, "%s: remove history of the last op done %s" + ,__FUNCTION__, op_info(rsc->last_op_done)); + lrmd_op_destroy(rsc->last_op_done); + rsc->last_op_done = NULL; + } + free(last_op_id); + } + if( client && + (client_last_op = g_hash_table_lookup(rsc->last_op_table + , client->app_name)) ) { + lrmd_debug2(LOG_DEBUG, "%s: found client %s in the last op table" + ,__FUNCTION__, client->app_name); + old_op = g_hash_table_lookup(client_last_op, op_id); + if (old_op) { + g_hash_table_remove(client_last_op, op_id); + lrmd_debug2(LOG_DEBUG, "%s: remove history of the client's last %s" + ,__FUNCTION__, op_info(old_op)); + lrmd_op_destroy(old_op); + } + } + free(op_id); + LRMAUDIT(); +} + +static void +add_op_to_runlist(lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + op->t_addtolist = time_longclock(); + rsc->op_list = g_list_append(rsc->op_list, op); + if (g_list_length(rsc->op_list) >= 4) { + lrmd_log(LOG_WARNING + , "operations list for %s is suspiciously" + " long [%d]" + , rsc->id + , g_list_length(rsc->op_list)); + lrmd_rsc_dump(rsc->id, "rsc->op_list: too many ops"); + } +} + +/* 1. this function sends a message to the client: + * a) on operation instance exit using the callback channel + * b) in case a client requested that operation to be cancelled, + * using the command channel + * c) in case a client requested a resource removal or flushing + * all ops and this is the last operation that finished, again + * using the command channel + * 2. if the op was not cancelled: + * a) it is copied to the last_op_done field of rsc + * b) if it's a repeating op, it is put in the repeat_op_list + * c) the outcome is recorded for future reference + * 3. op is destroyed and removed from the op_list + */ +int +on_op_done(lrmd_rsc_t* rsc, lrmd_op_t* op) +{ + int rc = HA_OK; + int target_rc, last_rc, op_rc; + int rc_changed; + op_status_t op_status; + + LRMAUDIT(); + CHECK_ALLOCATED(op, "op", HA_FAIL ); + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "%s: op->exec_pid == 0",__FUNCTION__); + return HA_FAIL; + } + op->t_done = time_longclock(); + + if (debug_level >= 2) { + lrmd_debug(LOG_DEBUG, "%s: %s",__FUNCTION__, op_info(op)); + lrmd_op_dump(op, __FUNCTION__); + } + + return_on_no_int_value(op->msg,F_LRM_TARGETRC,&target_rc); + return_on_no_int_value(op->msg,F_LRM_OPSTATUS,(int *)&op_status); + + last_rc = op_rc = -1; /* set all rc to -1 */ + ha_msg_value_int(op->msg,F_LRM_RC,&op_rc); + ha_msg_value_int(op->msg,F_LRM_LASTRC,&last_rc); + rc_changed = ( + op_status == LRM_OP_DONE + && op_rc != -1 + && ((last_rc == -1) || (last_rc != op_rc)) + ); + if (rc_changed) { + if (HA_OK != ha_msg_mod_int(op->msg, F_LRM_LASTRC, op_rc)) { + lrmd_log(LOG_ERR,"%s: cannot save status to msg",__FUNCTION__); + return HA_FAIL; + } + op->t_rcchange = op->t_perform; + } + if (store_timestamps(op)) + return HA_FAIL; + + /* remove the op from op_list */ + rsc->op_list = g_list_remove(rsc->op_list,op); + lrmd_debug2(LOG_DEBUG + , "%s:%s is removed from op list" + , __FUNCTION__, op_info(op)); + + if (!op->is_cancelled) { + if( !record_op_completion(rsc,op) ) { /*record the outcome of the op */ + if (op->interval) /* copy op to the repeat list */ + to_repeatlist(rsc,op); + } + } else { + if (HA_OK != ha_msg_mod_int(op->msg,F_LRM_OPSTATUS,(int)LRM_OP_CANCELLED)) { + LOG_FAILED_TO_ADD_FIELD(F_LRM_OPSTATUS); + return HA_FAIL; + } + op_status = LRM_OP_CANCELLED; + remove_op_history(op); + } + + if (rsc_removal_pending(rsc)) { + if (HA_OK != ha_msg_add_int(op->msg,F_LRM_RSCDELETED,1)) { + LOG_FAILED_TO_ADD_FIELD(F_LRM_RSCDELETED); + } + } + if (op_status != LRM_OP_DONE + || (op_rc == -1) + || (op_rc == target_rc) + || (target_rc == EVERYTIME) + || ((target_rc == CHANGED) && rc_changed) + || rsc_removal_pending(rsc) + ) { + notify_client(op); + } + lrmd_op_destroy(op); + if( !rsc->op_list ) { + if( rsc_removal_pending(rsc) ) { + lrmd_log(LOG_INFO, "late removal of resource %s", rsc->id); + lrmd_rsc_destroy(rsc); + rc = -1; /* let the caller know that the rsc is gone */ + } else { + rsc_reset_state(rsc); + } + } + LRMAUDIT(); + if (shutdown_in_progress && can_shutdown()) { + lrm_shutdown(); + } + return rc; +} + +/* + * an operation is flushed only in case there is + * no process running initiated by this operation + * NB: the caller has to destroy the operation itself + */ +int +flush_op(lrmd_op_t* op) +{ + CHECK_ALLOCATED(op, "op", HA_FAIL ); + if (op->exec_pid == 0) { + lrmd_debug(LOG_ERR, "%s: op->exec_pid == 0",__FUNCTION__); + return HA_FAIL; + } + + if (HA_OK != ha_msg_mod_int(op->msg, F_LRM_RC, HA_FAIL)) { + LOG_FAILED_TO_ADD_FIELD("F_LRM_RC"); + return HA_FAIL; + } + + if( op->exec_pid == -1 ) { + if (HA_OK != ha_msg_mod_int(op->msg,F_LRM_OPSTATUS,(int)LRM_OP_CANCELLED)){ + LOG_FAILED_TO_ADD_FIELD("opstatus"); + return HA_FAIL; + } + return HA_OK; + } else { + op->is_cancelled = TRUE; /* mark the op as cancelled */ + lrmd_log(LOG_INFO, "%s: process for %s still " + "running, flush delayed" + ,__FUNCTION__,small_op_info(op)); + return HA_RSCBUSY; + } +} + +/* Resume the execution of ops of the resource */ +static gboolean +rsc_execution_freeze_timeout(gpointer data) +{ + lrmd_rsc_t* rsc = (lrmd_rsc_t*)data; + + if (rsc == NULL) { + return FALSE; + } + + if (rsc->delay_timeout > 0) { + rsc->delay_timeout = (guint)0; + } + + perform_op(rsc); + + return FALSE; +} + +/* this function gets the first op in the rsc op list and execute it*/ +int +perform_op(lrmd_rsc_t* rsc) +{ + GList* node = NULL; + lrmd_op_t* op = NULL; + + LRMAUDIT(); + CHECK_ALLOCATED(rsc, "resource", HA_FAIL); + if (shutdown_in_progress && can_shutdown()) { + lrm_shutdown(); + } + + if (rsc_frozen(rsc)) { + lrmd_log(LOG_INFO,"%s: resource %s is frozen, " + "no ops allowed to run" + , __FUNCTION__, rsc->id); + return HA_OK; + } + + if (NULL == rsc->op_list) { + lrmd_debug2(LOG_DEBUG,"%s: no op to perform?", __FUNCTION__); + return HA_OK; + } + + node = g_list_first(rsc->op_list); + while (NULL != node) { + op = node->data; + if (-1 != op->exec_pid) { + if (!g_list_next(node)) { + /* this is the only operation, no need to do + * anything further */ + break; + } + lrmd_log(LOG_INFO, "%s:%d: %s for rsc is already running." + , __FUNCTION__, __LINE__, op_info(op)); + if( rsc->delay_timeout > 0 ) { + lrmd_log(LOG_INFO + , "%s:%d: operations on resource %s already delayed" + , __FUNCTION__, __LINE__, lrm_str(rsc->id)); + } else { + lrmd_log(LOG_INFO + , "%s:%d: postponing " + "all ops on resource %s by %d ms" + , __FUNCTION__, __LINE__ + , lrm_str(rsc->id), retry_interval); + rsc->delay_timeout = Gmain_timeout_add(retry_interval + , rsc_execution_freeze_timeout, rsc); + } + break; + } + if (op->weight && child_count >= max_child_count) { + if ((int)rsc->delay_timeout > 0) { + lrmd_log(LOG_INFO + , "%s:%d: max_child_count (%d) reached and operations on resource %s already delayed" + , __FUNCTION__, __LINE__, max_child_count, lrm_str(rsc->id)); + } else { + lrmd_debug(LOG_NOTICE + , "max_child_count (%d) reached, postponing " + "execution of %s by %d ms" + , max_child_count, op_info(op), retry_interval); + rsc->delay_timeout = Gmain_timeout_add(retry_interval + , rsc_execution_freeze_timeout, rsc); + } + break; + } + + if (HA_OK != perform_ra_op(op)) { + lrmd_log(LOG_ERR + , "unable to perform_ra_op on %s" + , op_info(op)); + if (HA_OK != ha_msg_add_int(op->msg, F_LRM_OPSTATUS, + LRM_OP_ERROR)) { + LOG_FAILED_TO_ADD_FIELD("opstatus"); + } + on_op_done(rsc,op); + node = g_list_first(rsc->op_list); + } + else { + break; + } + } + + LRMAUDIT(); + return HA_OK; +} + +static int +store_timestamps(lrmd_op_t* op) +{ + struct ha_msg* msg = op->msg; + longclock_t now = time_longclock(), /* tm2unix() needs this */ + exec_time = zero_longclock, + queue_time = zero_longclock; + + if (op->t_perform) { + queue_time = + longclockto_ms(sub_longclock(op->t_perform,op->t_addtolist)); + if (op->t_done) { + exec_time = + longclockto_ms(sub_longclock(op->t_done,op->t_perform)); + } + } + if ((HA_OK!=ha_msg_mod_ul(msg,F_LRM_T_RUN,tm2unix(op->t_perform))) + || (HA_OK!=ha_msg_mod_ul(msg,F_LRM_T_RCCHANGE,tm2unix(op->t_rcchange))) + || (HA_OK!=ha_msg_mod_ul(msg,F_LRM_EXEC_TIME,exec_time)) + || (HA_OK!=ha_msg_mod_ul(msg,F_LRM_QUEUE_TIME,queue_time)) + ) { + lrmd_log(LOG_ERR,"%s: can not save timestamps to msg",__FUNCTION__); + return 1; + } + return 0; +} + +static void +reset_timestamps(lrmd_op_t* op) +{ + op->t_perform = zero_longclock; + op->t_done = zero_longclock; + cl_msg_remove(op->msg, F_LRM_T_RUN); + cl_msg_remove(op->msg, F_LRM_T_RCCHANGE); + cl_msg_remove(op->msg, F_LRM_EXEC_TIME); + cl_msg_remove(op->msg, F_LRM_QUEUE_TIME); +} + +struct ha_msg* +op_to_msg(lrmd_op_t* op) +{ + struct ha_msg* msg = NULL; + + CHECK_ALLOCATED(op, "op", NULL); + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "%s: op->exec_pid is 0",__FUNCTION__); + return NULL; + } + msg = ha_msg_copy(op->msg); + if (NULL == msg) { + lrmd_log(LOG_ERR,"%s: can not copy the msg",__FUNCTION__); + return NULL; + } + if ((HA_OK!=ha_msg_mod_int(msg,F_LRM_CALLID,op->call_id))) { + lrmd_log(LOG_ERR,"%s: can not save F_LRM_CALLID to msg",__FUNCTION__); + ha_msg_del(msg); + msg = NULL; + } + return msg; +} + +/* //////////////////////////////RA wrap funcs/////////////////////////////////// */ +int +perform_ra_op(lrmd_op_t* op) +{ + int stdout_fd[2]; + int stderr_fd[2]; + pid_t pid; + int timeout; + struct RAExecOps * RAExec = NULL; + const char* op_type = NULL; + GHashTable* params = NULL; + GHashTable* op_params = NULL; + lrmd_rsc_t* rsc = NULL; + ra_pipe_op_t * rapop; + + LRMAUDIT(); + CHECK_ALLOCATED(op, "op", HA_FAIL); + rsc = (lrmd_rsc_t*)lookup_rsc(op->rsc_id); + CHECK_ALLOCATED(rsc, "rsc", HA_FAIL); + + if ( pipe(stdout_fd) < 0 ) { + cl_perror("%s::%d: pipe", __FUNCTION__, __LINE__); + } + + if ( pipe(stderr_fd) < 0 ) { + cl_perror("%s::%d: pipe", __FUNCTION__, __LINE__); + } + + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "%s::%d: op->exec_pid == 0.", __FUNCTION__, __LINE__); + return HA_FAIL; + } + + op_type = ha_msg_value(op->msg, F_LRM_OP); + op->t_perform = time_longclock(); + check_queue_duration(op); + + if(HA_OK != ha_msg_value_int(op->msg, F_LRM_TIMEOUT, &timeout)){ + timeout = 0; + lrmd_log(LOG_ERR,"%s::%d: failed to get timeout for %s" + , __FUNCTION__, __LINE__, small_op_info(op)); + } + + if( return_to_orig_privs() ) { + cl_perror("%s::%d: failed to raise privileges" + , __FUNCTION__, __LINE__); + } + switch(pid=fork()) { + case -1: + cl_perror("%s::%d: fork", __FUNCTION__, __LINE__); + close(stdout_fd[0]); + close(stdout_fd[1]); + close(stderr_fd[0]); + close(stderr_fd[1]); + if( return_to_dropped_privs() ) { + cl_perror("%s::%d: failed to drop privileges" + , __FUNCTION__, __LINE__); + } + return HA_FAIL; + + default: /* Parent */ + child_count += op->weight; + NewTrackedProc(pid, 1 + , debug_level ? + ((op->interval && !is_logmsg_due(op)) ? PT_LOGNORMAL : PT_LOGVERBOSE) : PT_LOGNONE + , op, &ManagedChildTrackOps); + + if (!op->interval || is_logmsg_due(op)) { /* log non-repeating ops */ + lrmd_log(LOG_INFO,"rsc:%s %s[%d] (pid %d)", + rsc->id,probe_str(op,op_type),op->call_id,pid); + } else { + lrmd_debug(LOG_DEBUG,"rsc:%s %s[%d] (pid %d)", + rsc->id,op_type,op->call_id,pid); + } + close(stdout_fd[1]); + close(stderr_fd[1]); + rapop = ra_pipe_op_new(stdout_fd[0], stderr_fd[0], op); + op->rapop = rapop; + op->exec_pid = pid; + if (0 < timeout ) { + + /* Wait 'timeout' ms then send SIGTERM */ + /* allow for extra 15 seconds for stonith, + * because stonithd handles its children with the + * same timeout; in this case the lrmd child + * should never timeout, but return the timeout + * reported by stonithd + */ + op->killseq[0].mstimeout = timeout + + (!strcmp(rsc->class,"stonith") ? 15000 : 0); + op->killseq[0].signalno = SIGTERM; + + /* Wait 5 seconds then send SIGKILL */ + op->killseq[1].mstimeout = 5000; + op->killseq[1].signalno = SIGKILL; + + /* Wait 5 more seconds then moan and complain */ + op->killseq[2].mstimeout = 5000; + op->killseq[2].signalno = 0; + + SetTrackedProcTimeouts(pid, op->killseq); + } + if( return_to_dropped_privs() ) { + lrmd_log(LOG_WARNING,"%s::%d: failed to drop privileges: %s" + , __FUNCTION__, __LINE__, strerror(errno)); + } + + if ( rapop == NULL) { + return HA_FAIL; + } + LRMAUDIT(); + return HA_OK; + + case 0: /* Child */ +#ifdef DEFAULT_REALTIME_POLICY + if (sched_getscheduler(0) != SCHED_OTHER) { + struct sched_param sp; + lrmd_debug(LOG_DEBUG, + "perform_ra_op: resetting scheduler class to SCHED_OTHER"); + sp.sched_priority = 0; + if (sched_setscheduler(0, SCHED_OTHER, &sp) == -1) + cl_perror("%s::%d: sched_setscheduler", + __FUNCTION__, __LINE__); + } +#endif + /* Man: The call setpgrp() is equivalent to setpgid(0,0) + * _and_ compiles on BSD variants too + * need to investigate if it works the same too. + */ + setpgid(0,0); + close(stdout_fd[0]); + close(stderr_fd[0]); + if (STDOUT_FILENO != stdout_fd[1]) { + if (dup2(stdout_fd[1], STDOUT_FILENO)!=STDOUT_FILENO) { + cl_perror("%s::%d: dup2" + , __FUNCTION__, __LINE__); + } + close(stdout_fd[1]); + } + if (STDERR_FILENO != stderr_fd[1]) { + if (dup2(stderr_fd[1], STDERR_FILENO)!=STDERR_FILENO) { + cl_perror("%s::%d: dup2", __FUNCTION__, __LINE__); + } + close(stderr_fd[1]); + } + RAExec = g_hash_table_lookup(RAExecFuncs,rsc->class); + if (NULL == RAExec) { + close(stdout_fd[1]); + close(stderr_fd[1]); + lrmd_log(LOG_ERR,"%s::%d: can't find RAExec for class %s" + , __FUNCTION__, __LINE__, rsc->class); + exit(EXECRA_EXEC_UNKNOWN_ERROR); + } + + /*should we use logging daemon or not in script*/ + setenv(HALOGD, cl_log_get_uselogd()?"yes":"no",1); + + /* Name of the resource and some others also + * need to be passed in. Maybe pass through the + * entire lrm_op_t too? */ + lrmd_debug2(LOG_DEBUG + , "perform_ra_op:calling RA plugin to perform %s, pid: [%d]" + , op_info(op), getpid()); + + op_params = ha_msg_value_str_table(op->msg, F_LRM_PARAM); + params = merge_str_tables(rsc->params,op_params); + if (op_params) { + free_str_table(op_params); + op_params = NULL; + } + + if (replace_secret_params(rsc->id, params) < 0) { + /* replacing secrets failed! */ + if (!strcmp(op_type,"stop")) { + /* don't fail on stop! */ + lrmd_log(LOG_INFO + , "%s:%d: proceeding with the stop operation for %s" + , __FUNCTION__, __LINE__, rsc->id); + } else { + lrmd_log(LOG_ERR + , "%s:%d: failed to get secrets for %s, " + "considering resource not configured" + , __FUNCTION__, __LINE__, rsc->id); + exit(EXECRA_NOT_CONFIGURED); + } + } + RAExec->execra (rsc->id, + rsc->type, + rsc->provider, + op_type, + timeout, + params); + + /* execra should never return. */ + exit(EXECRA_EXEC_UNKNOWN_ERROR); + + } + lrmd_log(LOG_ERR, "perform_ra_op: end(impossible)."); + return HA_OK; +} + +static void +on_ra_proc_registered(ProcTrack* p) +{ +} + +/* Handle one of our ra child processes finished*/ +static void +on_ra_proc_finished(ProcTrack* p, int status, int signo, int exitcode +, int waslogged) +{ + lrmd_op_t* op = NULL; + lrmd_rsc_t* rsc = NULL; + struct RAExecOps * RAExec = NULL; + const char* op_type; + int rc = EXECRA_EXEC_UNKNOWN_ERROR; + int ret; + int op_status; + + LRMAUDIT(); + + CHECK_ALLOCATED(p, "ProcTrack p", ); + op = proctrack_data(p); + + child_count -= op->weight; + if (child_count < 0) { + lrmd_log(LOG_ERR, "%s:%d: child count is less than zero: %d" + , __FUNCTION__, __LINE__, child_count); + child_count = 0; + } + + lrmd_debug2(LOG_DEBUG, "on_ra_proc_finished: accessing the op whose " + "address is %p", op); + CHECK_ALLOCATED(op, "op", ); + if (op->exec_pid == 0) { + lrmd_log(LOG_ERR, "on_ra_proc_finished: the op was freed."); + dump_data_for_debug(); + return; + } + RemoveTrackedProcTimeouts(op->exec_pid); + op->exec_pid = -1; + + rsc = lookup_rsc(op->rsc_id); + if (rsc == NULL) { + lrmd_log(LOG_ERR, "%s: the rsc (id=%s) does not exist" + , __FUNCTION__, lrm_str(op->rsc_id)); + lrmd_op_dump(op, __FUNCTION__); + lrmd_dump_all_resources(); + /* delete the op */ + lrmd_op_destroy(op); + reset_proctrack_data(p); + LRMAUDIT(); + return; + } + + RAExec = g_hash_table_lookup(RAExecFuncs,rsc->class); + if (NULL == RAExec) { + lrmd_log(LOG_ERR,"on_ra_proc_finished: can not find RAExec for" + " resource class <%s>", rsc->class); + dump_data_for_debug(); + return; + } + + op_type = ha_msg_value(op->msg, F_LRM_OP); + + if ( (NULL == strchr(op->first_line_ra_stdout, '\n')) + && (0==STRNCMP_CONST(rsc->class, "heartbeat")) + && ( (0==STRNCMP_CONST(op_type, "monitor")) + ||(0==STRNCMP_CONST(op_type, "status"))) ) { + if ( ( op->rapop != NULL ) + && (op->rapop->ra_stdout_fd >= 0) ) { + handle_pipe_ra_stdout(op->rapop->ra_stdout_fd + , op->rapop); + } else { + lrmd_log(LOG_WARNING, "There is something wrong: the " + "first line isn't read in. Maybe the heartbeat " + "does not ouput string correctly for status " + "operation. Or the code (myself) is wrong."); + } + } + + if( signo ) { + if( proctrack_timedout(p) ) { + lrmd_log(LOG_WARNING, "%s: pid %d timed out" + , small_op_info(op), proctrack_pid(p)); + op_status = LRM_OP_TIMEOUT; + } else { + op_status = LRM_OP_ERROR; + } + } else { + rc = RAExec->map_ra_retvalue(exitcode, op_type + , op->first_line_ra_stdout); + if (!op->interval || is_logmsg_due(op) || debug_level > 0) { /* log non-repeating ops */ + if (rc == exitcode) { + lrmd_log(LOG_INFO + , "%s: pid %d exited with" + " return code %d", small_op_info(op), proctrack_pid(p), rc); + }else{ + lrmd_log(LOG_INFO + , "%s: pid %d exited with" + " return code %d (mapped from %d)" + , small_op_info(op), proctrack_pid(p), rc, exitcode); + } + } + if (EXECRA_EXEC_UNKNOWN_ERROR == rc || EXECRA_NO_RA == rc) { + op_status = LRM_OP_ERROR; + lrmd_log(LOG_CRIT + , "on_ra_proc_finished: the exit code indicates a problem."); + } else { + op_status = LRM_OP_DONE; + } + } + if (op->interval && is_logmsg_due(op)) { + op->t_lastlogmsg = time_longclock(); + } + if (HA_OK != + ha_msg_mod_int(op->msg, F_LRM_OPSTATUS, op_status)) { + LOG_FAILED_TO_ADD_FIELD("opstatus"); + return ; + } + if (HA_OK != ha_msg_mod_int(op->msg, F_LRM_RC, rc)) { + LOG_FAILED_TO_ADD_FIELD("F_LRM_RC"); + return ; + } + + if ( 0 < strlen(op->first_line_ra_stdout) ) { + if (NULL != cl_get_string(op->msg, F_LRM_DATA)) { + cl_msg_remove(op->msg, F_LRM_DATA); + } + ret = ha_msg_add(op->msg, F_LRM_DATA, op->first_line_ra_stdout); + if (HA_OK != ret) { + LOG_FAILED_TO_ADD_FIELD("data"); + } + } + + if (on_op_done(rsc,op) >= 0) { + perform_op(rsc); + } + reset_proctrack_data(p); + LRMAUDIT(); +} + +/* Handle the death of one of our managed child processes */ +static const char * +on_ra_proc_query_name(ProcTrack* p) +{ + static char proc_name[MAX_PROC_NAME]; + lrmd_op_t* op = NULL; + lrmd_rsc_t* rsc = NULL; + const char* op_type = NULL; + + LRMAUDIT(); + op = (lrmd_op_t*)(proctrack_data(p)); + if (NULL == op || op->exec_pid == 0) { + return "*unknown*"; + } + + op_type = ha_msg_value(op->msg, F_LRM_OP); + rsc = lookup_rsc(op->rsc_id); + if (rsc == NULL) { + snprintf(proc_name + , MAX_PROC_NAME + , "unknown rsc(%s):%s maybe deleted" + , op->rsc_id, op_type); + }else { + snprintf(proc_name, MAX_PROC_NAME, "%s:%s", rsc->id, op_type); + } + LRMAUDIT(); + return proc_name; +} + +static int +get_lrmd_param(const char *name, char *value, int maxstring) +{ + if (!name) { + lrmd_log(LOG_ERR, "%s: empty name", __FUNCTION__); + return HA_FAIL; + } + if (!strcmp(name,"max-children")) { + snprintf(value, maxstring, "%d", max_child_count); + return HA_OK; + } else { + lrmd_log(LOG_ERR, "%s: unknown lrmd parameter %s", __FUNCTION__, name); + return HA_FAIL; + } +} + +static int +set_lrmd_param(const char *name, const char *value) +{ + int ival; + + if (!name) { + lrmd_log(LOG_ERR, "%s: empty name", __FUNCTION__); + return HA_FAIL; + } + if (!value) { + lrmd_log(LOG_ERR, "%s: empty value", __FUNCTION__); + return HA_FAIL; + } + if (!strcmp(name,"max-children")) { + ival = atoi(value); + if (ival <= 0) { + lrmd_log(LOG_ERR, "%s: invalid value for lrmd parameter %s" + , __FUNCTION__, name); + return HA_FAIL; + } else if (ival == max_child_count) { + lrmd_log(LOG_INFO, "max-children already set to %d", ival); + return HA_OK; + } + lrmd_log(LOG_INFO, "setting max-children to %d", ival); + max_child_count = ival; + return HA_OK; + } else { + lrmd_log(LOG_ERR, "%s: unknown lrmd parameter %s" + , __FUNCTION__, name); + return HA_FAIL; + } +} + +int +on_msg_set_lrmd_param(lrmd_client_t* client, struct ha_msg* msg) +{ + const char *name, *value; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + name = ha_msg_value(msg,F_LRM_LRMD_PARAM_NAME); + value = ha_msg_value(msg,F_LRM_LRMD_PARAM_VAL); + if (!name || !value) { + lrmd_log(LOG_ERR, "%s: no parameter defined" + , __FUNCTION__); + return HA_FAIL; + } + return set_lrmd_param(name,value); +} + +int +on_msg_get_lrmd_param(lrmd_client_t* client, struct ha_msg* msg) +{ + struct ha_msg* ret = NULL; + const char *name; + char value[MAX_NAME_LEN]; + + CHECK_ALLOCATED(client, "client", HA_FAIL); + CHECK_ALLOCATED(msg, "message", HA_FAIL); + + ret = create_lrm_ret(HA_OK, 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + + name = ha_msg_value(msg,F_LRM_LRMD_PARAM_NAME); + if (get_lrmd_param(name, value, MAX_NAME_LEN) != HA_OK) { + return HA_FAIL; + } + if (HA_OK != ha_msg_add(ret, F_LRM_LRMD_PARAM_VAL, value)) { + ha_msg_del(ret); + LOG_FAILED_TO_ADD_FIELD(F_LRM_LRMD_PARAM_VAL); + return HA_FAIL; + } + if (HA_OK != msg2ipcchan(ret, client->ch_cmd)) { + lrmd_log(LOG_ERR, "%s: can not send the ret msg",__FUNCTION__); + } + ha_msg_del(ret); + return HA_OK; +} + + +/* /////////////////Util Functions////////////////////////////////////////////// */ +int +send_ret_msg (IPC_Channel* ch, int ret) +{ + struct ha_msg* msg = NULL; + + msg = create_lrm_ret(ret, 1); + CHECK_RETURN_OF_CREATE_LRM_RET; + + if (HA_OK != msg2ipcchan(msg, ch)) { + lrmd_log(LOG_ERR, "send_ret_msg: can not send the ret msg"); + } + ha_msg_del(msg); + return HA_OK; +} + +static void +send_cbk_msg(struct ha_msg* msg, lrmd_client_t* client) +{ + if (!client) { + lrmd_log(LOG_WARNING, + "%s: zero client", __FUNCTION__); + return; + } + if (!client->ch_cbk) { + lrmd_log(LOG_WARNING, + "%s: callback channel is null", __FUNCTION__); + } else if (HA_OK != msg2ipcchan(msg, client->ch_cbk)) { + lrmd_log(LOG_WARNING, + "%s: can not send the ret msg", __FUNCTION__); + } +} + +static void +send_msg(struct ha_msg* msg, lrmd_client_t* client) +{ + if (!client) { + lrmd_log(LOG_WARNING, + "%s: zero client", __FUNCTION__); + return; + } + if (HA_OK != ha_msg_mod(msg,F_LRM_APP,client->app_name)) { + lrmd_log(LOG_ERR,"%s:%d: cannot add field to a message" + , __FUNCTION__, __LINE__); + return; + } + send_cbk_msg(msg, client); +} + +void +notify_client(lrmd_op_t* op) +{ + lrmd_client_t* client = lookup_client(op->client_id); + + if (client) { + /* send the result to client */ + send_cbk_msg(op->msg, client); + } else { + lrmd_log(LOG_WARNING + , "%s: client for the operation %s does not exist" + " and client requested notification." + , __FUNCTION__, op_info(op)); + } +} + +lrmd_client_t* +lookup_client (pid_t pid) +{ + return (lrmd_client_t*) g_hash_table_lookup(clients, &pid); +} + +static gboolean +client_cmp_name(gpointer key, gpointer val, gpointer app_name) +{ + return strcmp(((lrmd_client_t*)val)->app_name,(char *)app_name) ? + FALSE : TRUE; +} + +static lrmd_client_t* +lookup_client_by_name(char *app_name) +{ + return (lrmd_client_t*)g_hash_table_find(clients,client_cmp_name,app_name); +} + +lrmd_rsc_t* +lookup_rsc (const char* rid) +{ + return rid ? + (lrmd_rsc_t*)g_hash_table_lookup(resources, rid) : + NULL; +} + +lrmd_rsc_t* +lookup_rsc_by_msg (struct ha_msg* msg) +{ + const char* id = NULL; + lrmd_rsc_t* rsc = NULL; + + CHECK_ALLOCATED(msg, "msg", NULL); + id = ha_msg_value(msg, F_LRM_RID); + if (id == NULL) { + lrmd_log(LOG_ERR, "lookup_rsc_by_msg: got a NULL resource id."); + return NULL; + } + if (RID_LEN <= strnlen(id, RID_LEN+2)) { + lrmd_log(LOG_ERR, "lookup_rsc_by_msg: resource id is too long."); + return NULL; + } + rsc = lookup_rsc(id); + return rsc; +} + +static void +destroy_pipe_ra_stdout(gpointer user_data) +{ + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + + CHECK_ALLOCATED(rapop, "ra_pipe_op",); + if (rapop->ra_stderr_fd < 0) { + ra_pipe_op_destroy(rapop); + } +} + +static void +destroy_pipe_ra_stderr(gpointer user_data) +{ + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + + CHECK_ALLOCATED(rapop, "ra_pipe_op",); + if (rapop->ra_stdout_fd < 0) { + ra_pipe_op_destroy(rapop); + } +} + +static gboolean +handle_pipe_ra_stdout(int fd, gpointer user_data) +{ + gboolean rc = TRUE; + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + char * data = NULL; + lrmd_op_t* lrmd_op = NULL; + + CHECK_ALLOCATED(rapop, "ra_pipe_op", FALSE); + + if (rapop->lrmd_op == NULL) { + lrmd_debug2(LOG_DEBUG, "%s:%d: Unallocated lrmd_op 0x%lx!!" + , __FUNCTION__, __LINE__ + , (unsigned long)rapop->lrmd_op); + } else { + lrmd_op = rapop->lrmd_op; + } + + if (fd <= STDERR_FILENO) { + lrmd_log(LOG_CRIT, "%s:%d: Attempt to read from " + "closed/invalid file descriptor %d." + , __FUNCTION__, __LINE__, fd); + return FALSE; + } + + if (0 != read_pipe(fd, &data, rapop)) { + /* error or reach the EOF */ + if (fd > STDERR_FILENO) { + close(fd); + if (fd == rapop->ra_stdout_fd) { + rapop->ra_stdout_fd = -1; + } + } + if ( NULL != rapop->ra_stdout_gsource) { + /* + * Returning FALSE will trigger ipc code to release + * the GFDSource, so donn't release it here. + */ + rapop->ra_stdout_gsource = NULL; + } + rc = FALSE; + } + + if ( data!=NULL ) { + if ( (0==STRNCMP_CONST(rapop->op_type, "meta-data")) + ||(0==STRNCMP_CONST(rapop->op_type, "monitor")) + ||(0==STRNCMP_CONST(rapop->op_type, "status")) ) { + lrmd_debug(LOG_DEBUG, "RA output: (%s:%s:stdout) %s" + , lrm_str(rapop->rsc_id), rapop->op_type, data); + } else { + lrmd_log(LOG_INFO, "RA output: (%s:%s:stdout) %s" + , lrm_str(rapop->rsc_id), rapop->op_type, data); + } + + /* + * This code isn't good enough, it produces erratic and hard-to + * read messages in the logs. But this does not affect the + * function correctness, since the first line output is ensured + * to be collected into the buffer completely. + * Anyway, the meta-data (which is _many_ lines long) can be + * handled by another function, see raexec.h + */ + if ( (rapop->first_line_read == FALSE) + && (0==STRNCMP_CONST(rapop->rsc_class, "heartbeat")) + && ( lrmd_op != NULL ) + && ( (0==STRNCMP_CONST(rapop->op_type, "monitor")) + ||(0==STRNCMP_CONST(rapop->op_type, "status")) )) { + if (lrmd_op != NULL) { + strncat(lrmd_op->first_line_ra_stdout, data + , sizeof(lrmd_op->first_line_ra_stdout) - + strlen(lrmd_op->first_line_ra_stdout)-1); + if (strchr(lrmd_op->first_line_ra_stdout, '\n') + != NULL) { + rapop->first_line_read = TRUE; + } + } else { + lrmd_log(LOG_CRIT + , "Before read the first line, the RA " + "execution child quitted and waited."); + } + } + + g_free(data); + } + + return rc; +} + +static gboolean +handle_pipe_ra_stderr(int fd, gpointer user_data) +{ + gboolean rc = TRUE; + char * data = NULL; + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + + CHECK_ALLOCATED(rapop, "ra_pipe_op", FALSE); + + if (fd <= STDERR_FILENO) { + lrmd_log(LOG_CRIT, "%s:%d: Attempt to read from " + " closed/invalid file descriptor %d." + , __FUNCTION__, __LINE__, fd); + return FALSE; + } + + if (0 != read_pipe(fd, &data, rapop)) { + /* error or reach the EOF */ + if (fd > STDERR_FILENO) { + close(fd); + if (fd == rapop->ra_stderr_fd) { + rapop->ra_stderr_fd = -1; + } + } + if ( NULL != rapop->ra_stderr_gsource) { + /* + * G_main_del_fd will trigger + * destroy_pipe_ra_stderr + * ra_pipe_op_destroy + * + * Returning FALSE will trigger ipc code to release + * the GFDSource, so donn't release it here. + */ + rapop->ra_stderr_gsource = NULL; + } + rc = FALSE; + } + + if (data!=NULL) { + lrmd_log(LOG_INFO, "RA output: (%s:%s:stderr) %s" + , lrm_str(rapop->rsc_id), probe_str(rapop->lrmd_op,rapop->op_type), data); + g_free(data); + } + + return rc; +} + +int +read_pipe(int fd, char ** data, void * user_data) +{ + const int BUFFLEN = 81; + char buffer[BUFFLEN]; + int readlen; + GString * gstr_tmp; + int rc = 0; + lrmd_op_t * op = NULL; + ra_pipe_op_t * rapop = (ra_pipe_op_t *)user_data; + + lrmd_debug3(LOG_DEBUG, "%s begin.", __FUNCTION__); + + CHECK_ALLOCATED(rapop, "ra_pipe_op", FALSE); + + op = (lrmd_op_t *)rapop->lrmd_op; + if (NULL == op) { + lrmd_debug2(LOG_DEBUG, "%s:%d: Unallocated lrmd_op 0x%lx!!" + , __FUNCTION__, __LINE__ + , (unsigned long)op); + } + + *data = NULL; + gstr_tmp = g_string_new(""); + + do { + errno = 0; + readlen = read(fd, buffer, BUFFLEN - 1); + if (NULL == op) { + lrmd_debug2(LOG_NOTICE + , "read's ret: %d when lrmd_op finished" + , readlen); + } + if ( readlen > 0 ) { + buffer[readlen] = EOS; + g_string_append(gstr_tmp, buffer); + } + } while (readlen == BUFFLEN - 1 || errno == EINTR); + + if (errno == EINTR || errno == EAGAIN) { + errno = 0; + } + + /* Reach the EOF */ + if (readlen == 0) { + rc = -1; + } + + if ((readlen < 0) && (errno !=0)) { + rc = -1; + switch (errno) { + default: + cl_perror("%s:%d read error: fd %d errno=%d" + , __FUNCTION__, __LINE__ + , fd, errno); + if (NULL != op) { + lrmd_op_dump(op, "op w/bad errno"); + } else { + lrmd_log(LOG_NOTICE + , "%s::%d: lrmd_op has been freed" + , __FUNCTION__, __LINE__); + } + break; + + case EBADF: + lrmd_log(LOG_CRIT + , "%s:%d" + " Attempt to read from closed file descriptor %d." + , __FUNCTION__, __LINE__, fd); + if (NULL != op) { + lrmd_op_dump(op, "op w/bad errno"); + } else { + lrmd_log(LOG_NOTICE + , "%s::%d: lrmd_op has been freed" + , __FUNCTION__, __LINE__); + } + break; + } + } + + if ( gstr_tmp->len == 0 ) { + g_string_free(gstr_tmp, TRUE); + } else { + *data = gstr_tmp->str; + g_string_free(gstr_tmp, FALSE); + } + + lrmd_debug3(LOG_DEBUG, "%s end.", __FUNCTION__); + return rc; +} + + +static gboolean +debug_level_adjust(int nsig, gpointer user_data) +{ + char s[16]; + + switch (nsig) { + case SIGUSR1: + debug_level++; + dump_data_for_debug(); + break; + + case SIGUSR2: + dump_data_for_debug(); + debug_level--; + if (debug_level < 0) { + debug_level = 0; + } + break; + + default: + lrmd_log(LOG_WARNING, "debug_level_adjust: Received an " + "unexpected signal(%d). Something wrong?.",nsig); + } + + snprintf(s, sizeof(s), "%d", debug_level); + setenv(HADEBUGVAL, s, 1); + return TRUE; +} + +static void +dump_data_for_debug(void) +{ + lrmd_debug(LOG_DEBUG, "begin to dump internal data for debugging."); + lrmd_dump_all_clients(); + lrmd_dump_all_resources(); + lrmd_debug(LOG_DEBUG, "end to dump internal data for debugging."); +} + +const char* +gen_op_info(const lrmd_op_t* op, gboolean add_params) +{ + static char info[512]; + lrmd_rsc_t* rsc = NULL; + const char * op_type; + GString * param_gstr; + GHashTable* op_params = NULL; + + if (NULL == op) { + lrmd_log(LOG_ERR, "%s:%d: op==NULL" + , __FUNCTION__, __LINE__); + return NULL; + } + rsc = lookup_rsc(op->rsc_id); + op_type = ha_msg_value(op->msg, F_LRM_OP); + + if (rsc == NULL) { + snprintf(info,sizeof(info) + ,"operation %s[%d] on unknown rsc(maybe deleted) for client %d" + ,lrm_str(op_type) + ,op->call_id ,op->client_id); + + }else{ + if (op->exec_pid > 1) { + snprintf(info, sizeof(info) + ,"operation %s[%d] with pid %d on %s for client %d" + ,lrm_str(op_type), op->call_id, op->exec_pid, lrm_str(rsc->id) + ,op->client_id); + } else { + snprintf(info, sizeof(info) + ,"operation %s[%d] on %s for client %d" + ,lrm_str(op_type), op->call_id, lrm_str(rsc->id) + ,op->client_id); + } + + if( add_params ) { + param_gstr = g_string_new(""); + op_params = ha_msg_value_str_table(op->msg, F_LRM_PARAM); + hash_to_str(op_params, param_gstr); + if (op_params) { + free_str_table(op_params); + op_params = NULL; + } + + snprintf(info+strlen(info), sizeof(info)-strlen(info) + ,", its parameters: %s",param_gstr->str); + + g_string_free(param_gstr, TRUE); + } + } + return info; +} + +static void +hash_to_str(GHashTable * params , GString * str) +{ + if (params) { + g_hash_table_foreach(params, hash_to_str_foreach, str); + } +} + +static void +hash_to_str_foreach(gpointer key, gpointer value, gpointer user_data) +{ + char buffer_tmp[80]; + GString * str = (GString *)user_data; + + g_snprintf(buffer_tmp, sizeof(buffer_tmp), "%s=[%s] " + , (char *)key, (char *)value); + str = g_string_append(str, buffer_tmp); +} + +static void +check_queue_duration(lrmd_op_t* op) +{ + unsigned long t_stay_in_list = 0; + static struct msg_ctrl *ml; + + CHECK_ALLOCATED(op, "op", ); + t_stay_in_list = longclockto_ms(op->t_perform - op->t_addtolist); + if ( t_stay_in_list > WARNINGTIME_IN_LIST) + { + if (!ml) + ml = cl_limit_log_new(logmsg_ctrl_defs + OP_STAYED_TOO_LONG); + cl_limit_log(ml, LOG_WARNING + , "perform_ra_op: the %s stayed in operation " + "list for %lu ms (longer than %d ms)" + , small_op_info(op), t_stay_in_list + , WARNINGTIME_IN_LIST + ); + if (debug_level >= 2) { + dump_data_for_debug(); + } + } +} + diff --git a/lrm/lrmd/lrmd.h b/lrm/lrmd/lrmd.h new file mode 100644 index 0000000..eadea88 --- /dev/null +++ b/lrm/lrmd/lrmd.h @@ -0,0 +1,282 @@ +#define MAX_PID_LEN 256 +#define MAX_PROC_NAME 256 +#define MAX_MSGTYPELEN 32 +#define MAX_CLASSNAMELEN 32 +#define WARNINGTIME_IN_LIST 10000 +#define OPTARGS "skrhvmi:" +#define PID_FILE HA_VARRUNDIR"/lrmd.pid" +#define LRMD_COREDUMP_ROOT_DIR HA_COREDIR +#define APPHB_WARNTIME_FACTOR 3 +#define APPHB_INTVL_DETLA 30 /* Millisecond */ + +#define lrmd_log(priority, fmt...); \ + cl_log(priority, fmt); + +#define lrmd_debug(priority, fmt...); \ + if ( debug_level >= 1 ) { \ + cl_log(priority, fmt); \ + } + +#define lrmd_debug2(priority, fmt...); \ + if ( debug_level >= 2 ) { \ + cl_log(priority, fmt); \ + } + +#define lrmd_debug3(priority, fmt...); \ + if ( debug_level >= 3 ) { \ + cl_log(priority, fmt); \ + } + +#define lrmd_nullcheck(p) ((p) ? (p) : "") +#define lrm_str(p) (lrmd_nullcheck(p)) + +#define CHECK_ALLOCATED(thing, name, result) \ + if (!thing) { \ + lrmd_log(LOG_ERR \ + , "%s: %s pointer 0x%lx is not allocated." \ + , __FUNCTION__, name, (unsigned long)thing); \ + if (!in_alloc_dump) { \ + in_alloc_dump = TRUE; \ + dump_data_for_debug(); \ + in_alloc_dump = FALSE; \ + return result; \ + } \ + } + +#define CHECK_RETURN_OF_CREATE_LRM_RET do { \ + if (NULL == msg) { \ + lrmd_log(LOG_ERR \ + , "%s: cannot create a ret message with create_lrm_ret." \ + , __FUNCTION__); \ + return HA_FAIL; \ + } \ +} while(0) + +#define LOG_FAILED_TO_GET_FIELD(field) \ + lrmd_log(LOG_ERR \ + , "%s:%d: cannot get field %s from message." \ + ,__FUNCTION__,__LINE__,field) + +#define LOG_FAILED_TO_ADD_FIELD(field) \ + lrmd_log(LOG_ERR \ + , "%s:%d: cannot add the field %s to a message." \ + , __FUNCTION__ \ + , __LINE__ \ + , field) + +/* NB: There's a return in these macros, hence the names */ +#define return_on_no_int_value(msg,fld,i) do { \ + if (HA_OK != ha_msg_value_int(msg,fld,i)) { \ + LOG_FAILED_TO_GET_FIELD(fld); \ + return HA_FAIL; \ + } \ +} while(0) +#define return_on_no_value(msg,fld,v) do { \ + v = ha_msg_value(msg,fld); \ + if (!v) { \ + LOG_FAILED_TO_GET_FIELD(fld); \ + return HA_FAIL; \ + } \ +} while(0) + +#define LRMD_APPHB_HB \ + if (reg_to_apphb == TRUE) { \ + if (apphb_hb() != 0) { \ + reg_to_apphb = FALSE; \ + } \ + } + +#define tm2age(tm) \ + (cmp_longclock(tm, zero_longclock) <= 0) ? \ + 0 : longclockto_ms(sub_longclock(now, tm)) +#define tm2unix(tm) \ + (time(NULL)-(tm2age(tm)+999)/1000) + +/* + * The basic objects in our world: + * + * lrmd_client_t: + * Client - a process which has connected to us for service. + * + * lrmd_rsc_t: + * Resource - an abstract HA cluster resource implemented by a + * resource agent through our RA plugins + * It has two list of operations (lrmd_op_t) associated with it + * op_list - operations to be run as soon as they're ready + * repeat_op_list - operations to be run later + * It maintains the following tracking structures: + * last_op_done Last operation performed on this resource + * last_op_table Last operations of each type done per client + * + * lrmd_op_t: + * Resource operation - an operation on a resource -- requested + * by a client. + * + * ProcTrack - tracks a currently running resource operation. + * It points back to the lrmd_op_t that started it. + * + * Global structures containing these things: + * + * clients - a hash table of all (currently connected) clients + * + * resources - a hash table of all (currently configured) resources + * + * Proctrack keeps its own private data structures to keep track of + * child processes that it created. They in turn point to the + * lrmd_op_t objects that caused us to fork the child process. + * + * + */ + +/* + * Recognized privilege levels + */ + +#define PRIV_ADMIN 8 /* ADMIN_UIDS are administrators */ +#define ADMIN_UIDS "0,"HA_CCMUSER +#define ADMIN_GIDS "0,"HA_APIGROUP /* unused */ + +typedef struct +{ + char* app_name; + pid_t pid; + gid_t gid; + uid_t uid; + + IPC_Channel* ch_cmd; + IPC_Channel* ch_cbk; + + GCHSource* g_src; + GCHSource* g_src_cbk; + char lastrequest[MAX_MSGTYPELEN]; + time_t lastreqstart; + time_t lastreqend; + time_t lastrcsent; + int priv_lvl; /* client privilege level (depends on uid/gid) */ +}lrmd_client_t; + +typedef struct lrmd_rsc lrmd_rsc_t; +typedef struct lrmd_op lrmd_op_t; +typedef struct ra_pipe_op ra_pipe_op_t; + +#define RSC_REMOVAL_PENDING 1 +#define RSC_FLUSHING_OPS 2 +#define rsc_frozen(r) \ + ((r)->state==RSC_REMOVAL_PENDING || (r)->state==RSC_FLUSHING_OPS) +#define rsc_removal_pending(r) \ + ((r)->state==RSC_REMOVAL_PENDING) +#define set_rsc_removal_pending(r) \ + (r)->state = RSC_REMOVAL_PENDING +#define set_rsc_flushing_ops(r) \ + (r)->state = RSC_FLUSHING_OPS +#define rsc_reset_state(r) (r)->state = 0 +/* log messages for repeating ops (monitor) once an hour */ +#define LOGMSG_INTERVAL (60*60) +#define is_logmsg_due(op) \ + (longclockto_ms(sub_longclock(time_longclock(), op->t_lastlogmsg))/1000 >= \ + (unsigned long)LOGMSG_INTERVAL) +#define probe_str(op,op_type) \ + ((op && !op->interval && !strcmp(op_type,"monitor")) ? "probe" : op_type) +/* exclude stonith class from child count */ +#define no_child_count(rsc) \ + (strcmp((rsc)->class,"stonith") == 0) + +struct lrmd_rsc +{ + char* id; /* Unique resource identifier */ + char* type; /* */ + char* class; /* */ + char* provider; /* Resource provider (optional) */ + GHashTable* params; /* Parameters to this resource */ + /* as name/value pairs */ + GList* op_list; /* Queue of operations to run */ + GList* repeat_op_list; /* Unordered list of repeating */ + /* ops They will run later */ + GHashTable* last_op_table; /* Last operation of each type */ + lrmd_op_t* last_op_done; /* The last finished op of the resource */ + guint delay_timeout; /* The delay value of op_list execution */ + int state; /* status of the resource */ +}; + +struct lrmd_op +{ + char* rsc_id; + gboolean is_copy; + pid_t client_id; + int call_id; + int exec_pid; + guint repeat_timeout_tag; + int interval; + int delay; + gboolean is_cancelled; + int weight; + int copyparams; + struct ha_msg* msg; + ra_pipe_op_t * rapop; + char first_line_ra_stdout[80]; /* only for heartbeat RAs*/ + /*time stamps*/ + longclock_t t_recv; /* set in lrmd_op_new(), i.e. on op create */ + longclock_t t_addtolist; /* set in add_op_to_runlist() */ + longclock_t t_perform; /* set in perform_ra_op() */ + longclock_t t_done; /* set in on_op_done() */ + longclock_t t_rcchange; /* set in on_op_done(), could equal t_perform */ + longclock_t t_lastlogmsg; /* the last time the monitor op was logged */ + ProcTrackKillInfo killseq[3]; +}; + + +/* For reading the output from executing the RA */ +struct ra_pipe_op +{ + /* The same value of the one in corresponding lrmd_op */ + lrmd_op_t * lrmd_op; + int ra_stdout_fd; + int ra_stderr_fd; + GFDSource * ra_stdout_gsource; + GFDSource * ra_stderr_gsource; + gboolean first_line_read; + + /* For providing more detailed information in log */ + char * rsc_id; + char * op_type; + char * rsc_class; +}; + + +const char *gen_op_info(const lrmd_op_t* op, gboolean add_params); +#define op_info(op) gen_op_info(op,TRUE) +#define small_op_info(op) gen_op_info(op,FALSE) + +#define DOLRMAUDITS +#undef DOLRMAUDITS + +#define DOMEGALRMAUDITS +#define LRMAUDIT_CLIENTS +#define LRMAUDIT_RESOURCES + +#ifdef DOLRMAUDITS + + void lrmd_audit(const char *function, int line); + void audit_clients(void); + void audit_resources(void); + void audit_ops(GList* rsc_ops, lrmd_rsc_t *rsc, const char *desc); + void on_client(gpointer key, gpointer value, gpointer user_data); + void on_resource(gpointer key, gpointer value, gpointer user_data); + void on_op(lrmd_op_t *op, lrmd_rsc_t *rsc, const char *desc); + void on_ra_pipe_op(ra_pipe_op_t *rapop, lrmd_op_t *op, const char *desc); + +# define LRMAUDIT() lrmd_audit(__FUNCTION__,__LINE__) +# ifdef DOMEGALRMAUDITS +# define MEGALRMAUDIT lrmd_audit(__FUNCTION__,__LINE__) +# else +# define MEGALRMAUDIT /*nothing*/ +# endif +#else +# define LRMAUDIT() /*nothing*/ +# define MEGALRMAUDIT() /*nothing*/ +#endif + +/* + * load parameters from an ini file (cib_secrets.c) + */ +int replace_secret_params(char* rsc_id, GHashTable* params); diff --git a/lrm/lrmd/lrmd_fdecl.h b/lrm/lrmd/lrmd_fdecl.h new file mode 100644 index 0000000..9c97385 --- /dev/null +++ b/lrm/lrmd/lrmd_fdecl.h @@ -0,0 +1,111 @@ +/* TODO: This ought to be broken up into several source files for easier + * reading and debugging. */ + +/* Debug oriented funtions */ +static gboolean debug_level_adjust(int nsig, gpointer user_data); +static void dump_data_for_debug(void); + +/* glib loop call back functions */ +static gboolean on_connect_cmd(IPC_Channel* ch_cmd, gpointer user_data); +static gboolean on_connect_cbk(IPC_Channel* ch_cbk, gpointer user_data); +static int msg_type_cmp(const void *p1, const void *p2); +static gboolean on_receive_cmd(IPC_Channel* ch_cmd, gpointer user_data); +static gboolean on_repeat_op_readytorun(gpointer data); +static void on_remove_client(gpointer user_data); +static void destroy_pipe_ra_stderr(gpointer user_data); +static void destroy_pipe_ra_stdout(gpointer user_data); + +/* message handlers */ +static int on_msg_register(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_rsc_classes(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_rsc_types(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_rsc_providers(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_metadata(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_add_rsc(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_rsc(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_last_op(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_all(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_del_rsc(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_fail_rsc(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_cancel_op(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_flush_all(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_perform_op(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_state(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_set_lrmd_param(lrmd_client_t* client, struct ha_msg* msg); +static int on_msg_get_lrmd_param(lrmd_client_t* client, struct ha_msg* msg); +static int set_lrmd_param(const char *name, const char *value); +static int get_lrmd_param(const char *name, char *value, int maxstring); +static gboolean sigterm_action(int nsig, gpointer unused); + +/* functions wrap the call to ra plugins */ +static int perform_ra_op(lrmd_op_t* op); + +/* Apphb related functions */ +static int init_using_apphb(void); +static gboolean emit_apphb(gpointer data); + +/* Utility functions */ +static int flush_op(lrmd_op_t* op); +static gboolean rsc_execution_freeze_timeout(gpointer data); +static void add_op_to_runlist(lrmd_rsc_t* rsc, lrmd_op_t* op); +static int perform_op(lrmd_rsc_t* rsc); +static int unregister_client(lrmd_client_t* client); +static int on_op_done(lrmd_rsc_t* rsc, lrmd_op_t* op); +static int send_ret_msg ( IPC_Channel* ch, int rc); +static void send_cbk_msg(struct ha_msg* msg, lrmd_client_t* client); +static void send_msg(struct ha_msg* msg, lrmd_client_t* client); +static void notify_client(lrmd_op_t* op); +static lrmd_client_t* lookup_client (pid_t pid); +static lrmd_rsc_t* lookup_rsc (const char* rid); +static lrmd_rsc_t* lookup_rsc_by_msg (struct ha_msg* msg); +static int read_pipe(int fd, char ** data, gpointer user_data); +static gboolean handle_pipe_ra_stdout(int fd, gpointer user_data); +static gboolean handle_pipe_ra_stderr(int fd, gpointer user_data); +static struct ha_msg* op_to_msg(lrmd_op_t* op); +static int store_timestamps(lrmd_op_t* op); +static void reset_timestamps(lrmd_op_t* op); +static gboolean lrm_shutdown(void); +static gboolean can_shutdown(void); +static gboolean free_str_hash_pair(gpointer key +, gpointer value, gpointer user_data); +static gboolean free_str_op_pair(gpointer key +, gpointer value, gpointer user_data); +static lrmd_op_t* lrmd_op_copy(const lrmd_op_t* op); +static void send_last_op(gpointer key, gpointer value, gpointer user_data); +static void replace_last_op(lrmd_client_t* client, lrmd_rsc_t* rsc, lrmd_op_t* op); +static int record_op_completion(lrmd_rsc_t* rsc, lrmd_op_t* op); +static void to_repeatlist(lrmd_rsc_t* rsc, lrmd_op_t* op); +static void remove_op_history(lrmd_op_t* op); +static void hash_to_str(GHashTable * , GString *); +static void hash_to_str_foreach(gpointer key, gpointer value, gpointer userdata); +static void warning_on_active_rsc(gpointer key, gpointer value, gpointer user_data); +static void check_queue_duration(lrmd_op_t* op); +static gboolean flush_all(GList** listp, int client_pid); +static gboolean cancel_op(GList** listp,int cancel_op_id); +static int prepare_failmsg(struct ha_msg* msg, + int fail_rc, const char *fail_reason); +static void async_notify(gpointer key, gpointer val, gpointer data); +static gboolean client_cmp_name(gpointer key, gpointer val, gpointer app_name); +static lrmd_client_t* lookup_client_by_name(char *app_name); +static void calc_max_children(void); + +/* + * following functions are used to monitor the exit of ra proc + */ +static void on_ra_proc_registered(ProcTrack* p); +static void on_ra_proc_finished(ProcTrack* p, int status +, int signo, int exitcode, int waslogged); +static const char* on_ra_proc_query_name(ProcTrack* p); + + + +/* + * Daemon functions + * + * copy from the code of Andrew Beekhof + */ +static void usage(const char* cmd, int exit_status); +static int init_start(void); +static int init_stop(const char *pid_file); +static int init_status(const char *pid_file, const char *client_name); +static void lrmd_rsc_dump(char* rsc_id, const char * text); diff --git a/lrm/test/LRMBasicSanityCheck.in b/lrm/test/LRMBasicSanityCheck.in new file mode 100755 index 0000000..dbe8548 --- /dev/null +++ b/lrm/test/LRMBasicSanityCheck.in @@ -0,0 +1,55 @@ +#!/bin/sh + + # Copyright (c) 2004 International Business Machines + # Author: Huang Zhen + # + # This program is free software; you can redistribute it and/or + # modify it under the terms of the GNU General Public + # License as published by the Free Software Foundation; either + # version 2.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # +HBLIB=@libdir@/heartbeat +LRMD=$HBLIB/lrmd +LRMADMIN=@sbindir@/lrmadmin +export LRMD LRMADMIN + +if [ $# -gt 0 ]; then + LRMD=$1/lrmd +fi + +if [ ! -f $LRMD ]; then + echo $LRMD does not exist + exit 1 +fi + +if [ ! -f $LRMADMIN ]; then + echo $LRMADMIN does not exist + exit 1 +fi + +OUTDIR=/tmp/LRM_BSC_$$ +export OUTDIR +[ -d $OUTDIR ] && { + echo $OUTDIR exists, please cleanup + exit 1 +} + +`dirname $0`/regression.sh -q set:BSC +rc=$? +if [ $rc -eq 0 ]; then + echo "LRM tests PASSED" + rm -rf $OUTDIR +else + echo "LRM tests FAILED" + echo "Please check $OUTDIR for results" +fi +exit $rc diff --git a/lrm/test/Makefile.am b/lrm/test/Makefile.am new file mode 100644 index 0000000..84f6657 --- /dev/null +++ b/lrm/test/Makefile.am @@ -0,0 +1,48 @@ +# +# Author: Sun Jiang Dong +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +SUBDIRS = testcases + +INCLUDES = -I$(top_builddir)/include -I$(top_srcdir)/include \ + -I$(top_builddir)/libltdl -I$(top_srcdir)/libltdl + +COMMONLIBS = $(top_builddir)/lib/clplumbing/libplumb.la $(GLIBLIB) + +noinst_PROGRAMS = apitest plugintest callbacktest + +apitest_SOURCES = apitest.c +apitest_LDFLAGS = $(COMMONLIBS) +apitest_LDADD = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la +apitest_DEPENDENCIES = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la + +plugintest_SOURCES = plugintest.c +plugintest_LDADD = $(COMMONLIBS) +plugintest_LDFLAGS = -L$(top_builddir)/lib/pils -lpils @LIBLTDL@ + +callbacktest_SOURCES = callbacktest.c +callbacktest_LDFLAGS = $(COMMONLIBS) +callbacktest_LDADD = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la +callbacktest_DEPENDENCIES = $(top_builddir)/lib/$(LRM_DIR)/liblrm.la + +testdir = $(datadir)/$(PACKAGE_NAME)/lrmtest +test_SCRIPTS = LRMBasicSanityCheck regression.sh evaltest.sh lrmregtest lrmregtest-lsb +test_DATA = README.regression defaults descriptions lrmadmin-interface language +# shouldn't need this, but we do for some versions of autofoo tools +EXTRA_DIST = $(test_SCRIPTS) $(test_DATA) diff --git a/lrm/test/README.regression b/lrm/test/README.regression new file mode 100644 index 0000000..3588172 --- /dev/null +++ b/lrm/test/README.regression @@ -0,0 +1,164 @@ +LRM regression tests + +* WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * +* +* evaltest.sh uses eval to an extent you don't really want to +* know about. Beware. Beware twice. Any input from the testcases +* directory is considered to be trusted. So, think twice before +* devising your tests lest you kill your precious data. Got it? +* Good. +* +* Furthermore, we are deliberately small on testing the user +* input and no one should try to predict what is to happen on +* random input from the testcases. +* +* WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * + +Manifest + + regression.sh: the top level program + evaltest.sh: the engine test engine + + lrmadmin-interface: interface to lrmd (lrmadmin) + descriptions: describe what we are about to do + defaults: the default settings for test commands + + testcases/: here are the testcases and filters + output/: here goes the output + +All volatile data lives in the testcases/ directory. + +NB: You should never ever need to edit regression.sh and +evaltest.sh. If you really have to, please talk to me and I will +try to fix it so that you do not have to. + +Please write new test cases. The more the merrier :) + +Usage + +The usage is: + + ./regression.sh ["prepare"] ["set:"|] + +Test cases are collected in test sets. The default test set is +basicset and running regression.sh without arguments will do all +tests from that set. + +To show progress, for each test a '.' is printed. For sleeps, +a '+' for each second. Once all tests have been evaluated, the +output is checked against the expect file. If successful, "PASS" +is printed, otherwise "FAIL". + +Specifying "prepare" will make regression.sh create expect +output files for the given set of tests or testcase. + +The script will start and stop lrmd itself. stonithd is also +started to test the XML descriptions printed by stonith agents. +No other parts of stonithd functionality is tested. + +The following files may be generated: + + output/.out: the output of the testcase + output/regression.out: the output of regression.sh + output/lrmd.out: the output of lrmd + +On success output from testcases is removed and regression.out is +empty. + +Driving the test cases yourself + +evaltest.sh accepts input from stdin, evaluates it immediately, +and prints results to stdout/stderr. One can perhaps get a better +feeling of what's actually going on by running it interactively. +Please note that you have to start the lrmd yourself beforehand. + +Test cases + +Tests are written in a simple metalanguage. The list of commands +with rough translation to lrmadmin's options is in the language +file. The best description of the language is in the +lrmadmin-interface and descriptions scripts: + +$ egrep '^lrm|echo' lrmadmin-interface descriptions + +A test case is a list of tests, one per line. A few examples: + + add # add a resource with default name + list # list all resources + del rsc=wiwi # remove a wiwi resource + +A set of defaults for LRM options is in the defaults file. That's +why we can write short forms instead of + + add rsc=r1 class=ocf type=lrmregtest provider=heartbeat ... + +Special operations + +There are special operations with which it is possible to change +environment and do other useful things. All special ops start +with the '%' sign and may be followed by additional parameters. + +%setenv + change the environment variable; see defaults for the + set of global variables and resetvars() in evaltest.sh + +%sleep + sleep + +%stop + skip the rest of the tests + +%extcheck + feed the output of the next test case to the specified + external program/filter; the program should either reside in + testcases/ or be in the PATH, i.e. + + %extcheck cat + + simulates a null op :) + + see testcases/metadata for some examples + +%repeat num + repeat the next test num times + there are several variables which are substituted in the test + lines, so that we can simulate a for loop: + + s/%t/$test_cnt/g + s/%l/$line/g + s/%j/$job_cnt/g + s/%i/$repeat_cnt/g + + for example, to add 10 resources: + + %repeat 10 + add rsc=r-%i + +%bg [num] + run next num (or just the next one) tests in background + +%bgrepeat [num] + a combination of the previous two (used often) + +%wait + wait for the last background test to finish + +%shell + feed whatever is in the rest of the line to 'sh -s' + +Filters and except files + +Some output is necessarily very volatile, such as time stamps. +It is possible to specify a filter for each testcase to get rid +of superfluous information. A filter is a filter in UNIX +sense, it takes input from stdin and prints results to stdout. + +There is a common filter called very inventively +testcases/common.filter which is applied to all test cases. + +Except files are a list of extended regular expressions fed to +egrep(1). That way one can filter out lines which are not +interesting. Again, the one applied to all is +testcases/common.excl. + + diff --git a/lrm/test/apitest.c b/lrm/test/apitest.c new file mode 100644 index 0000000..0d4c572 --- /dev/null +++ b/lrm/test/apitest.c @@ -0,0 +1,317 @@ + +/* + * Test program for Local Resource Manager API. + * + * Copyright (C) 2004 Huang Zhen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void lrm_op_done_callback (lrm_op_t* op); +void printf_rsc(lrm_rsc_t* rsc); +void printf_op(lrm_op_t* op); +void printf_hash_table(GHashTable* hash_table); +void get_all_rsc(ll_lrm_t* lrm); +void get_cur_state(lrm_rsc_t* rsc); + +int main (int argc, char* argv[]) +{ + ll_lrm_t* lrm; + lrm_rsc_t* rsc = NULL; + lrm_op_t* op = NULL; + const char* rid = "ip248"; + GHashTable* param = NULL; + GList* classes; + int i; + + cl_log_set_entity("apitest"); + cl_log_set_facility(LOG_USER); + + lrm = ll_lrm_new("lrm"); + + if(NULL == lrm) + { + printf("lrm==NULL\n"); + return 1; + } + puts("sigon..."); + lrm->lrm_ops->signon(lrm,"apitest"); + + classes = lrm->lrm_ops->get_rsc_class_supported(lrm); + lrm_free_str_list(classes); + + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + puts("add_rsc..."); + lrm->lrm_ops->add_rsc(lrm, rid, "heartbeat", "IPaddr", "heartbeat", param); + puts("get_rsc..."); + rsc = lrm->lrm_ops->get_rsc(lrm, rid); + printf_rsc(rsc); + + puts("perform_op(start)..."); + op = lrm_op_new(); + op->op_type = g_strdup("start"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a start op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc = EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(status)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("status"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a status op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 1000; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(stop)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("stop"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a stop op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(status)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("status"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a status op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 2000; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(start)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("start"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a start op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc = EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(status)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("status"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a status op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 3000; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + puts("perform_op(stop)..."); + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("192.168.192.100")); + op = lrm_op_new(); + op->op_type = g_strdup("stop"); + op->params = param; + op->timeout = 0; + op->user_data = strdup("It is a stop op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + lrm_free_op(op); + + for(i = 0; i < 5; i++) { + puts("get_cur_state..."); + get_cur_state(rsc); + puts("sleep a while..."); + sleep(1); + } + + puts("delete_rsc..."); + lrm->lrm_ops->delete_rsc(lrm, rid); + lrm_free_rsc(rsc); + + puts("signoff..."); + lrm->lrm_ops->signoff(lrm); + + return 0; +} +void lrm_op_done_callback(lrm_op_t* op) +{ + puts("lrm_op_done_callback..."); + printf_op(op); +} +void printf_rsc(lrm_rsc_t* rsc) +{ + printf("print resource>>>>>>>>>\n"); + if (NULL == rsc) { + printf("resource is null\n"); + printf("print end\n"); + return; + } + printf("\tresource of id:%s\n", rsc->id); + printf("\ttype:%s\n", rsc->type); + printf("\tclass:%s\n", rsc->class); + printf("\tparams:\n"); + printf_hash_table(rsc->params); + printf("print end<<<<<<<<<<<<<<<\n"); +} + +void printf_op(lrm_op_t* op) +{ + printf("print op>>>>>>>>>>>>>>>>\n"); + + if (NULL == op) { + printf("op is null\n"); + printf("print end\n"); + return; + } + + printf("\top_type:%s\n",op->op_type?op->op_type:"null"); + printf("\tparams:\n"); + printf_hash_table(op->params); + printf("\ttimeout:%d\n",op->timeout); + printf("\tuser_data:%s\n",op->user_data?(char*)op->user_data:"null"); + printf("\top_status:%d\n",op->op_status); + printf("\tapp_name:%s\n",op->app_name?op->app_name:"null"); + printf("\toutput:%s\n",op->output?op->output:"null"); + printf("\trc:%d\n",op->rc); + printf("\tcall_id:%d\n",op->call_id); + printf("print end<<<<<<<<<<<<<<<<<<\n"); +} + +static void +printf_pair(gpointer key, gpointer value, gpointer user_data) +{ + printf("\t\t%s=%s\n",(char*)key,(char*)value); +} +void +printf_hash_table(GHashTable* hash_table) +{ + if (NULL == hash_table) { + printf("\t\tnull\n"); + return; + } + g_hash_table_foreach(hash_table, printf_pair, NULL); +} +void +get_all_rsc(ll_lrm_t* lrm) +{ + GList* element = NULL, * rid_list = NULL; + + puts("get_all_rscs..."); + rid_list = lrm->lrm_ops->get_all_rscs(lrm); + if (NULL != rid_list) { + element = g_list_first(rid_list); + while (NULL != element) { + printf("\tid:%s\n",(char*)element->data); + element = g_list_next(element); + } + } else { + puts("\tnone."); + } + lrm_free_str_list(rid_list); +} +void +get_cur_state(lrm_rsc_t* rsc) +{ + state_flag_t state; + GList* node = NULL, * op_list = NULL; + lrm_op_t* op = NULL; + printf("current state>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + + op_list = rsc->ops->get_cur_state(rsc, &state); + + printf("\tcurrent state:%s\n",state==LRM_RSC_IDLE?"Idle":"Busy"); + + + for(node = g_list_first(op_list); NULL != node; + node = g_list_next(node)) { + op = (lrm_op_t*)node->data; + printf_op(op); + } + lrm_free_op_list(op_list); + printf("current end<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); +} diff --git a/lrm/test/apitest.exp b/lrm/test/apitest.exp new file mode 100644 index 0000000..b153ee3 --- /dev/null +++ b/lrm/test/apitest.exp @@ -0,0 +1,122 @@ +sigon... +add_rsc... +get_rsc... +print resource + resource of id:ip248 + type:IPv6addr + class:heartbeat + params: + 1=3ffe:ffff:0:f101::3 +print end +perform_op(start)... +print op + op_type:start + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a start op! + op_status:0 + app_name:null + output:null + rc:0 +print end +perform_op(status)... +print op + op_type:status + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a status op! + op_status:0 + app_name:null + output:null + rc:0 +print end +perform_op(stop)... +print op + op_type:stop + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a stop op! + op_status:0 + app_name:null + output:null + rc:0 +print end +get_cur_state... + current state:Busy +print op + op_type:start + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a start op! + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +print op + op_type:status + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a status op! + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +print op + op_type:stop + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:It is a stop op! + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +stop_op... +get_cur_state... + current state:Busy +print op + op_type:start + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:null + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +print op + op_type:stop + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:null + op_status:-1 + app_name:apitest + output:null + rc:0 +print end +sleep a while... +get_cur_state... + current state:Idel +print op + op_type:stop + params: + 1=3ffe:ffff:0:f101::3 + timeout:0 + user_data:null + op_status:0 + app_name:apitest + output:null + rc:0 +print end +delete_rsc... +signoff... diff --git a/lrm/test/callbacktest.c b/lrm/test/callbacktest.c new file mode 100644 index 0000000..48f4d49 --- /dev/null +++ b/lrm/test/callbacktest.c @@ -0,0 +1,204 @@ + +/* + * Test program for the callback function of Local Resource Manager API. + * + * Copyright (C) 2004 Huang Zhen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void lrm_op_done_callback(lrm_op_t *op); +static void printf_rsc(lrm_rsc_t *rsc); +static void printf_op(lrm_op_t *op); +static void printf_hash_table(GHashTable *hash_table); +static gboolean lrm_dispatch(IPC_Channel *notused, gpointer user_data); +static GMainLoop *mainloop; + +int +main(int argc, char *argv[]) +{ + ll_lrm_t* lrm; + lrm_rsc_t* rsc = NULL; + lrm_op_t* op = NULL; + const char* rid = "ip248"; + GHashTable* param = NULL; + + lrm = ll_lrm_new("lrm"); + + if(NULL == lrm) + { + printf("lrm==NULL\n"); + return 1; + } + puts("sigon..."); + lrm->lrm_ops->signon(lrm,"apitest"); + lrm->lrm_ops->set_lrm_callback(lrm, lrm_op_done_callback); + + param = g_hash_table_new(g_str_hash,g_str_equal); + g_hash_table_insert(param, strdup("1"), strdup("3ffe:ffff:0:f101::3")); + puts("add_rsc..."); + lrm->lrm_ops->add_rsc(lrm, rid, "heartbeat", "IPv6addr", NULL, param); + puts("get_rsc..."); + rsc = lrm->lrm_ops->get_rsc(lrm, rid); + printf_rsc(rsc); + + puts("perform_op(start)..."); + op = lrm_op_new(); + op->op_type = g_strdup("start"); + op->params = NULL; + op->timeout = 0; + op->user_data = strdup("It is a start op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc = EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + + puts("perform_op(status)..."); + op = lrm_op_new(); + op->op_type = g_strdup("status"); + op->params = NULL; + op->timeout = 0; + op->user_data = strdup("It is a status op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 1000; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + + puts("perform_op(stop)..."); + op = lrm_op_new(); + op->op_type = g_strdup("stop"); + op->params = NULL; + op->timeout = 0; + op->user_data = strdup("It is a stop op!"); + if ( op->user_data == NULL ) { + fprintf(stderr, "No enough memory.\n"); + return -1; + } + op->user_data_len = strlen(op->user_data)+1; + op->interval = 0; + op->target_rc=EVERYTIME; + rsc->ops->perform_op(rsc,op); + printf_op(op); + + G_main_add_IPC_Channel(G_PRIORITY_LOW, + lrm->lrm_ops->ipcchan(lrm), + FALSE, + lrm_dispatch, lrm, + NULL); + + mainloop = g_main_new(FALSE); + g_main_run(mainloop); + + puts("delete_rsc..."); + lrm->lrm_ops->delete_rsc(lrm, rid); + + puts("signoff..."); + lrm->lrm_ops->signoff(lrm); + + return 0; +} + +static void +lrm_op_done_callback(lrm_op_t *op) +{ + puts("lrm_op_done_callback..."); + printf_op(op); +} + +static gboolean +lrm_dispatch(IPC_Channel *notused, gpointer user_data) +{ + ll_lrm_t *lrm = (ll_lrm_t*)user_data; + lrm->lrm_ops->rcvmsg(lrm, FALSE); + return TRUE; +} + +static void +printf_rsc(lrm_rsc_t *rsc) +{ + printf("print resource\n"); + if (NULL == rsc) { + printf("resource is null\n"); + printf("print end\n"); + return; + } + printf("\tresource of id:%s\n", rsc->id); + printf("\ttype:%s\n", rsc->type); + printf("\tclass:%s\n", rsc->class); + printf("\tparams:\n"); + printf_hash_table(rsc->params); + printf("print end\n"); +} + +static void +printf_op(lrm_op_t *op) +{ + printf("print op\n"); + + if (NULL == op) { + printf("op is null\n"); + printf("print end\n"); + return; + } + + printf("\top_type:%s\n",op->op_type?op->op_type:"null"); + printf("\tparams:\n"); + printf_hash_table(op->params); + printf("\ttimeout:%d\n",op->timeout); + printf("\tuser_data:%s\n",op->user_data?(char*)op->user_data:"null"); + printf("\tuser_data pointer:%p\n",op->user_data); + printf("\top_status:%d\n",op->op_status); + printf("\tapp_name:%s\n",op->app_name?op->app_name:"null"); + printf("\toutput:%s\n",op->output?op->output:"null"); + printf("\trc:%d\n",op->rc); +/* printf("\tcall_id:%d\n",op->call_id); */ + printf("print end\n"); +} + +static void +printf_pair(gpointer key, gpointer value, gpointer user_data) +{ + printf("\t\t%s=%s\n",(char*)key,(char*)value); +} + +static void +printf_hash_table(GHashTable *hash_table) +{ + if (NULL == hash_table) { + printf("\t\tnull\n"); + return; + } + g_hash_table_foreach(hash_table, printf_pair, NULL); +} diff --git a/lrm/test/defaults b/lrm/test/defaults new file mode 100644 index 0000000..039915b --- /dev/null +++ b/lrm/test/defaults @@ -0,0 +1,9 @@ +# defaults +: ${dflt_rsc:=r1} +: ${dflt_type:=lrmregtest} +: ${dflt_class:=ocf} +: ${dflt_provider:=heartbeat} +: ${dflt_timeout:=1000} +: ${dflt_interval:=0} +: ${dflt_targetrc:=EVERYTIME} +dflt_args="" diff --git a/lrm/test/descriptions b/lrm/test/descriptions new file mode 100644 index 0000000..f2aab6b --- /dev/null +++ b/lrm/test/descriptions @@ -0,0 +1,55 @@ +lead=".TRY" +describe_list() { + echo $lead List resources +} +describe_add() { + echo $lead Add resource \ + ${rsc:-$dflt_rsc} \ + class=${class:-$dflt_class} type=${type:-$dflt_type} \ + provider=${provider:-$dflt_provider} \ + args=$args +} +describe_del() { + echo $lead Delete resource \ + ${rsc:-$dflt_rsc} +} +describe_flush() { + echo $lead Flush resource \ + ${rsc:-$dflt_rsc} +} +describe_state() { + echo $lead Show state \ + ${rsc:-$dflt_rsc} +} +describe_info() { + echo $lead Show info \ + ${rsc:-$dflt_rsc} +} +describe_exec() { + echo $lead Exec \ + ${rsc:-$dflt_rsc} \ + op=${operation:-$dflt_operation} \ + timeout=${timeout:-$dflt_timeout} interval=${interval:-$dflt_interval} \ + target=${targetrc:-$dflt_targetrc} args=$args +} + +describe_classes() { + echo $lead List classes +} +describe_types() { + echo $lead List types \ + class=${class:-$dflt_class} +} +describe_classmeta() { + echo $lead Meta-data \ + class=${class:-$dflt_class} +} +describe_meta() { + echo $lead Show meta-data \ + class=${class:-$dflt_class} \ + type=${type:-$dflt_type} provider=${provider:-$dflt_provider} +} +describe_provider() { + echo $lead Show provider \ + class=${class:-$dflt_class} type=${type:-$dflt_type} +} diff --git a/lrm/test/evaltest.sh b/lrm/test/evaltest.sh new file mode 100755 index 0000000..f369102 --- /dev/null +++ b/lrm/test/evaltest.sh @@ -0,0 +1,171 @@ +#!/bin/sh + + # Copyright (C) 2007 Dejan Muhamedagic + # + # This program is free software; you can redistribute it and/or + # modify it under the terms of the GNU General Public + # License as published by the Free Software Foundation; either + # version 2.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +: ${TESTDIR:=testcases} +: ${LRMADMIN:=../admin/lrmadmin} +test -x $LRMADMIN || LRMADMIN=lrmadmin +: ${OCF_ROOT:=/usr/lib/ocf} + +. ./defaults +. ./lrmadmin-interface +. ./descriptions + +resetvars() { + unset rsc type class provider timeout interval targetrc args + unset extcheck +} + +# +# special operations squad +# +specopt_setenv() { + eval $rest +} +specopt_sleep() { + #sleep $rest + # the while loop below is the same + # but we give user some feedback on what's happening + while [ "$rest" -gt 0 ]; do + sleep 1 + echo -n "+" >&3 + rest=$(($rest-1)) + done +} +specopt_extcheck() { + extcheck="$rest" + set $extcheck + which "$1" >/dev/null 2>&1 || # a program in the PATH + extcheck="$TESTDIR/$extcheck" # or our script +} +specopt_repeat() { + repeat_limit=$rest +} +specopt_bg() { + if [ "$job_cnt" -gt "$bgprocs_num" ]; then + bgprocs_num=${rest:-1} + job_cnt=1 + else + echo ".BG bad usage: more tests yet to be backgrounded" + fi +} +specopt_bgrepeat() { # common + specopt_bg + specopt_repeat +} +specopt_wait() { # common + waitforbgprocs +} +specopt_shell() { # run command with shell + echo "$rest" | sh -s | # and execute the command + { [ "$extcheck" ] && $extcheck || cat;} +} +specopt() { + cmd=`echo $cmd | sed 's/%//'` # strip leading '%' + echo ".`echo $cmd | tr '[a-z]' '[A-Z]'` $rest" # show what we got + specopt_$cmd # do what they asked for +} + +# +# wait for background processes to finish +# and print their output +# NB: We wait for processes in a FIFO order +# The order in which they finish does not matter +# +waitforbgprocs() { + while [ "$bgprocs" ]; do + set $bgprocs + proc=$1 # get the first one + shift 1 # remove it from the list + bgprocs="$@" + IFS=":" + set $proc # split into lineno,pid + testline=$1 jobnum=$2 pid=$3 + unset IFS + + while kill -0 $pid 2>/dev/null; do + sleep 1 + done + wait $pid # capture the exit code + + echo ".BG test line $testline/job $jobnum finished (exit code: $?):" + echo "==========test:$testline:$jobnum start output==========" + cat $OUTDIR/bg$$-$testline-$jobnum + echo "==========test:$testline:$jobnum end output==========" + rm -f $OUTDIR/bg$$-$testline-$jobnum + done +} + +# +# substitute variables in the test line +# +substvars() { + sed " + s/%t/$test_cnt/g + s/%l/$line/g + s/%j/$job_cnt/g + s/%i/$repeat_cnt/g + " +} + +dotest() { + echo -n "." >&3 + test_cnt=$(($test_cnt+1)) + describe_$cmd # show what we are about to do + lrm_$cmd | # and execute the command + { [ "$extcheck" ] && $extcheck || cat;} +} +runonetest() { + eval `echo $rest | substvars` # set parameters + if [ "$job_cnt" -le "$bgprocs_num" ]; then + echo .BG test line $line/job $job_cnt runs in background + dotest > $OUTDIR/bg$$-$line-$job_cnt 2>&1 & + bgprocs="$bgprocs $line:$job_cnt:$!" + job_cnt=$(($job_cnt+1)) + else + dotest + fi +} +runtest() { + while [ $repeat_cnt -le $repeat_limit ]; do + runonetest + resetvars # unset all variables + repeat_cnt=$(($repeat_cnt+1)) + done + repeat_limit=1 repeat_cnt=1 +} + +# +# run the tests +# +bgprocs_num=0 job_cnt=1 +repeat_limit=1 repeat_cnt=1 +line=1 +test_cnt=1 + +while read cmd rest; do + case "$cmd" in + "") : empty ;; + "#"*) : a comment ;; + "%stop") break ;; + "%"*) specopt ;; + *) runtest ;; + esac + line=$(($line+1)) +done +waitforbgprocs diff --git a/lrm/test/language b/lrm/test/language new file mode 100644 index 0000000..d2785e8 --- /dev/null +++ b/lrm/test/language @@ -0,0 +1,16 @@ +The meta language and how it translates to the lrmadmin options: + +list:-L +add:-A %r %C %T %P +del:-D %r +flush:-F %r +state:-S %r +info:-I %r +exec:-E %r %o %t %i %e + +classes:-C +types:-T %C +classmeta:-O %C +meta:-M %C %T %P +provider:-P %C %T + diff --git a/lrm/test/lrmadmin-interface b/lrm/test/lrmadmin-interface new file mode 100644 index 0000000..4eb1656 --- /dev/null +++ b/lrm/test/lrmadmin-interface @@ -0,0 +1,43 @@ +lrm_list() { + $LRMADMIN -L +} +lrm_add() { + $LRMADMIN -A ${rsc:-$dflt_rsc} \ + ${class:-$dflt_class} ${type:-$dflt_type} \ + ${provider:-$dflt_provider} \ + $args +} +lrm_del() { + $LRMADMIN -D ${rsc:-$dflt_rsc} +} +lrm_flush() { + $LRMADMIN -F ${rsc:-$dflt_rsc} +} +lrm_state() { + $LRMADMIN -S ${rsc:-$dflt_rsc} +} +lrm_info() { + $LRMADMIN -I ${rsc:-$dflt_rsc} +} +lrm_exec() { + $LRMADMIN -E ${rsc:-$dflt_rsc} \ + ${operation:-$dflt_operation} \ + ${timeout:-$dflt_timeout} ${interval:-$dflt_interval} \ + ${targetrc:-$dflt_targetrc} $args +} + +lrm_classes() { + $LRMADMIN -C +} +lrm_types() { + $LRMADMIN -T ${class:-$dflt_class} +} +lrm_classmeta() { + $LRMADMIN -O ${class:-$dflt_class} +} +lrm_meta() { + $LRMADMIN -M ${class:-$dflt_class} ${type:-$dflt_type} ${provider:-$dflt_provider} +} +lrm_provider() { + $LRMADMIN -P ${class:-$dflt_class} ${type:-$dflt_type} +} diff --git a/lrm/test/lrmregtest-lsb b/lrm/test/lrmregtest-lsb new file mode 100644 index 0000000..4692b17 --- /dev/null +++ b/lrm/test/lrmregtest-lsb @@ -0,0 +1,54 @@ +#!/bin/sh +# +# WARNING: This script is for LRM regressions tests only +# +### BEGIN INIT INFO +# Provides: lrmregtest +# Required-Start: +# Should-Start: +# Required-Stop: +# Should-Stop: +# Default-Start: +# Default-Stop: +# Short-Description: LRM regression tests LSB RA +# Description: LRM regression tests LSB RA +### END INIT INFO + +TYPE=lrmregtest +# depends on resource-agents and the OCF +: ${OCF_ROOT:=/usr/lib/ocf} +. ${OCF_ROOT}/lib/heartbeat/ocf-shellfuncs + +case "$1" in + start) + echo -n "Starting $TYPE" + ha_pseudo_resource lrmregtest_lsb start + ;; + stop) + echo -n "Shutting down $TYPE" + ha_pseudo_resource lrmregtest_lsb stop + ;; + status) + echo -n "Checking for $TYPE" + ha_pseudo_resource lrmregtest_lsb monitor + if [ $? -eq 0 ]; then + echo " running" + exit 0 + else + echo " stopped" + exit 3 + fi + ;; + *) + echo "Usage: $0 {start|stop|status}" + exit 1 + ;; +esac + +if [ $? -eq 0 ]; then + echo " OK" + exit 0 +else + echo " failed" + exit 1 +fi diff --git a/lrm/test/lrmregtest.in b/lrm/test/lrmregtest.in new file mode 100644 index 0000000..001a662 --- /dev/null +++ b/lrm/test/lrmregtest.in @@ -0,0 +1,220 @@ +#!/bin/sh +# +# +# lrmregtest OCF RA. Does nothing but wait a few seconds, can be +# configured to fail occassionally. +# +# updated to support the LRM regression testing. +# +# Copyright (c) 2007 SUSE LINUX AG, Dejan Muhamedagic +# All Rights Reserved. +# +# Copyright (c) 2004 SUSE LINUX AG, Lars Marowsky-Brée +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. +# + +####################################################################### +# Initialization: + +. ${OCF_ROOT}/lib/heartbeat/ocf-shellfuncs + +####################################################################### + +meta_data() { + cat < + + +1.0 + + +This is a lrmregtest Resource Agent. Use for LRM regression +testing. + +lrmregtest resource agent + + + + +How long to delay before each action. + +Action delay + + + + + +Complain loudly if they try to run us in parallel on the same resource. + +Report error if run twice at the same time + + + + + +Process the TERM signal and don't exit. + +No TERM ain't gonna kill us. + + + + + +Print more information. + +Be verbose. + + + + + + + + + + + + + + + + +END +} + +####################################################################### + +# don't exit on TERM, to test that lrmd makes sure that we do exit +sigterm_handler() { + ocf_log info "They use TERM to bring us down. No such luck." + return +} + +dummy_usage() { + cat </dev/null + then + ocf_log err "There is another instance of ${OCF_RESOURCE_INSTANCE} running: pid `cat $lockf`." + exit $OCF_ERR_GENERIC + fi +} + +[ "$OCF_RESKEY_check_parallel" = 1 ] && + check4parallel + +[ "$OCF_RESKEY_ignore_TERM" = 1 ] && + trap sigterm_handler TERM + +echo $$ > $lockf +trap "rm -f $lockf" EXIT + +verbose && invocation $@ + +case $__OCF_ACTION in +meta-data) meta_data + exit $OCF_SUCCESS + ;; +start) dummy_start;; +stop) dummy_stop;; +monitor) dummy_monitor;; +migrate_to) ocf_log info "Migrating ${OCF_RESOURCE_INSTANCE} to ${OCF_RESKEY_CRM_meta_migrate_to}." + dummy_stop + ;; +migrate_from) ocf_log info "Migrating ${OCF_RESOURCE_INSTANCE} to ${OCF_RESKEY_CRM_meta_migrated_from}." + dummy_start + ;; +reload) ocf_log err "Reloading..." + dummy_start + ;; +validate-all) dummy_validate;; +usage|help) dummy_usage + exit $OCF_SUCCESS + ;; +*) dummy_usage + exit $OCF_ERR_UNIMPLEMENTED + ;; +esac +rc=$? +ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc" +exit $rc + diff --git a/lrm/test/plugintest.c b/lrm/test/plugintest.c new file mode 100644 index 0000000..d25c46d --- /dev/null +++ b/lrm/test/plugintest.c @@ -0,0 +1,84 @@ +/* File: plugintest.c + * Description: A small,simple tool to test RA execution plugin + * + * Author: Sun Jiang Dong + * Copyright (c) 2004 International Business Machines + * + * Todo: security verification + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include + +static void +g_print_item(gpointer data, gpointer user_data) +{ + printf("%s\n", (char*)data); +} + + +int main(void) +{ + PILPluginUniv * PluginLoadingSystem = NULL; + GHashTable * RAExecFuncs = NULL; + GList * ratype_list; + struct RAExecOps * RAExec; + /* + GHashTable * cmd_params; + */ + int ret; + + PILGenericIfMgmtRqst RegisterRqsts[]= { + {"RAExec", &RAExecFuncs, NULL, NULL, NULL}, + { NULL, NULL, NULL, NULL, NULL} }; + + PluginLoadingSystem = NewPILPluginUniv ("/usr/lib/heartbeat/plugins"); + + PILLoadPlugin(PluginLoadingSystem , "InterfaceMgr", "generic" , &RegisterRqsts); + + PILLoadPlugin(PluginLoadingSystem , "RAExec", "ocf", NULL); + RAExec = g_hash_table_lookup(RAExecFuncs,"ocf"); + ret = RAExec->get_resource_list(&ratype_list); + printf("length=%d\n", g_list_length(ratype_list)); + if (ret >= 0) { + g_list_foreach(ratype_list, g_print_item, NULL); + } + + /* + PILLoadPlugin(PluginLoadingSystem , "RAExec", "lsb", NULL); + RAExec = g_hash_table_lookup(RAExecFuncs,"lsb"); + cmd_params = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(cmd_params, g_strdup("1"), g_strdup("par1")); + g_hash_table_insert(cmd_params, g_strdup("2"), g_strdup("par2")); + ret = RAExec->execra("/tmp/test.sh", "start", cmd_params,NULL); + */ + + /* For test the dealing with directory appended to RA */ + /* + PILLoadPlugin(PluginLoadingSystem , "RAExec", "ocf", NULL); + RAExec = g_hash_table_lookup(RAExecFuncs,"ocf"); + if (0>RAExec->execra("/root/linux-ha-checkout/linux-ha/lrm/test.sh", + "stop",NULL,NULL, TRUE, &key)) + */ + printf("execra result: ret = %d\n", ret); + return -1; +} diff --git a/lrm/test/regression.sh.in b/lrm/test/regression.sh.in new file mode 100755 index 0000000..550321e --- /dev/null +++ b/lrm/test/regression.sh.in @@ -0,0 +1,248 @@ +#!/bin/sh + + # Copyright (C) 2007 Dejan Muhamedagic + # + # This program is free software; you can redistribute it and/or + # modify it under the terms of the GNU General Public + # License as published by the Free Software Foundation; either + # version 2.1 of the License, or (at your option) any later version. + # + # This software is distributed in the hope that it will be useful, + # but WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + # General Public License for more details. + # + # You should have received a copy of the GNU General Public + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + # + +OCF_ROOT=@OCF_ROOT_DIR@ +export OCF_ROOT +if [ -z "$OCF_ROOT" ]; then + [ -d /usr/lib/ocf ] && OCF_ROOT=/usr/lib/ocf +fi +if [ ! -d "$OCF_ROOT" ]; then + echo "OCF_ROOT environment variable not set" + exit 2 +fi + +TESTDIR=${TESTDIR:-testcases} +DFLT_TESTSET=basicset +OUTDIR=${OUTDIR:-output} +LRMD_OUTF="$OUTDIR/lrmd.out" +LRMD_LOGF="$OUTDIR/lrmd.log" +LRMD_DEBUGF="$OUTDIR/lrmd.debug" +OUTF="$OUTDIR/regression.out" +LRMADMIN="@sbindir@/lrmadmin" +LRMD_OPTS="-vvv" +STONITHD_OPTS="-at" +DIFF_OPTS="--ignore-all-space -U 1" +common_filter=$TESTDIR/common.filter +common_exclf=$TESTDIR/common.excl +OCF_RA=$OCF_ROOT/resource.d/heartbeat/lrmregtest +LSB_RA=@LSB_RA_DIR@/lrmregtest +export OUTDIR TESTDIR LRMADMIN + +logmsg() { + echo "`date`: $*" | tee -a $LRMD_DEBUGF | tee -a $LRMD_LOGF +} +abspath() { + echo $1 | grep -qs "^/" && + echo $1 || + echo `pwd`/$1 +} + +usage() { + cat</dev/null 2>&1 || { + echo "WARNING: xmllint not available, some of the tests may fail" +} + +rm -f $LRMD_LOGF $LRMD_DEBUGF + +# make lrmd log to our files only +HA_logfile=`abspath $LRMD_LOGF` +HA_debugfile=`abspath $LRMD_DEBUGF` +HA_use_logd=no +HA_logfacility="" +export HA_logfile HA_debugfile HA_use_logd HA_logfacility + +mkdir -p $OUTDIR +. ${OCF_ROOT}/lib/heartbeat/ocf-shellfuncs + +args=`getopt hq $*` +[ $? -ne 0 ] && usage +eval set -- "$args" + +SILENT="" +while [ x"$1" != x ]; do + case "$1" in + -h) usage;; + -q) SILENT=1;; + --) shift 1; break;; + *) usage;; + esac + shift 1 +done + +exec >$OUTF 2>&1 +if [ "$SILENT" = 1 ]; then + exec 3>/dev/null +else + exec 3>/dev/tty +fi + +start_stonithd() { + echo "starting stonithd" >&3 + $HA_BIN/stonithd -s 2>/dev/null + if [ $? -ne 0 ]; then + STOP_STONITHD=1 + $HA_BIN/stonithd $STONITHD_OPTS + sleep 1 + $HA_BIN/stonithd -s 2>/dev/null + else + STOP_STONITHD= + fi +} +stop_stonithd() { + if [ "$STOP_STONITHD" ]; then + echo "stopping stonithd" >&3 + $HA_BIN/stonithd -k >/dev/null 2>&1 + fi +} +start_lrmd() { + echo "starting lrmd" >&3 + $HA_BIN/lrmd -s 2>/dev/null + if [ $? -eq 3 ]; then + #strace -o /tmp/lrmd.trc $HA_BIN/lrmd $LRMD_OPTS >$LRMD_OUTF 2>&1 & + $HA_BIN/lrmd $LRMD_OPTS >$LRMD_OUTF 2>&1 & + sleep 1 + $HA_BIN/lrmd -s 2>/dev/null + else + echo "lrmd already running; can't proceed" >&3 + return 2 + fi +} +stop_lrmd() { + echo "stopping lrmd" >&3 + $HA_BIN/lrmd -k +} +cp_ra() { + cp -p lrmregtest $OCF_RA + chmod +x $OCF_RA + cp -p lrmregtest-lsb $LSB_RA + chmod +x $LSB_RA +} +rm_ra() { + rm -f $OCF_RA $LSB_RA +} + +cp_ra +start_lrmd || exit $? +# start_stonithd || exit $? +trap "stop_lrmd; stop_stonithd; rm_ra" EXIT + +setenvironment() { + filterf=$TESTDIR/$testcase.filter + exclf=$TESTDIR/$testcase.excl + log_filter=$TESTDIR/$testcase.log_filter + expf=$TESTDIR/$testcase.exp + outf=$OUTDIR/$testcase.out + difff=$OUTDIR/$testcase.diff +} + +filter_output() { + { [ -x $common_filter ] && $common_filter || cat;} | + { [ -f $common_exclf ] && egrep -vf $common_exclf || cat;} | + { [ -x $filterf ] && $filterf || cat;} | + { [ -f $exclf ] && egrep -vf $exclf || cat;} +} + +dumpcase() { + cat<&3 + logmsg "BEGIN testcase $testcase" + ./evaltest.sh < $TESTDIR/$testcase > $outf 2>&1 + + filter_output < $outf | + if [ "$prepare" ]; then + echo " saving to expect file" >&3 + cat > $expf + else + echo -n " checking..." >&3 + diff $DIFF_OPTS $expf - > $difff + if [ $? -ne 0 ]; then + echo " FAIL" >&3 + dumpcase + return 1 + else + echo " PASS" >&3 + rm -f $outf $difff + fi + fi + sed -n "/BEGIN testcase $testcase/,\$p" $LRMD_LOGF | + { [ -x $log_filter ] && $log_filter || cat;} | + egrep '(CRIT|ERROR):' + logmsg "END testcase $testcase" +} + +[ "$1" = prepare ] && { prepare=1; shift 1;} +[ $# -eq 0 ] && set "set:$DFLT_TESTSET" + +for a; do + if [ "$a" -a -f "$TESTDIR/$a" ]; then + testcase=$a + runtestcase + else + echo "$a" | grep -q "^set:" && + TESTSET=$TESTDIR/`echo $a | sed 's/set://'` + while read testcase; do + runtestcase + done < $TESTSET + fi +done + +if egrep -wv '(BEGIN|END) testcase' $OUTF >/dev/null +then + echo "seems like some tests failed or else something not expected" + echo "check $OUTF and diff files in $OUTDIR" + echo "in case you wonder what lrmd was doing, read $LRMD_LOGF and $LRMD_DEBUGF" + exit 1 +else + rm -f $OUTF $LRMD_OUTF +fi >&3 diff --git a/lrm/test/testcases/BSC b/lrm/test/testcases/BSC new file mode 100644 index 0000000..157fb6c --- /dev/null +++ b/lrm/test/testcases/BSC @@ -0,0 +1,4 @@ +rscmgmt +metadata +rscexec +stonith diff --git a/lrm/test/testcases/Makefile.am b/lrm/test/testcases/Makefile.am new file mode 100644 index 0000000..49728d9 --- /dev/null +++ b/lrm/test/testcases/Makefile.am @@ -0,0 +1,27 @@ +# +# Author: Sun Jiang Dong +# Copyright (c) 2004 International Business Machines +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +MAINTAINERCLEANFILES = Makefile.in + +testcasesdir = $(datadir)/$(PACKAGE_NAME)/lrmtest/testcases +testcases_SCRIPTS = common.filter ra-list.sh rscmgmt.log_filter xmllint.sh +testcases_DATA = BSC basicset metadata metadata.exp rscexec \ + rscexec.exp rscmgmt rscmgmt.exp \ + stonith stonith.exp +# shouldn't need this next line... +EXTRA_DIST = $(testcases_SCRIPTS) $(testcases_DATA) diff --git a/lrm/test/testcases/basicset b/lrm/test/testcases/basicset new file mode 100644 index 0000000..62b9c04 --- /dev/null +++ b/lrm/test/testcases/basicset @@ -0,0 +1,6 @@ +rscmgmt +metadata +rscexec +stonith +serialize +flood diff --git a/lrm/test/testcases/common.filter b/lrm/test/testcases/common.filter new file mode 100755 index 0000000..f95e9d8 --- /dev/null +++ b/lrm/test/testcases/common.filter @@ -0,0 +1,27 @@ +#!/bin/sh + +sed ' +/^lrmadmin/s/([0-9][0-9]*)/()/ +/^lrmadmin/s/^lrmadmin[^:]*: [^ ]* // +s/call_id=[0-9][0-9]*/call_id=(removed)/ +/run at:/d +/last rc change at:/d +/queue time:/d +' | +awk ' +/Waiting for lrmd to callback.../ { n=1; next; } +n==1 && /----------------operation--------------/ { n++; next; } +n==2 && /type:/ { op=$0; sub("type:","",op); next } +n==2 && /operation status:/ { desc=$0; sub("operation status:","",desc); next } +n==2 && /op_status:/ { stat=$0; sub("op_status: *","",stat); next } +n==2 && /return code:/ { rc=$0; sub("return code: *","",rc); next } +n==2 && /output data:/ { n++; next; } +n==3 && /---------------------------------------/ { + printf("> %s %s (status=%s,rc=%s): %s\n",op,desc,stat,rc,substr(output,2)); + n=0; + output=""; + next; +} +n==3 && $1!="" { output=output"/"$0; next; } +{ print } +' diff --git a/lrm/test/testcases/flood b/lrm/test/testcases/flood new file mode 100644 index 0000000..de6d742 --- /dev/null +++ b/lrm/test/testcases/flood @@ -0,0 +1,19 @@ +# 30 secs should be enough even on slow machines +list +%setenv dflt_timeout=30000 +# add 64 resources +%repeat 64 +add rsc=r%i args="delay=0" +# start all in background +%bgrepeat 64 +exec rsc=r%i operation=start +%sleep 1 +# and run a monitor on all in background +%bgrepeat 64 +exec rsc=r%i operation=monitor +%sleep 1 +# finally, stop all +%repeat 64 +exec rsc=r%i operation=stop +%repeat 64 +del rsc=r%i diff --git a/lrm/test/testcases/flood.exp b/lrm/test/testcases/flood.exp new file mode 100644 index 0000000..cf8a2bb --- /dev/null +++ b/lrm/test/testcases/flood.exp @@ -0,0 +1,1354 @@ +.TRY List resources +Currently no resources are managed by LRM. +.SETENV dflt_timeout=30000 +.REPEAT 64 +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r2 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r3 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r4 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r5 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r6 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r7 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r8 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r9 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r10 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r11 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r12 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r13 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r14 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r15 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r16 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r17 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r18 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r19 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r20 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r21 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r22 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r23 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r24 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r25 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r26 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r27 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r28 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r29 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r30 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r31 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r32 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r33 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r34 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r35 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r36 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r37 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r38 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r39 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r40 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r41 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r42 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r43 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r44 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r45 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r46 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r47 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r48 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r49 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r50 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r51 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r52 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r53 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r54 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r55 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r56 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r57 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r58 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r59 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r60 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r61 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r62 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r63 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY Add resource r64 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.BGREPEAT 64 +.BG test line 9/job 1 runs in background +.BG test line 9/job 2 runs in background +.BG test line 9/job 3 runs in background +.BG test line 9/job 4 runs in background +.BG test line 9/job 5 runs in background +.BG test line 9/job 6 runs in background +.BG test line 9/job 7 runs in background +.BG test line 9/job 8 runs in background +.BG test line 9/job 9 runs in background +.BG test line 9/job 10 runs in background +.BG test line 9/job 11 runs in background +.BG test line 9/job 12 runs in background +.BG test line 9/job 13 runs in background +.BG test line 9/job 14 runs in background +.BG test line 9/job 15 runs in background +.BG test line 9/job 16 runs in background +.BG test line 9/job 17 runs in background +.BG test line 9/job 18 runs in background +.BG test line 9/job 19 runs in background +.BG test line 9/job 20 runs in background +.BG test line 9/job 21 runs in background +.BG test line 9/job 22 runs in background +.BG test line 9/job 23 runs in background +.BG test line 9/job 24 runs in background +.BG test line 9/job 25 runs in background +.BG test line 9/job 26 runs in background +.BG test line 9/job 27 runs in background +.BG test line 9/job 28 runs in background +.BG test line 9/job 29 runs in background +.BG test line 9/job 30 runs in background +.BG test line 9/job 31 runs in background +.BG test line 9/job 32 runs in background +.BG test line 9/job 33 runs in background +.BG test line 9/job 34 runs in background +.BG test line 9/job 35 runs in background +.BG test line 9/job 36 runs in background +.BG test line 9/job 37 runs in background +.BG test line 9/job 38 runs in background +.BG test line 9/job 39 runs in background +.BG test line 9/job 40 runs in background +.BG test line 9/job 41 runs in background +.BG test line 9/job 42 runs in background +.BG test line 9/job 43 runs in background +.BG test line 9/job 44 runs in background +.BG test line 9/job 45 runs in background +.BG test line 9/job 46 runs in background +.BG test line 9/job 47 runs in background +.BG test line 9/job 48 runs in background +.BG test line 9/job 49 runs in background +.BG test line 9/job 50 runs in background +.BG test line 9/job 51 runs in background +.BG test line 9/job 52 runs in background +.BG test line 9/job 53 runs in background +.BG test line 9/job 54 runs in background +.BG test line 9/job 55 runs in background +.BG test line 9/job 56 runs in background +.BG test line 9/job 57 runs in background +.BG test line 9/job 58 runs in background +.BG test line 9/job 59 runs in background +.BG test line 9/job 60 runs in background +.BG test line 9/job 61 runs in background +.BG test line 9/job 62 runs in background +.BG test line 9/job 63 runs in background +.BG test line 9/job 64 runs in background +.SLEEP 1 +.BGREPEAT 64 +.BG test line 13/job 1 runs in background +.BG test line 13/job 2 runs in background +.BG test line 13/job 3 runs in background +.BG test line 13/job 4 runs in background +.BG test line 13/job 5 runs in background +.BG test line 13/job 6 runs in background +.BG test line 13/job 7 runs in background +.BG test line 13/job 8 runs in background +.BG test line 13/job 9 runs in background +.BG test line 13/job 10 runs in background +.BG test line 13/job 11 runs in background +.BG test line 13/job 12 runs in background +.BG test line 13/job 13 runs in background +.BG test line 13/job 14 runs in background +.BG test line 13/job 15 runs in background +.BG test line 13/job 16 runs in background +.BG test line 13/job 17 runs in background +.BG test line 13/job 18 runs in background +.BG test line 13/job 19 runs in background +.BG test line 13/job 20 runs in background +.BG test line 13/job 21 runs in background +.BG test line 13/job 22 runs in background +.BG test line 13/job 23 runs in background +.BG test line 13/job 24 runs in background +.BG test line 13/job 25 runs in background +.BG test line 13/job 26 runs in background +.BG test line 13/job 27 runs in background +.BG test line 13/job 28 runs in background +.BG test line 13/job 29 runs in background +.BG test line 13/job 30 runs in background +.BG test line 13/job 31 runs in background +.BG test line 13/job 32 runs in background +.BG test line 13/job 33 runs in background +.BG test line 13/job 34 runs in background +.BG test line 13/job 35 runs in background +.BG test line 13/job 36 runs in background +.BG test line 13/job 37 runs in background +.BG test line 13/job 38 runs in background +.BG test line 13/job 39 runs in background +.BG test line 13/job 40 runs in background +.BG test line 13/job 41 runs in background +.BG test line 13/job 42 runs in background +.BG test line 13/job 43 runs in background +.BG test line 13/job 44 runs in background +.BG test line 13/job 45 runs in background +.BG test line 13/job 46 runs in background +.BG test line 13/job 47 runs in background +.BG test line 13/job 48 runs in background +.BG test line 13/job 49 runs in background +.BG test line 13/job 50 runs in background +.BG test line 13/job 51 runs in background +.BG test line 13/job 52 runs in background +.BG test line 13/job 53 runs in background +.BG test line 13/job 54 runs in background +.BG test line 13/job 55 runs in background +.BG test line 13/job 56 runs in background +.BG test line 13/job 57 runs in background +.BG test line 13/job 58 runs in background +.BG test line 13/job 59 runs in background +.BG test line 13/job 60 runs in background +.BG test line 13/job 61 runs in background +.BG test line 13/job 62 runs in background +.BG test line 13/job 63 runs in background +.BG test line 13/job 64 runs in background +.SLEEP 1 +.REPEAT 64 +.TRY Exec r1 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r2 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r3 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r4 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r5 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r6 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r7 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r8 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r9 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r10 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r11 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r12 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r13 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r14 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r15 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r16 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r17 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r18 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r19 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r20 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r21 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r22 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r23 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r24 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r25 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r26 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r27 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r28 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r29 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r30 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r31 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r32 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r33 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r34 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r35 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r36 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r37 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r38 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r39 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r40 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r41 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r42 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r43 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r44 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r45 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r46 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r47 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r48 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r49 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r50 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r51 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r52 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r53 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r54 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r55 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r56 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r57 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r58 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r59 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r60 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r61 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r62 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r63 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec r64 op=stop timeout=30000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.REPEAT 64 +.TRY Delete resource r1 +Succeeded in deleting this resource. +.TRY Delete resource r2 +Succeeded in deleting this resource. +.TRY Delete resource r3 +Succeeded in deleting this resource. +.TRY Delete resource r4 +Succeeded in deleting this resource. +.TRY Delete resource r5 +Succeeded in deleting this resource. +.TRY Delete resource r6 +Succeeded in deleting this resource. +.TRY Delete resource r7 +Succeeded in deleting this resource. +.TRY Delete resource r8 +Succeeded in deleting this resource. +.TRY Delete resource r9 +Succeeded in deleting this resource. +.TRY Delete resource r10 +Succeeded in deleting this resource. +.TRY Delete resource r11 +Succeeded in deleting this resource. +.TRY Delete resource r12 +Succeeded in deleting this resource. +.TRY Delete resource r13 +Succeeded in deleting this resource. +.TRY Delete resource r14 +Succeeded in deleting this resource. +.TRY Delete resource r15 +Succeeded in deleting this resource. +.TRY Delete resource r16 +Succeeded in deleting this resource. +.TRY Delete resource r17 +Succeeded in deleting this resource. +.TRY Delete resource r18 +Succeeded in deleting this resource. +.TRY Delete resource r19 +Succeeded in deleting this resource. +.TRY Delete resource r20 +Succeeded in deleting this resource. +.TRY Delete resource r21 +Succeeded in deleting this resource. +.TRY Delete resource r22 +Succeeded in deleting this resource. +.TRY Delete resource r23 +Succeeded in deleting this resource. +.TRY Delete resource r24 +Succeeded in deleting this resource. +.TRY Delete resource r25 +Succeeded in deleting this resource. +.TRY Delete resource r26 +Succeeded in deleting this resource. +.TRY Delete resource r27 +Succeeded in deleting this resource. +.TRY Delete resource r28 +Succeeded in deleting this resource. +.TRY Delete resource r29 +Succeeded in deleting this resource. +.TRY Delete resource r30 +Succeeded in deleting this resource. +.TRY Delete resource r31 +Succeeded in deleting this resource. +.TRY Delete resource r32 +Succeeded in deleting this resource. +.TRY Delete resource r33 +Succeeded in deleting this resource. +.TRY Delete resource r34 +Succeeded in deleting this resource. +.TRY Delete resource r35 +Succeeded in deleting this resource. +.TRY Delete resource r36 +Succeeded in deleting this resource. +.TRY Delete resource r37 +Succeeded in deleting this resource. +.TRY Delete resource r38 +Succeeded in deleting this resource. +.TRY Delete resource r39 +Succeeded in deleting this resource. +.TRY Delete resource r40 +Succeeded in deleting this resource. +.TRY Delete resource r41 +Succeeded in deleting this resource. +.TRY Delete resource r42 +Succeeded in deleting this resource. +.TRY Delete resource r43 +Succeeded in deleting this resource. +.TRY Delete resource r44 +Succeeded in deleting this resource. +.TRY Delete resource r45 +Succeeded in deleting this resource. +.TRY Delete resource r46 +Succeeded in deleting this resource. +.TRY Delete resource r47 +Succeeded in deleting this resource. +.TRY Delete resource r48 +Succeeded in deleting this resource. +.TRY Delete resource r49 +Succeeded in deleting this resource. +.TRY Delete resource r50 +Succeeded in deleting this resource. +.TRY Delete resource r51 +Succeeded in deleting this resource. +.TRY Delete resource r52 +Succeeded in deleting this resource. +.TRY Delete resource r53 +Succeeded in deleting this resource. +.TRY Delete resource r54 +Succeeded in deleting this resource. +.TRY Delete resource r55 +Succeeded in deleting this resource. +.TRY Delete resource r56 +Succeeded in deleting this resource. +.TRY Delete resource r57 +Succeeded in deleting this resource. +.TRY Delete resource r58 +Succeeded in deleting this resource. +.TRY Delete resource r59 +Succeeded in deleting this resource. +.TRY Delete resource r60 +Succeeded in deleting this resource. +.TRY Delete resource r61 +Succeeded in deleting this resource. +.TRY Delete resource r62 +Succeeded in deleting this resource. +.TRY Delete resource r63 +Succeeded in deleting this resource. +.TRY Delete resource r64 +Succeeded in deleting this resource. +.BG test line 9/job 1 finished (exit code: 0): +==========test:9:1 start output========== +.TRY Exec r1 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:1 end output========== +.BG test line 9/job 2 finished (exit code: 0): +==========test:9:2 start output========== +.TRY Exec r2 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:2 end output========== +.BG test line 9/job 3 finished (exit code: 0): +==========test:9:3 start output========== +.TRY Exec r3 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:3 end output========== +.BG test line 9/job 4 finished (exit code: 0): +==========test:9:4 start output========== +.TRY Exec r4 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:4 end output========== +.BG test line 9/job 5 finished (exit code: 0): +==========test:9:5 start output========== +.TRY Exec r5 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:5 end output========== +.BG test line 9/job 6 finished (exit code: 0): +==========test:9:6 start output========== +.TRY Exec r6 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:6 end output========== +.BG test line 9/job 7 finished (exit code: 0): +==========test:9:7 start output========== +.TRY Exec r7 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:7 end output========== +.BG test line 9/job 8 finished (exit code: 0): +==========test:9:8 start output========== +.TRY Exec r8 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:8 end output========== +.BG test line 9/job 9 finished (exit code: 0): +==========test:9:9 start output========== +.TRY Exec r9 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:9 end output========== +.BG test line 9/job 10 finished (exit code: 0): +==========test:9:10 start output========== +.TRY Exec r10 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:10 end output========== +.BG test line 9/job 11 finished (exit code: 0): +==========test:9:11 start output========== +.TRY Exec r11 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:11 end output========== +.BG test line 9/job 12 finished (exit code: 0): +==========test:9:12 start output========== +.TRY Exec r12 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:12 end output========== +.BG test line 9/job 13 finished (exit code: 0): +==========test:9:13 start output========== +.TRY Exec r13 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:13 end output========== +.BG test line 9/job 14 finished (exit code: 0): +==========test:9:14 start output========== +.TRY Exec r14 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:14 end output========== +.BG test line 9/job 15 finished (exit code: 0): +==========test:9:15 start output========== +.TRY Exec r15 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:15 end output========== +.BG test line 9/job 16 finished (exit code: 0): +==========test:9:16 start output========== +.TRY Exec r16 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:16 end output========== +.BG test line 9/job 17 finished (exit code: 0): +==========test:9:17 start output========== +.TRY Exec r17 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:17 end output========== +.BG test line 9/job 18 finished (exit code: 0): +==========test:9:18 start output========== +.TRY Exec r18 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:18 end output========== +.BG test line 9/job 19 finished (exit code: 0): +==========test:9:19 start output========== +.TRY Exec r19 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:19 end output========== +.BG test line 9/job 20 finished (exit code: 0): +==========test:9:20 start output========== +.TRY Exec r20 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:20 end output========== +.BG test line 9/job 21 finished (exit code: 0): +==========test:9:21 start output========== +.TRY Exec r21 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:21 end output========== +.BG test line 9/job 22 finished (exit code: 0): +==========test:9:22 start output========== +.TRY Exec r22 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:22 end output========== +.BG test line 9/job 23 finished (exit code: 0): +==========test:9:23 start output========== +.TRY Exec r23 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:23 end output========== +.BG test line 9/job 24 finished (exit code: 0): +==========test:9:24 start output========== +.TRY Exec r24 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:24 end output========== +.BG test line 9/job 25 finished (exit code: 0): +==========test:9:25 start output========== +.TRY Exec r25 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:25 end output========== +.BG test line 9/job 26 finished (exit code: 0): +==========test:9:26 start output========== +.TRY Exec r26 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:26 end output========== +.BG test line 9/job 27 finished (exit code: 0): +==========test:9:27 start output========== +.TRY Exec r27 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:27 end output========== +.BG test line 9/job 28 finished (exit code: 0): +==========test:9:28 start output========== +.TRY Exec r28 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:28 end output========== +.BG test line 9/job 29 finished (exit code: 0): +==========test:9:29 start output========== +.TRY Exec r29 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:29 end output========== +.BG test line 9/job 30 finished (exit code: 0): +==========test:9:30 start output========== +.TRY Exec r30 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:30 end output========== +.BG test line 9/job 31 finished (exit code: 0): +==========test:9:31 start output========== +.TRY Exec r31 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:31 end output========== +.BG test line 9/job 32 finished (exit code: 0): +==========test:9:32 start output========== +.TRY Exec r32 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:32 end output========== +.BG test line 9/job 33 finished (exit code: 0): +==========test:9:33 start output========== +.TRY Exec r33 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:33 end output========== +.BG test line 9/job 34 finished (exit code: 0): +==========test:9:34 start output========== +.TRY Exec r34 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:34 end output========== +.BG test line 9/job 35 finished (exit code: 0): +==========test:9:35 start output========== +.TRY Exec r35 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:35 end output========== +.BG test line 9/job 36 finished (exit code: 0): +==========test:9:36 start output========== +.TRY Exec r36 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:36 end output========== +.BG test line 9/job 37 finished (exit code: 0): +==========test:9:37 start output========== +.TRY Exec r37 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:37 end output========== +.BG test line 9/job 38 finished (exit code: 0): +==========test:9:38 start output========== +.TRY Exec r38 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:38 end output========== +.BG test line 9/job 39 finished (exit code: 0): +==========test:9:39 start output========== +.TRY Exec r39 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:39 end output========== +.BG test line 9/job 40 finished (exit code: 0): +==========test:9:40 start output========== +.TRY Exec r40 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:40 end output========== +.BG test line 9/job 41 finished (exit code: 0): +==========test:9:41 start output========== +.TRY Exec r41 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:41 end output========== +.BG test line 9/job 42 finished (exit code: 0): +==========test:9:42 start output========== +.TRY Exec r42 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:42 end output========== +.BG test line 9/job 43 finished (exit code: 0): +==========test:9:43 start output========== +.TRY Exec r43 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:43 end output========== +.BG test line 9/job 44 finished (exit code: 0): +==========test:9:44 start output========== +.TRY Exec r44 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:44 end output========== +.BG test line 9/job 45 finished (exit code: 0): +==========test:9:45 start output========== +.TRY Exec r45 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:45 end output========== +.BG test line 9/job 46 finished (exit code: 0): +==========test:9:46 start output========== +.TRY Exec r46 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:46 end output========== +.BG test line 9/job 47 finished (exit code: 0): +==========test:9:47 start output========== +.TRY Exec r47 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:47 end output========== +.BG test line 9/job 48 finished (exit code: 0): +==========test:9:48 start output========== +.TRY Exec r48 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:48 end output========== +.BG test line 9/job 49 finished (exit code: 0): +==========test:9:49 start output========== +.TRY Exec r49 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:49 end output========== +.BG test line 9/job 50 finished (exit code: 0): +==========test:9:50 start output========== +.TRY Exec r50 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:50 end output========== +.BG test line 9/job 51 finished (exit code: 0): +==========test:9:51 start output========== +.TRY Exec r51 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:51 end output========== +.BG test line 9/job 52 finished (exit code: 0): +==========test:9:52 start output========== +.TRY Exec r52 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:52 end output========== +.BG test line 9/job 53 finished (exit code: 0): +==========test:9:53 start output========== +.TRY Exec r53 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:53 end output========== +.BG test line 9/job 54 finished (exit code: 0): +==========test:9:54 start output========== +.TRY Exec r54 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:54 end output========== +.BG test line 9/job 55 finished (exit code: 0): +==========test:9:55 start output========== +.TRY Exec r55 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:55 end output========== +.BG test line 9/job 56 finished (exit code: 0): +==========test:9:56 start output========== +.TRY Exec r56 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:56 end output========== +.BG test line 9/job 57 finished (exit code: 0): +==========test:9:57 start output========== +.TRY Exec r57 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:57 end output========== +.BG test line 9/job 58 finished (exit code: 0): +==========test:9:58 start output========== +.TRY Exec r58 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:58 end output========== +.BG test line 9/job 59 finished (exit code: 0): +==========test:9:59 start output========== +.TRY Exec r59 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:59 end output========== +.BG test line 9/job 60 finished (exit code: 0): +==========test:9:60 start output========== +.TRY Exec r60 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:60 end output========== +.BG test line 9/job 61 finished (exit code: 0): +==========test:9:61 start output========== +.TRY Exec r61 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:61 end output========== +.BG test line 9/job 62 finished (exit code: 0): +==========test:9:62 start output========== +.TRY Exec r62 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:62 end output========== +.BG test line 9/job 63 finished (exit code: 0): +==========test:9:63 start output========== +.TRY Exec r63 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:63 end output========== +.BG test line 9/job 64 finished (exit code: 0): +==========test:9:64 start output========== +.TRY Exec r64 op=start timeout=30000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:9:64 end output========== +.BG test line 13/job 1 finished (exit code: 0): +==========test:13:1 start output========== +.TRY Exec r1 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:1 end output========== +.BG test line 13/job 2 finished (exit code: 0): +==========test:13:2 start output========== +.TRY Exec r2 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:2 end output========== +.BG test line 13/job 3 finished (exit code: 0): +==========test:13:3 start output========== +.TRY Exec r3 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:3 end output========== +.BG test line 13/job 4 finished (exit code: 0): +==========test:13:4 start output========== +.TRY Exec r4 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:4 end output========== +.BG test line 13/job 5 finished (exit code: 0): +==========test:13:5 start output========== +.TRY Exec r5 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:5 end output========== +.BG test line 13/job 6 finished (exit code: 0): +==========test:13:6 start output========== +.TRY Exec r6 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:6 end output========== +.BG test line 13/job 7 finished (exit code: 0): +==========test:13:7 start output========== +.TRY Exec r7 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:7 end output========== +.BG test line 13/job 8 finished (exit code: 0): +==========test:13:8 start output========== +.TRY Exec r8 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:8 end output========== +.BG test line 13/job 9 finished (exit code: 0): +==========test:13:9 start output========== +.TRY Exec r9 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:9 end output========== +.BG test line 13/job 10 finished (exit code: 0): +==========test:13:10 start output========== +.TRY Exec r10 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:10 end output========== +.BG test line 13/job 11 finished (exit code: 0): +==========test:13:11 start output========== +.TRY Exec r11 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:11 end output========== +.BG test line 13/job 12 finished (exit code: 0): +==========test:13:12 start output========== +.TRY Exec r12 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:12 end output========== +.BG test line 13/job 13 finished (exit code: 0): +==========test:13:13 start output========== +.TRY Exec r13 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:13 end output========== +.BG test line 13/job 14 finished (exit code: 0): +==========test:13:14 start output========== +.TRY Exec r14 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:14 end output========== +.BG test line 13/job 15 finished (exit code: 0): +==========test:13:15 start output========== +.TRY Exec r15 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:15 end output========== +.BG test line 13/job 16 finished (exit code: 0): +==========test:13:16 start output========== +.TRY Exec r16 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:16 end output========== +.BG test line 13/job 17 finished (exit code: 0): +==========test:13:17 start output========== +.TRY Exec r17 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:17 end output========== +.BG test line 13/job 18 finished (exit code: 0): +==========test:13:18 start output========== +.TRY Exec r18 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:18 end output========== +.BG test line 13/job 19 finished (exit code: 0): +==========test:13:19 start output========== +.TRY Exec r19 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:19 end output========== +.BG test line 13/job 20 finished (exit code: 0): +==========test:13:20 start output========== +.TRY Exec r20 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:20 end output========== +.BG test line 13/job 21 finished (exit code: 0): +==========test:13:21 start output========== +.TRY Exec r21 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:21 end output========== +.BG test line 13/job 22 finished (exit code: 0): +==========test:13:22 start output========== +.TRY Exec r22 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:22 end output========== +.BG test line 13/job 23 finished (exit code: 0): +==========test:13:23 start output========== +.TRY Exec r23 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:23 end output========== +.BG test line 13/job 24 finished (exit code: 0): +==========test:13:24 start output========== +.TRY Exec r24 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:24 end output========== +.BG test line 13/job 25 finished (exit code: 0): +==========test:13:25 start output========== +.TRY Exec r25 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:25 end output========== +.BG test line 13/job 26 finished (exit code: 0): +==========test:13:26 start output========== +.TRY Exec r26 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:26 end output========== +.BG test line 13/job 27 finished (exit code: 0): +==========test:13:27 start output========== +.TRY Exec r27 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:27 end output========== +.BG test line 13/job 28 finished (exit code: 0): +==========test:13:28 start output========== +.TRY Exec r28 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:28 end output========== +.BG test line 13/job 29 finished (exit code: 0): +==========test:13:29 start output========== +.TRY Exec r29 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:29 end output========== +.BG test line 13/job 30 finished (exit code: 0): +==========test:13:30 start output========== +.TRY Exec r30 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:30 end output========== +.BG test line 13/job 31 finished (exit code: 0): +==========test:13:31 start output========== +.TRY Exec r31 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:31 end output========== +.BG test line 13/job 32 finished (exit code: 0): +==========test:13:32 start output========== +.TRY Exec r32 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:32 end output========== +.BG test line 13/job 33 finished (exit code: 0): +==========test:13:33 start output========== +.TRY Exec r33 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:33 end output========== +.BG test line 13/job 34 finished (exit code: 0): +==========test:13:34 start output========== +.TRY Exec r34 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:34 end output========== +.BG test line 13/job 35 finished (exit code: 0): +==========test:13:35 start output========== +.TRY Exec r35 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:35 end output========== +.BG test line 13/job 36 finished (exit code: 0): +==========test:13:36 start output========== +.TRY Exec r36 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:36 end output========== +.BG test line 13/job 37 finished (exit code: 0): +==========test:13:37 start output========== +.TRY Exec r37 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:37 end output========== +.BG test line 13/job 38 finished (exit code: 0): +==========test:13:38 start output========== +.TRY Exec r38 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:38 end output========== +.BG test line 13/job 39 finished (exit code: 0): +==========test:13:39 start output========== +.TRY Exec r39 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:39 end output========== +.BG test line 13/job 40 finished (exit code: 0): +==========test:13:40 start output========== +.TRY Exec r40 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:40 end output========== +.BG test line 13/job 41 finished (exit code: 0): +==========test:13:41 start output========== +.TRY Exec r41 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:41 end output========== +.BG test line 13/job 42 finished (exit code: 0): +==========test:13:42 start output========== +.TRY Exec r42 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:42 end output========== +.BG test line 13/job 43 finished (exit code: 0): +==========test:13:43 start output========== +.TRY Exec r43 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:43 end output========== +.BG test line 13/job 44 finished (exit code: 0): +==========test:13:44 start output========== +.TRY Exec r44 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:44 end output========== +.BG test line 13/job 45 finished (exit code: 0): +==========test:13:45 start output========== +.TRY Exec r45 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:45 end output========== +.BG test line 13/job 46 finished (exit code: 0): +==========test:13:46 start output========== +.TRY Exec r46 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:46 end output========== +.BG test line 13/job 47 finished (exit code: 0): +==========test:13:47 start output========== +.TRY Exec r47 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:47 end output========== +.BG test line 13/job 48 finished (exit code: 0): +==========test:13:48 start output========== +.TRY Exec r48 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:48 end output========== +.BG test line 13/job 49 finished (exit code: 0): +==========test:13:49 start output========== +.TRY Exec r49 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:49 end output========== +.BG test line 13/job 50 finished (exit code: 0): +==========test:13:50 start output========== +.TRY Exec r50 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:50 end output========== +.BG test line 13/job 51 finished (exit code: 0): +==========test:13:51 start output========== +.TRY Exec r51 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:51 end output========== +.BG test line 13/job 52 finished (exit code: 0): +==========test:13:52 start output========== +.TRY Exec r52 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:52 end output========== +.BG test line 13/job 53 finished (exit code: 0): +==========test:13:53 start output========== +.TRY Exec r53 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:53 end output========== +.BG test line 13/job 54 finished (exit code: 0): +==========test:13:54 start output========== +.TRY Exec r54 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:54 end output========== +.BG test line 13/job 55 finished (exit code: 0): +==========test:13:55 start output========== +.TRY Exec r55 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:55 end output========== +.BG test line 13/job 56 finished (exit code: 0): +==========test:13:56 start output========== +.TRY Exec r56 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:56 end output========== +.BG test line 13/job 57 finished (exit code: 0): +==========test:13:57 start output========== +.TRY Exec r57 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:57 end output========== +.BG test line 13/job 58 finished (exit code: 0): +==========test:13:58 start output========== +.TRY Exec r58 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:58 end output========== +.BG test line 13/job 59 finished (exit code: 0): +==========test:13:59 start output========== +.TRY Exec r59 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:59 end output========== +.BG test line 13/job 60 finished (exit code: 0): +==========test:13:60 start output========== +.TRY Exec r60 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:60 end output========== +.BG test line 13/job 61 finished (exit code: 0): +==========test:13:61 start output========== +.TRY Exec r61 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:61 end output========== +.BG test line 13/job 62 finished (exit code: 0): +==========test:13:62 start output========== +.TRY Exec r62 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:62 end output========== +.BG test line 13/job 63 finished (exit code: 0): +==========test:13:63 start output========== +.TRY Exec r63 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:63 end output========== +.BG test line 13/job 64 finished (exit code: 0): +==========test:13:64 start output========== +.TRY Exec r64 op=monitor timeout=30000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:13:64 end output========== diff --git a/lrm/test/testcases/metadata b/lrm/test/testcases/metadata new file mode 100644 index 0000000..d155757 --- /dev/null +++ b/lrm/test/testcases/metadata @@ -0,0 +1,29 @@ +# list various meta-data +%setenv LANG=POSIX +%extcheck sort +classes +%extcheck ra-list.sh +types class=ocf +%extcheck ra-list.sh +types class=lsb +%extcheck ra-list.sh +types class=heartbeat +#%extcheck ra-list.sh +#types class=stonith +%extcheck xmllint.sh many +classmeta class=ocf +%extcheck xmllint.sh many +classmeta class=lsb +%extcheck xmllint.sh many +classmeta class=heartbeat +#%extcheck xmllint.sh many +#classmeta class=stonith +%extcheck xmllint.sh +meta class=ocf type=Dummy +%extcheck xmllint.sh +meta class=lsb type=lrmregtest +%extcheck xmllint.sh +meta class=heartbeat type=Dummy +#%extcheck xmllint.sh +#meta class=stonith type=ssh +provider class=ocf type=IPaddr diff --git a/lrm/test/testcases/metadata.exp b/lrm/test/testcases/metadata.exp new file mode 100644 index 0000000..158bad2 --- /dev/null +++ b/lrm/test/testcases/metadata.exp @@ -0,0 +1,31 @@ +.SETENV LANG=POSIX +.EXTCHECK sort +.TRY List classes +There are 4 RA classes supported: +heartbeat +lsb +ocf +stonith +.EXTCHECK ra-list.sh +.TRY List types class=ocf +Cool. RA list passed. +.EXTCHECK ra-list.sh +.TRY List types class=lsb +Cool. RA list passed. +.EXTCHECK ra-list.sh +.TRY List types class=heartbeat +Cool. RA list passed. +.EXTCHECK xmllint.sh many +.TRY Meta-data class=ocf +.EXTCHECK xmllint.sh many +.TRY Meta-data class=lsb +.EXTCHECK xmllint.sh many +.TRY Meta-data class=heartbeat +.EXTCHECK xmllint.sh +.TRY Show meta-data class=ocf type=Dummy provider=heartbeat +.EXTCHECK xmllint.sh +.TRY Show meta-data class=lsb type=lrmregtest provider=heartbeat +.EXTCHECK xmllint.sh +.TRY Show meta-data class=heartbeat type=Dummy provider=heartbeat +.TRY Show provider class=ocf type=IPaddr +heartbeat diff --git a/lrm/test/testcases/ra-list.sh b/lrm/test/testcases/ra-list.sh new file mode 100755 index 0000000..38fb67b --- /dev/null +++ b/lrm/test/testcases/ra-list.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +awk ' +NR==1 {num=$3;next} +{in_num++} +END{ + if( num!=in_num ) + print "ERROR: A mismatch in number of reported RAs!"; + else + print "Cool. RA list passed."; +} +' diff --git a/lrm/test/testcases/rscexec b/lrm/test/testcases/rscexec new file mode 100644 index 0000000..e118ae1 --- /dev/null +++ b/lrm/test/testcases/rscexec @@ -0,0 +1,48 @@ +list +# ocf +%setenv dflt_rsc=rscexec_rsc_r1 +add rsc=rscexec_rsc_r1 args="delay=0" +list +exec operation=start +state +exec operation=monitor +exec operation=start +exec operation=monitor +exec operation=stop +state +exec operation=monitor +exec operation=stop +exec operation=monitor +exec operation=meta-data +del +# lsb +%setenv dflt_class=lsb dftl_rsc=rscexec_rsc_r1-lsb +add +exec operation=start +state +exec operation=monitor +exec operation=start +exec operation=monitor +exec operation=stop +state +exec operation=monitor +exec operation=stop +exec operation=monitor +exec operation=meta-data +del +%stop +# stonith +%setenv dflt_class=stonith dftl_rsc=rscexec_rsc_r1-stonith +add type=null args="hostlist=node1" +exec operation=start +state +exec operation=monitor +exec operation=start +exec operation=monitor +exec operation=stop +state +exec operation=monitor +exec operation=stop +exec operation=monitor +exec operation=meta-data +del diff --git a/lrm/test/testcases/rscexec.exp b/lrm/test/testcases/rscexec.exp new file mode 100644 index 0000000..71bdc2e --- /dev/null +++ b/lrm/test/testcases/rscexec.exp @@ -0,0 +1,117 @@ +.TRY List resources +Currently no resources are managed by LRM. +.SETENV dflt_rsc=rscexec_rsc_r1 +.TRY Add resource rscexec_rsc_r1 class=ocf type=lrmregtest provider=heartbeat args=delay=0 +Succeeded in adding this resource. +.TRY List resources + +Resource ID:rscexec_rsc_r1 +Resource agent class:ocf +Resource agent type:lrmregtest +Resource agent provider:heartbeat +Resource agent parameters:delay=0 +.TRY Exec rscexec_rsc_r1 op=start timeout=1000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.TRY Show state rscexec_rsc_r1 +resource state:LRM_RSC_IDLE +The resource 1 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=0 +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=start timeout=1000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=stop timeout=1000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Show state rscexec_rsc_r1 +resource state:LRM_RSC_IDLE +The resource 3 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=0 + operation 'monitor' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=0 + operation 'stop' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=0 +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=7): [null] + +.TRY Exec rscexec_rsc_r1 op=stop timeout=1000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=7): [null] + +.TRY Exec rscexec_rsc_r1 op=meta-data timeout=1000 interval=0 target=EVERYTIME args= +> meta-data succeed (status=0,rc=0): [null] + +.TRY Delete resource rscexec_rsc_r1 +Succeeded in deleting this resource. +.SETENV dflt_class=lsb dftl_rsc=rscexec_rsc_r1-lsb +.TRY Add resource rscexec_rsc_r1 class=lsb type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY Exec rscexec_rsc_r1 op=start timeout=1000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.TRY Show state rscexec_rsc_r1 +resource state:LRM_RSC_IDLE +The resource 1 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=start timeout=1000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=stop timeout=1000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Show state rscexec_rsc_r1 +resource state:LRM_RSC_IDLE +The resource 3 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: + operation 'monitor' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: + operation 'stop' [call_id=(removed)]: + start_delay=0, interval=0, timeout=1000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=7): [null] + +.TRY Exec rscexec_rsc_r1 op=stop timeout=1000 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Exec rscexec_rsc_r1 op=monitor timeout=1000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=7): [null] + +.TRY Exec rscexec_rsc_r1 op=meta-data timeout=1000 interval=0 target=EVERYTIME args= +> meta-data succeed (status=0,rc=0): [null] + +.TRY Delete resource rscexec_rsc_r1 +Succeeded in deleting this resource. diff --git a/lrm/test/testcases/rscmgmt b/lrm/test/testcases/rscmgmt new file mode 100644 index 0000000..8d745d3 --- /dev/null +++ b/lrm/test/testcases/rscmgmt @@ -0,0 +1,29 @@ +list +# add/remove resources +# +add rsc=r1 +info rsc=r1 +list +del rsc=r1 +%setenv dflt_class=lsb dflt_type=lrmregtest +list +add rsc=r1 +list +del rsc=r1 +list +# +# a bit of mix +# +%setenv dflt_class=ocf +add rsc=r1 +add rsc=r1 class=lsb type=lrmregtest +add rsc=r1 +del rsc=r1 +add rsc=r1 class=lsb type=lrmregtest +list +add rsc=r2 +list +del rsc=r1 +del rsc=r2 +list +del rsc=r1 diff --git a/lrm/test/testcases/rscmgmt.exp b/lrm/test/testcases/rscmgmt.exp new file mode 100644 index 0000000..3a5c4bf --- /dev/null +++ b/lrm/test/testcases/rscmgmt.exp @@ -0,0 +1,74 @@ +.TRY List resources +Currently no resources are managed by LRM. +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY Show info r1 + +Resource ID:r1 +Resource agent class:ocf +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY List resources + +Resource ID:r1 +Resource agent class:ocf +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY Delete resource r1 +Succeeded in deleting this resource. +.SETENV dflt_class=lsb dflt_type=lrmregtest +.TRY List resources +Currently no resources are managed by LRM. +.TRY Add resource r1 class=lsb type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY List resources + +Resource ID:r1 +Resource agent class:lsb +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY Delete resource r1 +Succeeded in deleting this resource. +.TRY List resources +Currently no resources are managed by LRM. +.SETENV dflt_class=ocf +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY Add resource r1 class=lsb type=lrmregtest provider=heartbeat args= +ERROR: lrm_add_rsc(): got a return code HA_FAIL from a reply message of addrsc with function get_ret_from_msg. +Failed to add this resource. +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args= +ERROR: lrm_add_rsc(): got a return code HA_FAIL from a reply message of addrsc with function get_ret_from_msg. +Failed to add this resource. +.TRY Delete resource r1 +Succeeded in deleting this resource. +.TRY Add resource r1 class=lsb type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY List resources + +Resource ID:r1 +Resource agent class:lsb +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY Add resource r2 class=ocf type=lrmregtest provider=heartbeat args= +Succeeded in adding this resource. +.TRY List resources + +Resource ID:r2 +Resource agent class:ocf +Resource agent type:lrmregtest +Resource agent provider:heartbeat + +Resource ID:r1 +Resource agent class:lsb +Resource agent type:lrmregtest +Resource agent provider:heartbeat +.TRY Delete resource r1 +Succeeded in deleting this resource. +.TRY Delete resource r2 +Succeeded in deleting this resource. +.TRY List resources +Currently no resources are managed by LRM. +.TRY Delete resource r1 +ERROR: lrm_delete_rsc(): got a return code HA_FAIL from a reply message of delrsc with function get_ret_from_msg. +Failed to delete this resource. diff --git a/lrm/test/testcases/rscmgmt.log_filter b/lrm/test/testcases/rscmgmt.log_filter new file mode 100755 index 0000000..34debc5 --- /dev/null +++ b/lrm/test/testcases/rscmgmt.log_filter @@ -0,0 +1,13 @@ +#!/bin/sh + +awk ' +n<2 && /ERROR: on_msg_add_rsc: same id resource exists./ {n++; next} +m<1 && /ERROR: on_msg_del_rsc: no rsc with id/ {m++; next} +{print} +END{ + if( n!=2 ) + print "ERROR: missed on_msg_add_rsc errors"; + if( m!=1 ) + print "ERROR: missed on_msg_del_rsc errors"; +} +' diff --git a/lrm/test/testcases/serialize b/lrm/test/testcases/serialize new file mode 100644 index 0000000..cad96b3 --- /dev/null +++ b/lrm/test/testcases/serialize @@ -0,0 +1,33 @@ +list +# allow for a delay of 2 seconds +%setenv dflt_timeout=2500 +add rsc=r1 args="delay=2" +# +# we run the next three ops in the background +# in case ops are not serialized, the lrmregtest RA should complain +# +%bg 2 +exec operation=start +# insert sleeps to make sure that the operations are started in +# the order given here +%sleep 1 +# set timeouts high enough so that no op fails +exec operation=start timeout=3000 +%sleep 1 +%bgrepeat 4 +exec operation=monitor timeout=11000 +%sleep 11 +state +exec operation=stop +state +del rsc=r1 +# +# +# +%setenv dflt_rsc=r2 dflt_timeout=10500 +add rsc=r2 args="ignore_TERM=1 delay=9" +exec operation=start +%bg +exec operation=monitor timeout=500 +exec operation=monitor +del rsc=r2 diff --git a/lrm/test/testcases/serialize.exp b/lrm/test/testcases/serialize.exp new file mode 100644 index 0000000..b290c95 --- /dev/null +++ b/lrm/test/testcases/serialize.exp @@ -0,0 +1,100 @@ +.TRY List resources +Currently no resources are managed by LRM. +.SETENV dflt_timeout=2500 +.TRY Add resource r1 class=ocf type=lrmregtest provider=heartbeat args=delay=2 +Succeeded in adding this resource. +.BG 2 +.BG test line 10/job 1 runs in background +.SLEEP 1 +.BG test line 15/job 2 runs in background +.SLEEP 1 +.BGREPEAT 4 +.BG test line 18/job 1 runs in background +.BG test line 18/job 2 runs in background +.BG test line 18/job 3 runs in background +.BG test line 18/job 4 runs in background +.SLEEP 11 +.TRY Show state r1 +resource state:LRM_RSC_IDLE +The resource 2 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=3000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 + operation 'monitor' [call_id=(removed)]: + start_delay=0, interval=0, timeout=11000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 +.TRY Exec r1 op=stop timeout=2500 interval=0 target=EVERYTIME args= +> stop succeed (status=0,rc=0): [null] + +.TRY Show state r1 +resource state:LRM_RSC_IDLE +The resource 3 operations' information: + operation 'start' [call_id=(removed)]: + start_delay=0, interval=0, timeout=3000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 + operation 'monitor' [call_id=(removed)]: + start_delay=0, interval=0, timeout=11000, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 + operation 'stop' [call_id=(removed)]: + start_delay=0, interval=0, timeout=2500, app_name=lrmadmin + rc=0 (ok), op_status=0 (succeed) + parameters: delay=2 +.TRY Delete resource r1 +Succeeded in deleting this resource. +.SETENV dflt_rsc=r2 dflt_timeout=10500 +.TRY Add resource r2 class=ocf type=lrmregtest provider=heartbeat args=ignore_TERM=1 delay=9 +Succeeded in adding this resource. +.TRY Exec r2 op=start timeout=10500 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +.BG +.BG test line 31/job 1 runs in background +.TRY Exec r2 op=monitor timeout=10500 interval=0 target=EVERYTIME args= +ERROR: This operation has timed out - no result from lrmd. +.TRY Delete resource r2 +Succeeded in deleting this resource. +.BG test line 10/job 1 finished (exit code: 0): +==========test:10:1 start output========== +.TRY Exec r1 op=start timeout=2500 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:10:1 end output========== +.BG test line 15/job 2 finished (exit code: 0): +==========test:15:2 start output========== +.TRY Exec r1 op=start timeout=3000 interval=0 target=EVERYTIME args= +> start succeed (status=0,rc=0): [null] + +==========test:15:2 end output========== +.BG test line 18/job 1 finished (exit code: 0): +==========test:18:1 start output========== +.TRY Exec r1 op=monitor timeout=11000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:18:1 end output========== +.BG test line 18/job 2 finished (exit code: 0): +==========test:18:2 start output========== +.TRY Exec r1 op=monitor timeout=11000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:18:2 end output========== +.BG test line 18/job 3 finished (exit code: 0): +==========test:18:3 start output========== +.TRY Exec r1 op=monitor timeout=11000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:18:3 end output========== +.BG test line 18/job 4 finished (exit code: 0): +==========test:18:4 start output========== +.TRY Exec r1 op=monitor timeout=11000 interval=0 target=EVERYTIME args= +> monitor succeed (status=0,rc=0): [null] + +==========test:18:4 end output========== +.BG test line 31/job 1 finished (exit code: 0): +==========test:31:1 start output========== +.TRY Exec r2 op=monitor timeout=500 interval=0 target=EVERYTIME args= +ERROR: This operation has timed out - no result from lrmd. +==========test:31:1 end output========== diff --git a/lrm/test/testcases/stonith b/lrm/test/testcases/stonith new file mode 100644 index 0000000..f21cf18 --- /dev/null +++ b/lrm/test/testcases/stonith @@ -0,0 +1,2 @@ +%extcheck xmllint.sh many +%shell stonith -L | while read p; do echo $p:heartbeat; stonith -m -t $p; done diff --git a/lrm/test/testcases/stonith.exp b/lrm/test/testcases/stonith.exp new file mode 100644 index 0000000..f9f1042 --- /dev/null +++ b/lrm/test/testcases/stonith.exp @@ -0,0 +1,2 @@ +.EXTCHECK xmllint.sh many +.SHELL stonith -L | while read p; do echo $p:heartbeat; stonith -m -t $p; done diff --git a/lrm/test/testcases/xmllint.sh b/lrm/test/testcases/xmllint.sh new file mode 100755 index 0000000..f61288c --- /dev/null +++ b/lrm/test/testcases/xmllint.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +gawk -v many="$1" ' +BEGIN{XMLLINT="xmllint --noout -";} +function chkoutput(ra) { + if( ra=="" ) return; + if( close(XMLLINT) ) # we need gawk for this + print "xmllint reported error in RA:",ra; +} +many=="many" && /^[a-zA-Z][^:]*:[a-zA-Z0-9]+$/ { + chkoutput(ra); + ra=$0; + next; +} +{ print | XMLLINT } +END{ + if( many!="many" ) + chkoutput("noname"); +} +' diff --git a/replace/Makefile.am b/replace/Makefile.am new file mode 100644 index 0000000..52892ba --- /dev/null +++ b/replace/Makefile.am @@ -0,0 +1,29 @@ +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include \ + -I$(top_srcdir)/linux-ha -I$(top_builddir)/linux-ha + +QUIET_LIBTOOL_OPTS = @QUIET_LIBTOOL_OPTS@ +LIBTOOL = @LIBTOOL@ @QUIET_LIBTOOL_OPTS@ + + +noinst_LTLIBRARIES = libreplace.la +libreplace_la_SOURCES = +libreplace_la_LIBADD = @LTLIBOBJS@ diff --git a/replace/NoSuchFunctionName.c b/replace/NoSuchFunctionName.c new file mode 100644 index 0000000..373eabd --- /dev/null +++ b/replace/NoSuchFunctionName.c @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2002 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +void nosuchfunctionname(void); + +/* + * This is a completely useless function put here only to make OpenBSD make + * procedures happy. I hope no one ever makes such a function ;-) + */ +void +nosuchfunctionname(void) +{ + return; +} diff --git a/replace/alphasort.c b/replace/alphasort.c new file mode 100644 index 0000000..94cd811 --- /dev/null +++ b/replace/alphasort.c @@ -0,0 +1,53 @@ +/* + * + * alphasort - replacement for alphasort functions. + * + * Matt Soffen + + * Copyright (C) 2001 Matt Soffen + * + * Taken from the FreeBSD file (with copyright notice) + * /usr/src/gnu/lib/libdialog/dir.c + *************************************************************************** + * Program: dir.c + * Author: Marc van Kempen + * desc: Directory routines, sorting and reading + * + * Copyright (c) 1995, Marc van Kempen + * + * All rights reserved. + * + * This software may be used, modified, copied, distributed, and + * sold, in both source and binary form provided that the above + * copyright and these terms are retained, verbatim, as the first + * lines of this file. Under no circumstances is the author + * responsible for the proper functioning of this software, nor does + * the author assume any responsibility for damages incurred with + * its use. + * + *************************************************************************** + */ + +#include +#include +#include + +#include /* XXX for _POSIX_VERSION ifdefs */ + +#if HAVE_STRINGS_H +#include +#endif + +#if !defined sgi && !defined _POSIX_VERSION +#include +#endif + +#include +#include +#include +#include + +int alphasort(const void *dirent1, const void *dirent2) { + return(strcmp((*(const struct dirent **)dirent1)->d_name, + (*(const struct dirent **)dirent2)->d_name)); +} diff --git a/replace/daemon.c b/replace/daemon.c new file mode 100644 index 0000000..7697113 --- /dev/null +++ b/replace/daemon.c @@ -0,0 +1,83 @@ +/*- + * + * daemon - replacement for daemon function. + * + * Matt Soffen + * Copyright (C) 2004 Matt Soffen + * + * Taken from the FreeBSD file (with copyright notice) + * ------------------------------------------------------------ + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/lib/libc/gen/daemon.c,v 1.3 2000/01/27 23:06:14 jasone Exp $ + * + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)daemon.c 8.1 (Berkeley) 6/4/93"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include +#include + +int +daemon(nochdir, noclose) + int nochdir, noclose; +{ + int fd; + + switch (fork()) { + case -1: + return (-1); + case 0: + break; + default: + exit(0); + } + + if (setsid() == -1) + return (-1); + + if (!nochdir) + (void)chdir("/"); + + if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) { + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + if (fd > 2) + (void)close(fd); + } + return (0); +} diff --git a/replace/inet_pton.c b/replace/inet_pton.c new file mode 100644 index 0000000..f5aa93b --- /dev/null +++ b/replace/inet_pton.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 1996,1999 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* Chris Wright June 22, 2001 + * Merged contents of inet_pton.c from Apache2.0.16 and BIND8 + * The Apache base is more portable within heartbeat's envrionment, + * however, the BIND8 version has two small logic changes that are + * newer. + */ + +#include + +#if HAVE_SYS_TYPES_H +#include +#endif +#if HAVE_SYS_SOCKET_H +#include +#endif +#if HAVE_NETINET_IN_H +#include +#endif +#if HAVE_ARPA_INET_H +#include +#endif +#include +#include + +#ifndef IN6ADDRSZ +#define IN6ADDRSZ 16 +#endif + +#ifndef INT16SZ +#define INT16SZ sizeof(short) +#endif + +#ifndef INADDRSZ +#define INADDRSZ 4 +#endif + +#ifndef __P +#define __P(x) x +#endif + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4 __P((const char *src, unsigned char *dst)); +#if HAVE_IPV6 +static int inet_pton6 __P((const char *src, unsigned char *dst)); +#endif + +/* int + * inet_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * author: + * Paul Vixie, 1996. + */ +int +inet_pton(int af, const char *src, void *dst) +{ + switch (af) { + case AF_INET: + return (inet_pton4(src, dst)); +#if HAVE_IPV6 + case AF_INET6: + return (inet_pton6(src, dst)); +#endif + default: + errno = EAFNOSUPPORT; + return (-1); + } + /* NOTREACHED */ +} + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton4(const char *src, unsigned char *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + *(tp = tmp) = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr(digits, ch)) != NULL) { + unsigned int new = *tp * 10 + (pch - digits); + + if (new > 255) + return (0); + *tp = new; + if (! saw_digit) { + if (++octets > 4) + return (0); + saw_digit = 1; + } + } else if (ch == '.' && saw_digit) { + if (octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } else + return (0); + } + if (octets < 4) + return (0); + + memcpy(dst, tmp, INADDRSZ); + return (1); +} + +#if HAVE_IPV6 +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton6(const char *src, unsigned char *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + unsigned int val; + + memset((tp = tmp), '\0', IN6ADDRSZ); + endp = tp + IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if (*src == ':') + if (*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while ((ch = *src++) != '\0') { + const char *pch; + + if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return (0); + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return (0); + colonp = tp; + continue; + } else if (*src == '\0') { + return (0); + } + if (tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if (saw_xdigit) { + if (tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + if (tp == endp) + return (0); + for (i = 1; i <= n; i++) { + endp[- i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return (0); + memcpy(dst, tmp, IN6ADDRSZ); + return (1); +} +#endif /* HAVE_IPV6 */ diff --git a/replace/scandir.c b/replace/scandir.c new file mode 100644 index 0000000..528f544 --- /dev/null +++ b/replace/scandir.c @@ -0,0 +1,236 @@ +/* scandir: Scan a directory, collecting all (selected) items into a an array. + * + * This code borrowed from 'libit', which can be found here: + * + * http://www.iro.umontreal.ca/~pinard/libit/dist/scandir/ + * + * The original author put this code in the public domain. + * It has been modified slightly to get rid of warnings, etc. + * + * Below is the email I received from pinard@iro.umontreal.ca (François Pinard) + * when I sent him an email asking him about the license, etc. of this + * code which I obtained from his site. + * + * I think the correct spelling of his name is Rich Salz. I think he's now + * rsalz@datapower.com... + * -- + * Rich Salz, Chief Security Architect + * DataPower Technology http://www.datapower.com + * XS40 XML Security Gateway http://www.datapower.com/products/xs40.html + * + * Copyright(C): none (public domain) + * License: none (public domain) + * Author: Rich Salz + * + * + * + * -- Alan Robertson + * alanr@unix.sh + * + ************************************************************************** + * + * Subject: Re: Scandir replacement function + * Date: 18 May 2001 12:00:48 -0400 + * From: pinard@iro.umontreal.ca (François Pinard) + * To: Alan Robertson + * References: 1 + * + * + * [Alan Robertson] + * + * > Hi, I'd like to use your scandir replacement function found here: + * > http://www.iro.umontreal.ca/~pinard/libit/dist/scandir/ But, it does + * > not indicate authorship or licensing terms in it. Could you tell me + * > who wrote this code, under what license you distribute it, and whether + * > and under what terms I may further distribute it? + * + * Hello, Alan. These are (somewhat) explained in UNSHAR.HDR found in the + * same directory. The routines have been written by Rick Saltz (I'm not + * completely sure of the spelling) a long while ago. I think that nowadays, + * Rick is better known as the main author of the nice INN package. + * + ************************************************************************** + * + * I spent a little time verifying this with Rick Salz. + * The results are below: + * + ************************************************************************** + * + * Date: Tue, 20 Sep 2005 21:52:09 -0400 (EDT) + * From: Rich Salz + * To: Alan Robertson + * Subject: Re: Verifying permissions/licenses/etc on some old code of yours - + * scandir.c + * In-Reply-To: <433071CA.8000107@unix.sh> + * Message-ID: + * Content-Type: TEXT/PLAIN; charset=US-ASCII + * + * yes, it's most definitely in the public domain. + * + * I'm glad you find it useful. I'm surprised it hasn't been replaced by, + * e.g,. something in GLibC. Ii'm impressed you tracked me down. + * + * /r$ + * + * -- + * Rich Salz Chief Security Architect + * DataPower Technology http://www.datapower.com + * XS40 XML Security Gateway http://www.datapower.com/products/xs40.html + * ----------------------------------------------------------------------> + * Subject: scandir, ftw REDUX + * Date: 1 Jan 88 00:47:01 GMT + * From: rsalz@pebbles.bbn.com + * Newsgroups: comp.sources.misc + * + * + * Forget my previous message -- I just decided for completeness's sake to + * implement the SysV ftw(3) routine, too. + * + * To repeat, these are public-domain implementations of the SystemV ftw() + * routine, the BSD scandir() and alphasort() routines, and documentation for + * same. The FTW manpage could be more readable, but so it goes. + * + * Anyhow, feel free to post these, and incorporate them into your existing + * packages. I have readdir() routiens for MSDOS and the Amiga if anyone + * wants them, and should have them for VMS by the end of January; let me + * know if you want copies. + * + * Yours in filesystems, + * /r$ + * + * Anyhow, feel free to post + * ----------------------------------------------------------------------< + * + */ + +#include +#include +#include +#include +#include +#include + +#ifndef NULL +# define NULL ((void *) 0) +#endif + +/* Initial guess at directory allocated. */ +#define INITIAL_ALLOCATION 20 + +int +scandir (const char *directory_name, + struct dirent ***array_pointer, + int (*select_function) (const struct dirent *), + +#ifdef USE_SCANDIR_COMPARE_STRUCT_DIRENT + /* This is what the Linux man page says */ + int (*compare_function) (const struct dirent**, const struct dirent**) +#else + /* This is what the Linux header file says ... */ + int (*compare_function) (const void *, const void *) +#endif + ); + +int +scandir (const char *directory_name, + struct dirent ***array_pointer, + int (*select_function) (const struct dirent *), +#ifdef USE_SCANDIR_COMPARE_STRUCT_DIRENT + /* This is what the linux man page says */ + int (*compare_function) (const struct dirent**, const struct dirent**) +#else + /* This is what the linux header file says ... */ + int (*compare_function) (const void *, const void *) +#endif + ) +{ + DIR *directory; + struct dirent **array; + struct dirent *entry; + struct dirent *copy; + int allocated = INITIAL_ALLOCATION; + int counter = 0; + + /* Get initial list space and open directory. */ + + if (directory = opendir (directory_name), directory == NULL) + return -1; + + if (array = (struct dirent **) malloc (allocated * sizeof (struct dirent *)), + array == NULL) + return -1; + + /* Read entries in the directory. */ + + while (entry = readdir (directory), entry) + if (select_function == NULL || (*select_function) (entry)) + { + /* User wants them all, or he wants this one. Copy the entry. */ + + /* + * On some OSes the declaration of "entry->d_name" is a minimal-length + * placeholder. Example: Solaris: + * /usr/include/sys/dirent.h: + * "char d_name[1];" + * man page "dirent(3)": + * The field d_name is the beginning of the character array + * giving the name of the directory entry. This name is + * null terminated and may have at most MAXNAMLEN chars. + * So our malloc length may need to be increased accordingly. + * sizeof(entry->d_name): space (possibly minimal) in struct. + * strlen(entry->d_name): actual length of the entry. + * + * John Kavadias + * David Lee + */ + int namelength = strlen(entry->d_name) + 1; /* length with NULL */ + int extra = 0; + + if (sizeof(entry->d_name) <= namelength) { + /* allocated space <= required space */ + extra += namelength - sizeof(entry->d_name); + } + + if (copy = (struct dirent *) malloc (sizeof (struct dirent) + extra), + copy == NULL) + { + closedir (directory); + free (array); + return -1; + } + copy->d_ino = entry->d_ino; + copy->d_reclen = entry->d_reclen; + strcpy (copy->d_name, entry->d_name); + + /* Save the copy. */ + + if (counter + 1 == allocated) + { + allocated <<= 1; + array = (struct dirent **) + realloc ((char *) array, allocated * sizeof (struct dirent *)); + if (array == NULL) + { + closedir (directory); + free (array); + free (copy); + return -1; + } + } + array[counter++] = copy; + } + + /* Close things off. */ + + array[counter] = NULL; + *array_pointer = array; + closedir (directory); + + /* Sort? */ + + if (counter > 1 && compare_function) + qsort ((char *) array, counter, sizeof (struct dirent *) + , (int (*)(const void *, const void *))(compare_function)); + + return counter; +} diff --git a/replace/setenv.c b/replace/setenv.c new file mode 100644 index 0000000..e8cafb1 --- /dev/null +++ b/replace/setenv.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2001 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include + +/* + * Small replacement function for setenv() + */ +int +setenv(const char *name, const char * value, int why) +{ + int rc = -1; + + if ( name && value ) { + char * envp = NULL; + envp = malloc(strlen(name)+strlen(value)+2); + if (envp) { + /* + * Unfortunately, the putenv API guarantees memory leaks when + * changing environment variables repeatedly... :-( + */ + + sprintf(envp, "%s=%s", name, value); + + /* Cannot free envp (!) */ + rc = putenv(envp); + } + + } + return(rc); +} diff --git a/replace/strerror.c b/replace/strerror.c new file mode 100644 index 0000000..477239f --- /dev/null +++ b/replace/strerror.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2002 Alan Robertson + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of version 2.1 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +extern const char * sys_err[]; +extern int sys_nerr; +char * +strerror(int errnum) +{ + static char whaterr[32]; + + if (errnum < 0) { + return "negative errno"; + } + if (errnum >= sys_nerr) { + snprintf(whaterr, sizeof(whaterr),"error %d", errnum); + return whaterr; + } + return sys_err[sys_nerr]; +} diff --git a/replace/strlcat.c b/replace/strlcat.c new file mode 100644 index 0000000..8b909f9 --- /dev/null +++ b/replace/strlcat.c @@ -0,0 +1,33 @@ +#include +#include +/* + * Copyright (C) 2007 Alan Robertson + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +size_t +strlcat(char *dest, const char * src, size_t maxlen) +{ + size_t curlen = strlen(dest); + size_t addlen = strlen(src); + size_t appendlen = (maxlen-1) - curlen; + if (appendlen > 0) { + strlcpy(dest+curlen, src, maxlen-curlen); + } + return curlen + addlen; +} diff --git a/replace/strlcpy.c b/replace/strlcpy.c new file mode 100644 index 0000000..661d02e --- /dev/null +++ b/replace/strlcpy.c @@ -0,0 +1,32 @@ +#include +#include +/* + * Copyright (C) 2007 Alan Robertson + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +size_t +strlcpy(char *dest, const char * src, size_t maxlen) +{ + size_t srclen = strlen(src); + if (maxlen > 0) { + strncpy(dest, src, maxlen); + dest[maxlen-1]=EOS; + } + return srclen; +} diff --git a/replace/strndup.c b/replace/strndup.c new file mode 100644 index 0000000..4312743 --- /dev/null +++ b/replace/strndup.c @@ -0,0 +1,38 @@ +#include +#include +#include +/* + * Copyright (C) 2004 Matt Soffen + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* Taken from the GlibC implementation of strndup */ + +char *strndup(const char *str, size_t len) +{ + size_t n = strnlen(str,len); + char *new = (char *) malloc (len+1); + + if (NULL == new) { + return NULL; + } + + new[n] = '\0'; + return (char *)memcpy (new, str, len); +} + diff --git a/replace/strnlen.c b/replace/strnlen.c new file mode 100644 index 0000000..8b3bcd2 --- /dev/null +++ b/replace/strnlen.c @@ -0,0 +1,31 @@ +#include +#include +/* + * Copyright (C) 2003 Alan Robertson + * This software licensed under the GNU LGPL. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +size_t +strnlen(const char *s, size_t maxlen) +{ + const char * eospos; + + eospos = memchr(s, (int)'\0', maxlen); + + return (eospos == NULL ? maxlen : (size_t)(eospos-s)); +} diff --git a/replace/unsetenv.c b/replace/unsetenv.c new file mode 100644 index 0000000..aeb84a3 --- /dev/null +++ b/replace/unsetenv.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2001 Alan Robertson + * This software licensed under the GNU LGPL. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#define __environ environ +#ifndef HAVE_ENVIRON_DECL +extern char **environ; +#endif + +int +unsetenv (const char *name) +{ + const size_t len = strlen (name); + char **ep; + + for (ep = __environ; *ep; ++ep) { + if (!strncmp (*ep, name, len) && (*ep)[len] == '=') { + /* Found it. */ + /* Remove this pointer by moving later ones back. */ + char **dp = ep; + do + dp[0] = dp[1]; + while (*dp++); + /* Continue the loop in case NAME appears again. */ + } + } + return 0; +} diff --git a/replace/uuid_parse.c b/replace/uuid_parse.c new file mode 100644 index 0000000..beecac6 --- /dev/null +++ b/replace/uuid_parse.c @@ -0,0 +1,519 @@ +/* + * uuid: emulation of e2fsprogs interface if implementation lacking. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Original uuid implementation: copyright (C) Theodore Ts'o + * + * This importation into heartbeat: + * Copyright (C) 2004 David Lee + * + */ + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#include +#include + +#include + +/* + * Local "replace" implementation of uuid functions. + */ + + +#include +#include +#include + +/* UUID Variant definitions */ +#define UUID_VARIANT_NCS 0 +#define UUID_VARIANT_DCE 1 +#define UUID_VARIANT_MICROSOFT 2 +#define UUID_VARIANT_OTHER 3 + +/* UUID Type definitions */ +#define UUID_TYPE_DCE_TIME 1 +#define UUID_TYPE_DCE_RANDOM 4 + + + +/* For uuid_compare() */ +#define UUCMP(u1,u2) if (u1 != u2) return((u1 < u2) ? -1 : 1); + +/************************************ + * Private types + ************************************/ + +#define longlong long long + +/* + * Offset between 15-Oct-1582 and 1-Jan-70 + */ +#define TIME_OFFSET_HIGH 0x01B21DD2 +#define TIME_OFFSET_LOW 0x13814000 + +#if (SIZEOF_INT == 4) +typedef unsigned int __u32; +#elif (SIZEOF_LONG == 4) +typedef unsigned long __u32; +#endif + +#if (SIZEOF_INT == 2) +typedef int __s16; +typedef unsigned int __u16; +#elif (SIZEOF_SHORT == 2) +typedef short __s16; +typedef unsigned short __u16; +#endif + +typedef unsigned char __u8; + +struct uuid { + __u32 time_low; + __u16 time_mid; + __u16 time_hi_and_version; + __u16 clock_seq; + __u8 node[6]; +}; + +/************************************ + * internal routines + ************************************/ +static void uuid_pack(const struct uuid *uu, uuid_t ptr) +{ + __u32 tmp; + unsigned char *out = ptr; + + tmp = uu->time_low; + out[3] = (unsigned char) tmp; + tmp >>= 8; + out[2] = (unsigned char) tmp; + tmp >>= 8; + out[1] = (unsigned char) tmp; + tmp >>= 8; + out[0] = (unsigned char) tmp; + + tmp = uu->time_mid; + out[5] = (unsigned char) tmp; + tmp >>= 8; + out[4] = (unsigned char) tmp; + + tmp = uu->time_hi_and_version; + out[7] = (unsigned char) tmp; + tmp >>= 8; + out[6] = (unsigned char) tmp; + + tmp = uu->clock_seq; + out[9] = (unsigned char) tmp; + tmp >>= 8; + out[8] = (unsigned char) tmp; + + memcpy(out+10, uu->node, 6); +} + +static void uuid_unpack(const uuid_t in, struct uuid *uu) +{ + const __u8 *ptr = in; + __u32 tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_low = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_mid = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_hi_and_version = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->clock_seq = tmp; + + memcpy(uu->node, ptr, 6); +} + +/************************************ + * Main routines, except uuid_generate*() + ************************************/ +void +uuid_clear(uuid_t uu) +{ + memset(uu, 0, 16); +} + +int +uuid_compare(const uuid_t uu1, const uuid_t uu2) +{ + struct uuid uuid1, uuid2; + + uuid_unpack(uu1, &uuid1); + uuid_unpack(uu2, &uuid2); + + UUCMP(uuid1.time_low, uuid2.time_low); + UUCMP(uuid1.time_mid, uuid2.time_mid); + UUCMP(uuid1.time_hi_and_version, uuid2.time_hi_and_version); + UUCMP(uuid1.clock_seq, uuid2.clock_seq); + return memcmp(uuid1.node, uuid2.node, 6); +} + +void +uuid_copy(uuid_t dst, const uuid_t src) +{ + unsigned char *cp1; + const unsigned char *cp2; + int i; + + for (i=0, cp1 = dst, cp2 = src; i < 16; i++) + *cp1++ = *cp2++; +} + +/* if uu is the null uuid, return 1 else 0 */ +int +uuid_is_null(const uuid_t uu) +{ + const unsigned char *cp; + int i; + + for (i=0, cp = uu; i < 16; i++) + if (*cp++) + return 0; + return 1; +} + +/* 36byte-string=>uuid */ +int +uuid_parse(const char *in, uuid_t uu) +{ + struct uuid uuid; + int i; + const char *cp; + char buf[3]; + + if (strlen(in) != 36) + return -1; + for (i=0, cp = in; i <= 36; i++,cp++) { + if ((i == 8) || (i == 13) || (i == 18) || + (i == 23)) { + if (*cp == '-') + continue; + else + return -1; + } + if (i== 36) + if (*cp == 0) + continue; + if (!isxdigit((int) *cp)) + return -1; + } + uuid.time_low = strtoul(in, NULL, 16); + uuid.time_mid = strtoul(in+9, NULL, 16); + uuid.time_hi_and_version = strtoul(in+14, NULL, 16); + uuid.clock_seq = strtoul(in+19, NULL, 16); + cp = in+24; + buf[2] = 0; + for (i=0; i < 6; i++) { + buf[0] = *cp++; + buf[1] = *cp++; + uuid.node[i] = strtoul(buf, NULL, 16); + } + + uuid_pack(&uuid, uu); + return 0; +} + +/* uuid=>36byte-string-with-null */ +void +uuid_unparse(const uuid_t uu, char *out) +{ + struct uuid uuid; + + uuid_unpack(uu, &uuid); + sprintf(out, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, + uuid.clock_seq >> 8, uuid.clock_seq & 0xFF, + uuid.node[0], uuid.node[1], uuid.node[2], + uuid.node[3], uuid.node[4], uuid.node[5]); +} + + +/************************************ + * Main routines: uuid_generate*() + ************************************/ + +#include +#include +#include + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_SYS_SOCKIO_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_SRANDOM +#define srand(x) srandom(x) +#define rand() random() +#endif + +static int get_random_fd(void) +{ + struct timeval tv; + static int fd = -2; + int i; + + if (fd == -2) { + gettimeofday(&tv, 0); + fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) + fd = open("/dev/random", O_RDONLY | O_NONBLOCK); + srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec); + } + /* Crank the random number generator a few times */ + gettimeofday(&tv, 0); + for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--) + rand(); + return fd; +} + +/* + * Generate a series of random bytes. Use /dev/urandom if possible, + * and if not, use srandom/random. + */ +static void get_random_bytes(void *buf, int nbytes) +{ + int i, n = nbytes, fd = get_random_fd(); + int lose_counter = 0; + unsigned char *cp = (unsigned char *) buf; + + if (fd >= 0) { + while (n > 0) { + i = read(fd, cp, n); + if (i <= 0) { + if (lose_counter++ > 16) + break; + continue; + } + n -= i; + cp += i; + lose_counter = 0; + } + } + + /* + * We do this all the time, but this is the only source of + * randomness if /dev/random/urandom is out to lunch. + */ + for (cp = buf, i = 0; i < nbytes; i++) + *cp++ ^= (rand() >> 7) & 0xFF; + return; +} + +/* + * Get the ethernet hardware address, if we can find it... + */ +static int get_node_id(unsigned char *node_id) +{ +#ifdef HAVE_NET_IF_H + int sd; + struct ifreq ifr, *ifrp; + struct ifconf ifc; + char buf[1024]; + int n, i; + unsigned char *a; + +/* + * BSD 4.4 defines the size of an ifreq to be + * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len + * However, under earlier systems, sa_len isn't present, so the size is + * just sizeof(struct ifreq) + */ +#ifdef HAVE_SA_LEN +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#define ifreq_size(i) max(sizeof(struct ifreq),\ + sizeof((i).ifr_name)+(i).ifr_addr.sa_len) +#else +#define ifreq_size(i) sizeof(struct ifreq) +#endif /* HAVE_SA_LEN*/ + + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sd < 0) { + return -1; + } + memset(buf, 0, sizeof(buf)); + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl (sd, SIOCGIFCONF, (char *)&ifc) < 0) { + close(sd); + return -1; + } + n = ifc.ifc_len; + for (i = 0; i < n; i+= ifreq_size(*ifr) ) { + ifrp = (struct ifreq *)((char *) ifc.ifc_buf+i); + strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); +#ifdef SIOCGIFHWADDR + if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) + continue; + a = (unsigned char *) &ifr.ifr_hwaddr.sa_data; +#else +#ifdef SIOCGENADDR + if (ioctl(sd, SIOCGENADDR, &ifr) < 0) + continue; + a = (unsigned char *) ifr.ifr_enaddr; +#else + /* + * XXX we don't have a way of getting the hardware + * address + */ + close(sd); + return 0; +#endif /* SIOCGENADDR */ +#endif /* SIOCGIFHWADDR */ + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + continue; + if (node_id) { + memcpy(node_id, a, 6); + close(sd); + return 1; + } + } + close(sd); +#endif + return 0; +} + +/* Assume that the gettimeofday() has microsecond granularity */ +#define MAX_ADJUSTMENT 10 + +static int +get_clock(__u32 *clock_high, __u32 *clock_low, __u16 *ret_clock_seq) +{ + static int adjustment = 0; + static struct timeval last = {0, 0}; + static __u16 clock_seq; + struct timeval tv; + unsigned longlong clock_reg; + +try_again: + gettimeofday(&tv, 0); + if ((last.tv_sec == 0) && (last.tv_usec == 0)) { + get_random_bytes(&clock_seq, sizeof(clock_seq)); + clock_seq &= 0x1FFF; + last = tv; + last.tv_sec--; + } + if ((tv.tv_sec < last.tv_sec) || + ((tv.tv_sec == last.tv_sec) && + (tv.tv_usec < last.tv_usec))) { + clock_seq = (clock_seq+1) & 0x1FFF; + adjustment = 0; + last = tv; + } else if ((tv.tv_sec == last.tv_sec) && + (tv.tv_usec == last.tv_usec)) { + if (adjustment >= MAX_ADJUSTMENT) + goto try_again; + adjustment++; + } else { + adjustment = 0; + last = tv; + } + + clock_reg = tv.tv_usec*10 + adjustment; + clock_reg += ((unsigned longlong) tv.tv_sec)*10000000; + clock_reg += (((unsigned longlong) 0x01B21DD2) << 32) + 0x13814000; + + *clock_high = clock_reg >> 32; + *clock_low = clock_reg; + *ret_clock_seq = clock_seq; + return 0; +} + +/* create a new uuid, based on randomness */ +void +uuid_generate_random(uuid_t out) +{ + uuid_t buf; + struct uuid uu; + + get_random_bytes(buf, sizeof(buf)); + uuid_unpack(buf, &uu); + + uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000; + uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x4000; + uuid_pack(&uu, out); +} + +/* create a new uuid, based on time */ +static void +uuid_generate_time(uuid_t out) +{ + static unsigned char node_id[6]; + static int has_init = 0; + struct uuid uu; + __u32 clock_mid; + + if (!has_init) { + if (get_node_id(node_id) <= 0) { + get_random_bytes(node_id, 6); + /* + * Set multicast bit, to prevent conflicts + * with IEEE 802 addresses obtained from + * network cards + */ + node_id[0] |= 0x80; + } + has_init = 1; + } + get_clock(&clock_mid, &uu.time_low, &uu.clock_seq); + uu.clock_seq |= 0x8000; + uu.time_mid = (__u16) clock_mid; + uu.time_hi_and_version = (clock_mid >> 16) | 0x1000; + memcpy(uu.node, node_id, 6); + uuid_pack(&uu, out); +} + +void +uuid_generate(uuid_t out) +{ + if (get_random_fd() >= 0) { + uuid_generate_random(out); + }else{ + uuid_generate_time(out); + } +} -- cgit v1.2.3