diff --git a/cts/cli/regression.access_render.exp b/cts/cli/regression.access_render.exp index f0ec2675178..ed2f704bcfd 100644 --- a/cts/cli/regression.access_render.exp +++ b/cts/cli/regression.access_render.exp @@ -1,6 +1,6 @@ =#=#=#= Begin test: Configure some ACLs =#=#=#= =#=#=#= Current cib after: Configure some ACLs =#=#=#= - + @@ -23,7 +23,7 @@ * Passed: cibadmin - Configure some ACLs =#=#=#= Begin test: Enable ACLs =#=#=#= =#=#=#= Current cib after: Enable ACLs =#=#=#= - + @@ -50,7 +50,7 @@ * Passed: crm_attribute - Enable ACLs =#=#=#= Begin test: An instance of ACLs render (into color) =#=#=#= - +    @@ -77,7 +77,7 @@ * Passed: cibadmin - An instance of ACLs render (into color) =#=#=#= Begin test: An instance of ACLs render (into namespacing) =#=#=#= - + @@ -105,7 +105,7 @@ =#=#=#= Begin test: An instance of ACLs render (into text) =#=#=#= vvv---[ READABLE ]---vvv - + diff --git a/cts/cli/regression.acls.exp b/cts/cli/regression.acls.exp index a6aeb155e23..353edeb53c3 100644 --- a/cts/cli/regression.acls.exp +++ b/cts/cli/regression.acls.exp @@ -1,6 +1,6 @@ =#=#=#= Begin test: Configure some ACLs =#=#=#= =#=#=#= Current cib after: Configure some ACLs =#=#=#= - + @@ -56,7 +56,7 @@ * Passed: cibadmin - Configure some ACLs =#=#=#= Begin test: Enable ACLs =#=#=#= =#=#=#= Current cib after: Enable ACLs =#=#=#= - + @@ -116,7 +116,7 @@ * Passed: crm_attribute - Enable ACLs =#=#=#= Begin test: Set cluster option =#=#=#= =#=#=#= Current cib after: Set cluster option =#=#=#= - + @@ -177,7 +177,7 @@ * Passed: crm_attribute - Set cluster option =#=#=#= Begin test: New ACL role =#=#=#= =#=#=#= Current cib after: New ACL role =#=#=#= - + @@ -241,7 +241,7 @@ * Passed: cibadmin - New ACL role =#=#=#= Begin test: New ACL target =#=#=#= =#=#=#= Current cib after: New ACL target =#=#=#= - + @@ -308,7 +308,7 @@ * Passed: cibadmin - New ACL target =#=#=#= Begin test: Another ACL role =#=#=#= =#=#=#= Current cib after: Another ACL role =#=#=#= - + @@ -378,7 +378,7 @@ * Passed: cibadmin - Another ACL role =#=#=#= Begin test: Another ACL target =#=#=#= =#=#=#= Current cib after: Another ACL target =#=#=#= - + @@ -451,7 +451,7 @@ * Passed: cibadmin - Another ACL target =#=#=#= Begin test: Updated ACL =#=#=#= =#=#=#= Current cib after: Updated ACL =#=#=#= - + @@ -560,7 +560,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= End test: l33t-haxor: Create a resource - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - l33t-haxor: Create a resource =#=#=#= Begin test: niceguy: Query configuration =#=#=#= - + @@ -641,7 +641,7 @@ crm_attribute: Error performing operation: Permission denied =#=#=#= Begin test: niceguy: Set fencing-enabled =#=#=#= pcmk__apply_creation_acl trace: ACLs allow creation of with id="cib-bootstrap-options-fencing-enabled" =#=#=#= Current cib after: niceguy: Set fencing-enabled =#=#=#= - + @@ -721,7 +721,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= End test: niceguy: Create a resource - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - niceguy: Create a resource =#=#=#= Begin test: root: Query configuration =#=#=#= - + @@ -796,7 +796,7 @@ cibadmin: CIB API call failed: Permission denied * Passed: cibadmin - root: Query configuration =#=#=#= Begin test: root: Set fencing-enabled =#=#=#= =#=#=#= Current cib after: root: Set fencing-enabled =#=#=#= - + @@ -871,7 +871,7 @@ cibadmin: CIB API call failed: Permission denied * Passed: crm_attribute - root: Set fencing-enabled =#=#=#= Begin test: root: Create a resource =#=#=#= =#=#=#= Current cib after: root: Create a resource =#=#=#= - + @@ -948,7 +948,7 @@ cibadmin: CIB API call failed: Permission denied * Passed: cibadmin - root: Create a resource =#=#=#= Begin test: root: Create another resource (with description) =#=#=#= =#=#=#= Current cib after: root: Create another resource (with description) =#=#=#= - + @@ -1045,7 +1045,7 @@ pcmk__apply_creation_acl trace: Creation of scaffolding with pcmk__apply_creation_acl trace: ACLs allow creation of with id="dummy-meta_attributes-target-role" Set 'dummy' option: id=dummy-meta_attributes-target-role set=dummy-meta_attributes name=target-role value=Stopped =#=#=#= Current cib after: niceguy: Create a resource meta attribute =#=#=#= - + @@ -1129,7 +1129,7 @@ Set 'dummy' option: id=dummy-meta_attributes-target-role set=dummy-meta_attribut unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Stopped =#=#=#= Current cib after: niceguy: Query a resource meta attribute =#=#=#= - + @@ -1213,7 +1213,7 @@ Stopped unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Deleted 'dummy' option: id=dummy-meta_attributes-target-role name=target-role =#=#=#= Current cib after: niceguy: Remove a resource meta attribute =#=#=#= - + @@ -1296,7 +1296,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h pcmk__apply_creation_acl trace: ACLs allow creation of with id="dummy-meta_attributes-target-role" Set 'dummy' option: id=dummy-meta_attributes-target-role set=dummy-meta_attributes name=target-role value=Started =#=#=#= Current cib after: niceguy: Create a resource meta attribute =#=#=#= - + @@ -1404,7 +1404,7 @@ Set 'dummy' option: id=dummy-meta_attributes-target-role set=dummy-meta_attribut =#=#=#= End test: betteridea: Query configuration - explicit deny - OK (0) =#=#=#= * Passed: cibadmin - betteridea: Query configuration - explicit deny - + @@ -1432,7 +1432,7 @@ pcmk__check_acl trace: Default ACL denies user 'niceguy' read/write access to / cibadmin: CIB API call failed: Permission denied =#=#=#= End test: niceguy: Replace - remove acls - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - niceguy: Replace - remove acls - + @@ -1518,7 +1518,7 @@ pcmk__apply_creation_acl trace: ACLs disallow creation of with id=" cibadmin: CIB API call failed: Permission denied =#=#=#= End test: niceguy: Replace - create resource - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - niceguy: Replace - create resource - + @@ -1602,7 +1602,7 @@ pcmk__check_acl trace: Default ACL denies user 'niceguy' read/write access to / cibadmin: CIB API call failed: Permission denied =#=#=#= End test: niceguy: Replace - modify attribute (deny) - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - niceguy: Replace - modify attribute (deny) - + @@ -1686,7 +1686,7 @@ pcmk__check_acl trace: Default ACL denies user 'niceguy' read/write access to / cibadmin: CIB API call failed: Permission denied =#=#=#= End test: niceguy: Replace - delete attribute (deny) - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - niceguy: Replace - delete attribute (deny) - + @@ -1770,7 +1770,7 @@ pcmk__check_acl trace: Default ACL denies user 'niceguy' read/write access to / cibadmin: CIB API call failed: Permission denied =#=#=#= End test: niceguy: Replace - create attribute (deny) - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - niceguy: Replace - create attribute (deny) - + @@ -1851,7 +1851,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: bob: Replace - create attribute (direct allow) =#=#=#= =#=#=#= End test: bob: Replace - create attribute (direct allow) - OK (0) =#=#=#= * Passed: cibadmin - bob: Replace - create attribute (direct allow) - + @@ -1932,7 +1932,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: bob: Replace - modify attribute (direct allow) =#=#=#= =#=#=#= End test: bob: Replace - modify attribute (direct allow) - OK (0) =#=#=#= * Passed: cibadmin - bob: Replace - modify attribute (direct allow) - + @@ -2009,7 +2009,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: bob: Replace - delete attribute (direct allow) =#=#=#= =#=#=#= End test: bob: Replace - delete attribute (direct allow) - OK (0) =#=#=#= * Passed: cibadmin - bob: Replace - delete attribute (direct allow) - + @@ -2086,7 +2086,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: joe: Replace - create attribute (inherited allow) =#=#=#= =#=#=#= End test: joe: Replace - create attribute (inherited allow) - OK (0) =#=#=#= * Passed: cibadmin - joe: Replace - create attribute (inherited allow) - + @@ -2163,7 +2163,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: joe: Replace - modify attribute (inherited allow) =#=#=#= =#=#=#= End test: joe: Replace - modify attribute (inherited allow) - OK (0) =#=#=#= * Passed: cibadmin - joe: Replace - modify attribute (inherited allow) - + @@ -2240,7 +2240,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: joe: Replace - delete attribute (inherited allow) =#=#=#= =#=#=#= End test: joe: Replace - delete attribute (inherited allow) - OK (0) =#=#=#= * Passed: cibadmin - joe: Replace - delete attribute (inherited allow) - + @@ -2317,7 +2317,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: mike: Replace - create attribute (allow overrides deny) =#=#=#= =#=#=#= End test: mike: Replace - create attribute (allow overrides deny) - OK (0) =#=#=#= * Passed: cibadmin - mike: Replace - create attribute (allow overrides deny) - + @@ -2394,7 +2394,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: mike: Replace - modify attribute (allow overrides deny) =#=#=#= =#=#=#= End test: mike: Replace - modify attribute (allow overrides deny) - OK (0) =#=#=#= * Passed: cibadmin - mike: Replace - modify attribute (allow overrides deny) - + @@ -2471,7 +2471,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: mike: Replace - delete attribute (allow overrides deny) =#=#=#= =#=#=#= End test: mike: Replace - delete attribute (allow overrides deny) - OK (0) =#=#=#= * Passed: cibadmin - mike: Replace - delete attribute (allow overrides deny) - + @@ -2548,7 +2548,7 @@ cibadmin: CIB API call failed: Permission denied =#=#=#= Begin test: mike: Create another resource =#=#=#= pcmk__apply_creation_acl trace: ACLs allow creation of with id="dummy2" =#=#=#= Current cib after: mike: Create another resource =#=#=#= - + @@ -2625,7 +2625,7 @@ pcmk__apply_creation_acl trace: ACLs allow creation of with id="dum =#=#=#= End test: mike: Create another resource - OK (0) =#=#=#= * Passed: cibadmin - mike: Create another resource - + @@ -2705,7 +2705,7 @@ pcmk__check_acl trace: Parent ACL denies user 'chris' read/write access to /cib cibadmin: CIB API call failed: Permission denied =#=#=#= End test: chris: Replace - create attribute (deny overrides allow) - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - chris: Replace - create attribute (deny overrides allow) - + @@ -2785,7 +2785,7 @@ pcmk__check_acl trace: Parent ACL denies user 'chris' read/write access to /cib cibadmin: CIB API call failed: Permission denied =#=#=#= End test: chris: Replace - modify attribute (deny overrides allow) - Insufficient privileges (4) =#=#=#= * Passed: cibadmin - chris: Replace - modify attribute (deny overrides allow) - + diff --git a/cts/cli/regression.cibadmin.exp b/cts/cli/regression.cibadmin.exp index 94d919f082e..0b009a88815 100644 --- a/cts/cli/regression.cibadmin.exp +++ b/cts/cli/regression.cibadmin.exp @@ -1,5 +1,5 @@ =#=#=#= Begin test: Validate CIB =#=#=#= - + @@ -17,7 +17,7 @@ =#=#=#= Current cib after: Validate CIB =#=#=#= - + @@ -38,7 +38,7 @@ * Passed: cibadmin - Validate CIB =#=#=#= Begin test: Validate CIB (XML) =#=#=#= - + @@ -59,7 +59,7 @@ =#=#=#= Current cib after: Validate CIB (XML) =#=#=#= - + @@ -79,12 +79,12 @@ =#=#=#= End test: Validate CIB (XML) - OK (0) =#=#=#= * Passed: cibadmin - Validate CIB (XML) =#=#=#= Begin test: Digest calculation =#=#=#= -9130d6f39c2b83bd032a00e76621508c +1f3492630cde57d6b7ee45de4d390469 =#=#=#= End test: Digest calculation - OK (0) =#=#=#= * Passed: cibadmin - Digest calculation =#=#=#= Begin test: Digest calculation (XML) =#=#=#= - + =#=#=#= End test: Digest calculation (XML) - OK (0) =#=#=#= @@ -92,7 +92,7 @@ =#=#=#= Begin test: Require --force for CIB erasure =#=#=#= cibadmin: The supplied command is considered dangerous. To prevent accidental destruction of the cluster, the --force flag is required in order to proceed. =#=#=#= Current cib after: Require --force for CIB erasure =#=#=#= - + @@ -120,7 +120,7 @@ cibadmin: The supplied command is considered dangerous. To prevent accidental de =#=#=#= Current cib after: Require --force for CIB erasure (XML) =#=#=#= - + @@ -149,7 +149,7 @@ cibadmin: The supplied command is considered dangerous. To prevent accidental de =#=#=#= End test: Allow CIB erasure with --force (XML) - OK (0) =#=#=#= * Passed: cibadmin - Allow CIB erasure with --force (XML) =#=#=#= Begin test: Query CIB =#=#=#= - + @@ -159,7 +159,7 @@ cibadmin: The supplied command is considered dangerous. To prevent accidental de =#=#=#= Current cib after: Query CIB =#=#=#= - + @@ -172,7 +172,7 @@ cibadmin: The supplied command is considered dangerous. To prevent accidental de * Passed: cibadmin - Query CIB =#=#=#= Begin test: Query CIB (XML) =#=#=#= - + @@ -185,7 +185,7 @@ cibadmin: The supplied command is considered dangerous. To prevent accidental de =#=#=#= Current cib after: Query CIB (XML) =#=#=#= - + diff --git a/cts/cli/regression.crm_attribute.exp b/cts/cli/regression.crm_attribute.exp index 882f5da2113..a9e69cec54c 100644 --- a/cts/cli/regression.crm_attribute.exp +++ b/cts/cli/regression.crm_attribute.exp @@ -1067,7 +1067,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: crm_attribute - Query the value of an attribute that does not exist =#=#=#= Begin test: Configure something before erasing =#=#=#= =#=#=#= Current cib after: Configure something before erasing =#=#=#= - + @@ -1084,7 +1084,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: crm_attribute - Configure something before erasing =#=#=#= Begin test: Test '++' XML attribute update syntax =#=#=#= =#=#=#= Current cib after: Test '++' XML attribute update syntax =#=#=#= - + @@ -1101,7 +1101,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: cibadmin - Test '++' XML attribute update syntax =#=#=#= Begin test: Test '+=' XML attribute update syntax =#=#=#= =#=#=#= Current cib after: Test '+=' XML attribute update syntax =#=#=#= - + @@ -1118,7 +1118,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: cibadmin - Test '+=' XML attribute update syntax =#=#=#= Begin test: Test '++' nvpair value update syntax =#=#=#= =#=#=#= Current cib after: Test '++' nvpair value update syntax =#=#=#= - + @@ -1138,7 +1138,7 @@ crm_attribute: Error performing operation: No such device or address =#=#=#= Current cib after: Test '++' nvpair value update syntax (XML) =#=#=#= - + @@ -1155,7 +1155,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: crm_attribute - Test '++' nvpair value update syntax (XML) =#=#=#= Begin test: Test '+=' nvpair value update syntax =#=#=#= =#=#=#= Current cib after: Test '+=' nvpair value update syntax =#=#=#= - + @@ -1175,7 +1175,7 @@ crm_attribute: Error performing operation: No such device or address =#=#=#= Current cib after: Test '+=' nvpair value update syntax (XML) =#=#=#= - + @@ -1192,7 +1192,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: crm_attribute - Test '+=' nvpair value update syntax (XML) =#=#=#= Begin test: Test '++' XML attribute update syntax (--score not set) =#=#=#= =#=#=#= Current cib after: Test '++' XML attribute update syntax (--score not set) =#=#=#= - + @@ -1209,7 +1209,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: cibadmin - Test '++' XML attribute update syntax (--score not set) =#=#=#= Begin test: Test '+=' XML attribute update syntax (--score not set) =#=#=#= =#=#=#= Current cib after: Test '+=' XML attribute update syntax (--score not set) =#=#=#= - + @@ -1226,7 +1226,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: cibadmin - Test '+=' XML attribute update syntax (--score not set) =#=#=#= Begin test: Test '++' nvpair value update syntax (--score not set) =#=#=#= =#=#=#= Current cib after: Test '++' nvpair value update syntax (--score not set) =#=#=#= - + @@ -1246,7 +1246,7 @@ crm_attribute: Error performing operation: No such device or address =#=#=#= Current cib after: Test '++' nvpair value update syntax (--score not set) (XML) =#=#=#= - + @@ -1263,7 +1263,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: crm_attribute - Test '++' nvpair value update syntax (--score not set) (XML) =#=#=#= Begin test: Test '+=' nvpair value update syntax (--score not set) =#=#=#= =#=#=#= Current cib after: Test '+=' nvpair value update syntax (--score not set) =#=#=#= - + @@ -1283,7 +1283,7 @@ crm_attribute: Error performing operation: No such device or address =#=#=#= Current cib after: Test '+=' nvpair value update syntax (--score not set) (XML) =#=#=#= - + @@ -1300,7 +1300,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: crm_attribute - Test '+=' nvpair value update syntax (--score not set) (XML) =#=#=#= Begin test: Set cluster option =#=#=#= =#=#=#= Current cib after: Set cluster option =#=#=#= - + @@ -1321,7 +1321,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: cibadmin - Query new cluster option =#=#=#= Begin test: Set no-quorum policy =#=#=#= =#=#=#= Current cib after: Set no-quorum policy =#=#=#= - + @@ -1339,7 +1339,7 @@ crm_attribute: Error performing operation: No such device or address * Passed: crm_attribute - Set no-quorum policy =#=#=#= Begin test: Delete nvpair =#=#=#= =#=#=#= Current cib after: Delete nvpair =#=#=#= - + @@ -1364,7 +1364,7 @@ cibadmin: CIB API call failed: File exists =#=#=#= Current cib after: Create operation should fail =#=#=#= - + @@ -1381,7 +1381,7 @@ cibadmin: CIB API call failed: File exists * Passed: cibadmin - Create operation should fail =#=#=#= Begin test: Modify cluster options section =#=#=#= =#=#=#= Current cib after: Modify cluster options section =#=#=#= - + @@ -1400,7 +1400,7 @@ cibadmin: CIB API call failed: File exists =#=#=#= Begin test: Query updated cluster option =#=#=#= =#=#=#= Current cib after: Query updated cluster option =#=#=#= - + @@ -1418,7 +1418,7 @@ cibadmin: CIB API call failed: File exists * Passed: cibadmin - Query updated cluster option =#=#=#= Begin test: Set duplicate cluster option =#=#=#= =#=#=#= Current cib after: Set duplicate cluster option =#=#=#= - + @@ -1443,7 +1443,7 @@ Multiple attributes match name=cluster-delay Value: 60s (id=cib-bootstrap-options-cluster-delay) Value: 40s (id=duplicate-cluster-delay) =#=#=#= Current cib after: Setting multiply defined cluster option should fail =#=#=#= - + @@ -1464,7 +1464,7 @@ Multiple attributes match name=cluster-delay * Passed: crm_attribute - Setting multiply defined cluster option should fail =#=#=#= Begin test: Set cluster option with -s =#=#=#= =#=#=#= Current cib after: Set cluster option with -s =#=#=#= - + @@ -1486,7 +1486,7 @@ Multiple attributes match name=cluster-delay =#=#=#= Begin test: Delete cluster option with -i =#=#=#= Deleted crm_config option: id=(null) name=cluster-delay =#=#=#= Current cib after: Delete cluster option with -i =#=#=#= - + @@ -1526,7 +1526,7 @@ Revised Cluster Status: * Full List of Resources: * No resources =#=#=#= Current cib after: Create node1 and bring it online =#=#=#= - + @@ -1550,7 +1550,7 @@ Revised Cluster Status: * Passed: crm_simulate - Create node1 and bring it online =#=#=#= Begin test: Create node attribute =#=#=#= =#=#=#= Current cib after: Create node attribute =#=#=#= - + @@ -1579,7 +1579,7 @@ Revised Cluster Status: =#=#=#= Begin test: Query new node attribute =#=#=#= =#=#=#= Current cib after: Query new node attribute =#=#=#= - + @@ -1607,7 +1607,7 @@ Revised Cluster Status: * Passed: cibadmin - Query new node attribute =#=#=#= Begin test: Create second node attribute =#=#=#= =#=#=#= Current cib after: Create second node attribute =#=#=#= - + @@ -1641,7 +1641,7 @@ scope=nodes name=rattr value=XYZ * Passed: crm_attribute - Query node attributes by pattern =#=#=#= Begin test: Update node attributes by pattern =#=#=#= =#=#=#= Current cib after: Update node attributes by pattern =#=#=#= - + @@ -1671,7 +1671,7 @@ scope=nodes name=rattr value=XYZ =#=#=#= Begin test: Delete node attributes by pattern =#=#=#= Deleted nodes attribute: id=nodes-node1-rattr name=rattr =#=#=#= Current cib after: Delete node attributes by pattern =#=#=#= - + @@ -1699,7 +1699,7 @@ Deleted nodes attribute: id=nodes-node1-rattr name=rattr * Passed: crm_attribute - Delete node attributes by pattern =#=#=#= Begin test: Set a transient (fail-count) node attribute =#=#=#= =#=#=#= Current cib after: Set a transient (fail-count) node attribute =#=#=#= - + @@ -1734,7 +1734,7 @@ Deleted nodes attribute: id=nodes-node1-rattr name=rattr =#=#=#= Begin test: Query a fail count =#=#=#= scope=status name=fail-count-foo value=3 =#=#=#= Current cib after: Query a fail count =#=#=#= - + @@ -1782,7 +1782,7 @@ Current cluster status: * Passed: crm_simulate - Show node attributes with crm_simulate =#=#=#= Begin test: Set a second transient node attribute =#=#=#= =#=#=#= Current cib after: Set a second transient node attribute =#=#=#= - + @@ -1822,7 +1822,7 @@ scope=status name=fail-count-bar value=5 * Passed: crm_attribute - Query transient node attributes by pattern =#=#=#= Begin test: Update transient node attributes by pattern =#=#=#= =#=#=#= Current cib after: Update transient node attributes by pattern =#=#=#= - + @@ -1859,7 +1859,7 @@ scope=status name=fail-count-bar value=5 Deleted status attribute: id=status-node1-fail-count-foo name=fail-count-foo Deleted status attribute: id=status-node1-fail-count-bar name=fail-count-bar =#=#=#= Current cib after: Delete transient node attributes by pattern =#=#=#= - + @@ -1895,7 +1895,7 @@ crm_attribute: Error: must specify attribute name or pattern to delete * Passed: crm_attribute - crm_attribute given invalid delete usage =#=#=#= Begin test: Set a utilization node attribute =#=#=#= =#=#=#= Current cib after: Set a utilization node attribute =#=#=#= - + diff --git a/cts/cli/regression.crm_resource.exp b/cts/cli/regression.crm_resource.exp index 394d5b933a8..1b8f728f653 100644 --- a/cts/cli/regression.crm_resource.exp +++ b/cts/cli/regression.crm_resource.exp @@ -824,7 +824,7 @@ Special parameters that are available for all fencing resources, regardless of t * Passed: crm_resource - List all available fencing parameters (XML) =#=#=#= Begin test: Create a resource =#=#=#= =#=#=#= Current cib after: Create a resource =#=#=#= - + @@ -857,7 +857,7 @@ crm_resource: --class, --agent, and --provider can only be used with --validate unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Set 'dummy' option: id=dummy-meta_attributes-is-managed set=dummy-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute =#=#=#= - + @@ -886,7 +886,7 @@ Set 'dummy' option: id=dummy-meta_attributes-is-managed set=dummy-meta_attribute unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. false =#=#=#= Current cib after: Query a resource meta attribute =#=#=#= - + @@ -915,7 +915,7 @@ false unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Deleted 'dummy' option: id=dummy-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Remove a resource meta attribute =#=#=#= - + @@ -984,7 +984,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Attribute 'nonexistent' not found for 'dummy' =#=#=#= Current cib after: Get a non-existent attribute from a resource element =#=#=#= - + @@ -1017,7 +1017,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h =#=#=#= Current cib after: Get a non-existent attribute from a resource element (XML) =#=#=#= - + @@ -1044,7 +1044,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. ocf =#=#=#= Current cib after: Get an existent attribute from a resource element =#=#=#= - + @@ -1073,7 +1073,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h =#=#=#= Current cib after: Set a non-existent attribute for a resource element (XML) =#=#=#= - + @@ -1102,7 +1102,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h =#=#=#= Current cib after: Set an existent attribute for a resource element (XML) =#=#=#= - + @@ -1131,7 +1131,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h =#=#=#= Current cib after: Delete an existent attribute for a resource element (XML) =#=#=#= - + @@ -1160,7 +1160,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h =#=#=#= Current cib after: Delete a non-existent attribute for a resource element (XML) =#=#=#= - + @@ -1187,7 +1187,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Set attribute: name=description value=test_description =#=#=#= Current cib after: Set a non-existent attribute for a resource element =#=#=#= - + @@ -1214,7 +1214,7 @@ Set attribute: name=description value=test_description unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Set attribute: name=description value=test_description =#=#=#= Current cib after: Set an existent attribute for a resource element =#=#=#= - + @@ -1241,7 +1241,7 @@ Set attribute: name=description value=test_description unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Deleted attribute: description =#=#=#= Current cib after: Delete an existent attribute for a resource element =#=#=#= - + @@ -1268,7 +1268,7 @@ Deleted attribute: description unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Deleted attribute: description =#=#=#= Current cib after: Delete a non-existent attribute for a resource element =#=#=#= - + @@ -1295,7 +1295,7 @@ Deleted attribute: description unpack_resources error: Resource start-up disabled since no fencing resources have been defined. Either configure some or disable fencing with the fencing-enabled option. NOTE: Clusters with shared data need fencing to ensure data integrity. Set 'dummy' option: id=dummy-instance_attributes-delay set=dummy-instance_attributes name=delay value=10s =#=#=#= Current cib after: Create a resource attribute =#=#=#= - + @@ -1326,7 +1326,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h Full List of Resources: * dummy (ocf:pacemaker:Dummy): Stopped =#=#=#= Current cib after: List the configured resources =#=#=#= - + @@ -1361,7 +1361,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h =#=#=#= Current cib after: List the configured resources (XML) =#=#=#= - + @@ -1432,7 +1432,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h crm_resource: Resource 'dummy' not moved: active in 0 locations. To prevent 'dummy' from running on a specific location, specify a node. =#=#=#= Current cib after: Require a destination when migrating a resource that is stopped =#=#=#= - + @@ -1463,7 +1463,7 @@ unpack_resources error: Resource start-up disabled since no fencing resources h crm_resource: Node 'i.do.not.exist' not found Error performing operation: No such object =#=#=#= Current cib after: Don't support migration to non-existent locations =#=#=#= - + @@ -1491,7 +1491,7 @@ Error performing operation: No such object * Passed: crm_resource - Don't support migration to non-existent locations =#=#=#= Begin test: Create a fencing resource =#=#=#= =#=#=#= Current cib after: Create a fencing resource =#=#=#= - + @@ -1545,7 +1545,7 @@ Revised Cluster Status: * dummy (ocf:pacemaker:Dummy): Started node1 * Fence (stonith:fence_true): Started node1 =#=#=#= Current cib after: Bring resources online =#=#=#= - + @@ -1586,7 +1586,7 @@ Revised Cluster Status: =#=#=#= Begin test: Try to move a resource to its existing location =#=#=#= crm_resource: Error performing operation: Requested item already exists =#=#=#= Current cib after: Try to move a resource to its existing location =#=#=#= - + @@ -1634,7 +1634,7 @@ WARNING: Creating rsc_location constraint 'cli-ban-dummy-on-node1' with a score This will prevent dummy from running on node1 until the constraint is removed using the clear option or by editing the CIB with an appropriate tool. This will be the case even if node1 is the last node in the cluster =#=#=#= Current cib after: Move a resource from its existing location =#=#=#= - + @@ -1677,7 +1677,7 @@ WARNING: Creating rsc_location constraint 'cli-ban-dummy-on-node1' with a score =#=#=#= Begin test: Clear out constraints generated by --move =#=#=#= Removing constraint: cli-ban-dummy-on-node1 =#=#=#= Current cib after: Clear out constraints generated by --move =#=#=#= - + @@ -1752,7 +1752,7 @@ Revised Cluster Status: * dummy (ocf:pacemaker:Dummy): Started node1 * Fence (stonith:fence_true): Started node2 =#=#=#= Current cib after: Create two more nodes and bring them online =#=#=#= - + @@ -1821,7 +1821,7 @@ WARNING: Creating rsc_location constraint 'cli-ban-dummy-on-node1' with a score This will prevent dummy from running on node1 until the constraint is removed using the clear option or by editing the CIB with an appropriate tool. This will be the case even if node1 is the last node in the cluster =#=#=#= Current cib after: Ban dummy from node1 =#=#=#= - + @@ -1901,7 +1901,7 @@ Locations: =#=#=#= Current cib after: Ban dummy from node2 (XML) =#=#=#= - + @@ -1992,7 +1992,7 @@ Revised Cluster Status: * dummy (ocf:pacemaker:Dummy): Started node3 * Fence (stonith:fence_true): Started node2 =#=#=#= Current cib after: Relocate resources due to ban =#=#=#= - + @@ -2064,7 +2064,7 @@ Revised Cluster Status: =#=#=#= Current cib after: Move dummy to node1 (XML) =#=#=#= - + @@ -2134,7 +2134,7 @@ Revised Cluster Status: =#=#=#= Begin test: Clear implicit constraints for dummy on node2 =#=#=#= Removing constraint: cli-ban-dummy-on-node2 =#=#=#= Current cib after: Clear implicit constraints for dummy on node2 =#=#=#= - + @@ -2210,7 +2210,7 @@ Removing constraint: cli-ban-dummy-on-node2 Performing update of 'is-managed' on 'test-clone', the parent of 'test-primitive' Set 'test-clone' option: id=test-clone-meta_attributes-is-managed set=test-clone-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute =#=#=#= - + @@ -2248,7 +2248,7 @@ Set 'test-clone' option: id=test-clone-meta_attributes-is-managed set=test-clone =#=#=#= Begin test: Create a resource meta attribute in the primitive =#=#=#= Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed set=test-primitive-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute in the primitive =#=#=#= - + @@ -2295,7 +2295,7 @@ Multiple attributes match name=is-managed A value for 'is-managed' already exists in child 'test-primitive', performing update on that instead of 'test-clone' Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed value=true =#=#=#= Current cib after: Update resource meta attribute with duplicates =#=#=#= - + @@ -2337,7 +2337,7 @@ Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=i =#=#=#= Begin test: Update resource meta attribute with duplicates (force clone) =#=#=#= Set 'test-clone' option: id=test-clone-meta_attributes-is-managed name=is-managed value=true =#=#=#= Current cib after: Update resource meta attribute with duplicates (force clone) =#=#=#= - + @@ -2383,7 +2383,7 @@ Multiple attributes match name=is-managed Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed value=false =#=#=#= Current cib after: Update child resource meta attribute with duplicates =#=#=#= - + @@ -2430,7 +2430,7 @@ Multiple attributes match name=is-managed A value for 'is-managed' already exists in child 'test-primitive', performing delete on that instead of 'test-clone' Deleted 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Delete resource meta attribute with duplicates =#=#=#= - + @@ -2471,7 +2471,7 @@ Deleted 'test-primitive' option: id=test-primitive-meta_attributes-is-managed na Performing delete of 'is-managed' on 'test-clone', the parent of 'test-primitive' Deleted 'test-clone' option: id=test-clone-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Delete resource meta attribute in parent =#=#=#= - + @@ -2509,7 +2509,7 @@ Deleted 'test-clone' option: id=test-clone-meta_attributes-is-managed name=is-ma =#=#=#= Begin test: Create a resource meta attribute in the primitive =#=#=#= Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed set=test-primitive-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute in the primitive =#=#=#= - + @@ -2550,7 +2550,7 @@ Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed set=te A value for 'is-managed' already exists in child 'test-primitive', performing update on that instead of 'test-clone' Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed value=true =#=#=#= Current cib after: Update existing resource meta attribute =#=#=#= - + @@ -2590,7 +2590,7 @@ Set 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=i =#=#=#= Begin test: Create a resource meta attribute in the parent =#=#=#= Set 'test-clone' option: id=test-clone-meta_attributes-is-managed set=test-clone-meta_attributes name=is-managed value=true =#=#=#= Current cib after: Create a resource meta attribute in the parent =#=#=#= - + @@ -2632,7 +2632,7 @@ Set 'test-clone' option: id=test-clone-meta_attributes-is-managed set=test-clone =#=#=#= Begin test: Delete resource parent meta attribute (force) =#=#=#= Deleted 'test-clone' option: id=test-clone-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Delete resource parent meta attribute (force) =#=#=#= - + @@ -2676,7 +2676,7 @@ Multiple attributes match name=is-managed Deleted 'test-primitive' option: id=test-primitive-meta_attributes-is-managed name=is-managed =#=#=#= Current cib after: Delete resource child meta attribute =#=#=#= - + @@ -2715,7 +2715,7 @@ Deleted 'test-primitive' option: id=test-primitive-meta_attributes-is-managed na * Passed: crm_resource - Delete resource child meta attribute =#=#=#= Begin test: Create the dummy-group resource group =#=#=#= =#=#=#= Current cib after: Create the dummy-group resource group =#=#=#= - + @@ -2759,7 +2759,7 @@ Deleted 'test-primitive' option: id=test-primitive-meta_attributes-is-managed na =#=#=#= Begin test: Create a resource meta attribute in dummy1 =#=#=#= Set 'dummy1' option: id=dummy1-meta_attributes-is-managed set=dummy1-meta_attributes name=is-managed value=true =#=#=#= Current cib after: Create a resource meta attribute in dummy1 =#=#=#= - + @@ -2808,7 +2808,7 @@ Set 'dummy1' option: id=dummy1-meta_attributes-is-managed set=dummy1-meta_attrib Set 'dummy1' option: id=dummy1-meta_attributes-is-managed name=is-managed value=false Set 'dummy-group' option: id=dummy-group-meta_attributes-is-managed set=dummy-group-meta_attributes name=is-managed value=false =#=#=#= Current cib after: Create a resource meta attribute in dummy-group =#=#=#= - + @@ -2858,7 +2858,7 @@ Set 'dummy-group' option: id=dummy-group-meta_attributes-is-managed set=dummy-gr * Passed: crm_resource - Create a resource meta attribute in dummy-group =#=#=#= Begin test: Delete the dummy-group resource group =#=#=#= =#=#=#= Current cib after: Delete the dummy-group resource group =#=#=#= - + @@ -2898,7 +2898,7 @@ Set 'dummy-group' option: id=dummy-group-meta_attributes-is-managed set=dummy-gr =#=#=#= Begin test: Specify a lifetime when moving a resource =#=#=#= Migration will take effect until: =#=#=#= Current cib after: Specify a lifetime when moving a resource =#=#=#= - + @@ -2942,7 +2942,7 @@ Migration will take effect until: * Passed: crm_resource - Specify a lifetime when moving a resource =#=#=#= Begin test: Try to move a resource previously moved with a lifetime =#=#=#= =#=#=#= Current cib after: Try to move a resource previously moved with a lifetime =#=#=#= - + @@ -2985,7 +2985,7 @@ WARNING: Creating rsc_location constraint 'cli-ban-dummy-on-node1' with a score This will prevent dummy from running on node1 until the constraint is removed using the clear option or by editing the CIB with an appropriate tool. This will be the case even if node1 is the last node in the cluster =#=#=#= Current cib after: Ban dummy from node1 for a short time =#=#=#= - + @@ -3031,7 +3031,7 @@ WARNING: Creating rsc_location constraint 'cli-ban-dummy-on-node1' with a score =#=#=#= Begin test: Remove expired constraints =#=#=#= Removing constraint: cli-ban-dummy-on-node1 =#=#=#= Current cib after: Remove expired constraints =#=#=#= - + @@ -3071,7 +3071,7 @@ Removing constraint: cli-ban-dummy-on-node1 =#=#=#= Begin test: Clear all implicit constraints for dummy =#=#=#= Removing constraint: cli-prefer-dummy =#=#=#= Current cib after: Clear all implicit constraints for dummy =#=#=#= - + @@ -3108,7 +3108,7 @@ Removing constraint: cli-prefer-dummy * Passed: crm_resource - Clear all implicit constraints for dummy =#=#=#= Begin test: Set a node health strategy =#=#=#= =#=#=#= Current cib after: Set a node health strategy =#=#=#= - + @@ -3146,7 +3146,7 @@ Removing constraint: cli-prefer-dummy * Passed: crm_attribute - Set a node health strategy =#=#=#= Begin test: Set a node health attribute =#=#=#= =#=#=#= Current cib after: Set a node health attribute =#=#=#= - + @@ -3197,7 +3197,7 @@ Removing constraint: cli-prefer-dummy * Passed: crm_resource - Show why a resource is not running on an unhealthy node (XML) =#=#=#= Begin test: Delete a resource =#=#=#= =#=#=#= Current cib after: Delete a resource =#=#=#= - + diff --git a/cts/cli/regression.crm_shadow.exp b/cts/cli/regression.crm_shadow.exp index f8a733743db..84fae0fdf17 100644 --- a/cts/cli/regression.crm_shadow.exp +++ b/cts/cli/regression.crm_shadow.exp @@ -743,7 +743,7 @@ cts-cli * Passed: crm_shadow - Get active shadow instance's diff (copied) (XML) =#=#=#= Begin test: Get active shadow instance's diff (after changes) =#=#=#= Diff: --- 1.1.173 2 -Diff: +++ 1.4.1 (null) +Diff: +++ 1.4.1 (no digest) -- /cib/configuration/op_defaults + /cib: @epoch=4, @num_updates=1 + /cib/configuration/resources/primitive[@id='dummy']: @description=desc @@ -800,7 +800,7 @@ To prevent accidental destruction of the cluster, the --force flag is required i * Passed: crm_shadow - Commit shadow instance (force) =#=#=#= Begin test: Get active shadow instance's diff (after commit) =#=#=#= Diff: --- 1.2.0 2 -Diff: +++ 1.4.1 (null) +Diff: +++ 1.4.1 (no digest) + /cib: @epoch=4, @num_updates=1 ++ /cib/status: =#=#=#= End test: Get active shadow instance's diff (after commit) - Error occurred (1) =#=#=#= @@ -810,7 +810,7 @@ Diff: +++ 1.4.1 (null) * Passed: crm_shadow - Commit shadow instance (force) (all) =#=#=#= Begin test: Get active shadow instance's diff (after commit all) =#=#=#= Diff: --- 1.4.2 2 -Diff: +++ 1.4.1 (null) +Diff: +++ 1.4.1 (no digest) + /cib: @num_updates=1 =#=#=#= End test: Get active shadow instance's diff (after commit all) - Error occurred (1) =#=#=#= * Passed: crm_shadow - Get active shadow instance's diff (after commit all) @@ -1322,7 +1322,7 @@ A new shadow instance was created. To begin using it, enter the following into y =#=#=#= End test: Create empty shadow instance (file already exists) (force) (XML) - OK (0) =#=#=#= * Passed: crm_shadow - Create empty shadow instance (file already exists) (force) (XML) =#=#=#= Begin test: Get active shadow instance's contents (empty CIB) =#=#=#= - + @@ -1336,7 +1336,7 @@ A new shadow instance was created. To begin using it, enter the following into y =#=#=#= Begin test: Get active shadow instance's contents (empty CIB) (XML) =#=#=#= - + @@ -1353,7 +1353,7 @@ A new shadow instance was created. To begin using it, enter the following into y * Passed: crm_shadow - Get active shadow instance's contents (empty CIB) (XML) =#=#=#= Begin test: Get active shadow instance's diff (empty CIB) =#=#=#= Diff: --- 1.1.173 2 -Diff: +++ 0.1.0 (null) +Diff: +++ 0.1.0 (no digest) -- /cib/configuration/crm_config/cluster_property_set[@id='cib-bootstrap-options'] -- /cib/configuration/nodes/node[@id='1'] -- /cib/configuration/nodes/node[@id='2'] @@ -1374,7 +1374,7 @@ Diff: +++ 0.1.0 (null) -- /cib/status/node_state[@id='1'] -- /cib/status/node_state[@id='httpd-bundle-0'] -- /cib/status/node_state[@id='httpd-bundle-1'] -+ /cib: @validate-with=pacemaker-X, @num_updates=0, @admin_epoch=0 ++ /cib: @validate-with=pacemaker-X, @admin_epoch=0, @epoch=1, @num_updates=0 -- /cib: @cib-last-written, @update-origin, @update-client, @update-user, @have-quorum, @dc-uuid =#=#=#= End test: Get active shadow instance's diff (empty CIB) - Error occurred (1) =#=#=#= * Passed: crm_shadow - Get active shadow instance's diff (empty CIB) @@ -1410,8 +1410,9 @@ Diff: +++ 0.1.0 (null) - + + @@ -1420,7 +1421,7 @@ Diff: +++ 0.1.0 (null) - + @@ -1445,7 +1446,7 @@ A new shadow instance was created. To begin using it, enter the following into y * Passed: crm_shadow - Active shadow instance no different from active CIB after reset =#=#=#= Begin test: Active shadow instance differs from active CIB after change =#=#=#= Diff: --- 1.1.173 2 -Diff: +++ 1.2.0 (null) +Diff: +++ 1.2.0 (no digest) + /cib: @epoch=2, @num_updates=0 ++ /cib/configuration/crm_config/cluster_property_set[@id='cib-bootstrap-options']: =#=#=#= End test: Active shadow instance differs from active CIB after change - Error occurred (1) =#=#=#= diff --git a/cts/cli/regression.crm_standby.exp b/cts/cli/regression.crm_standby.exp index ef406bb72be..5dce954c75d 100644 --- a/cts/cli/regression.crm_standby.exp +++ b/cts/cli/regression.crm_standby.exp @@ -4,7 +4,7 @@ scope=status name=standby value=off * Passed: crm_standby - Default standby value =#=#=#= Begin test: Set standby status =#=#=#= =#=#=#= Current cib after: Set standby status =#=#=#= - + @@ -28,7 +28,7 @@ scope=nodes name=standby value=true =#=#=#= Begin test: Delete standby value =#=#=#= Deleted nodes attribute: id=nodes-node1-standby name=standby =#=#=#= Current cib after: Delete standby value =#=#=#= - + diff --git a/cts/cli/regression.crm_ticket.exp b/cts/cli/regression.crm_ticket.exp index afb8be30794..1a30cb97190 100644 --- a/cts/cli/regression.crm_ticket.exp +++ b/cts/cli/regression.crm_ticket.exp @@ -4,7 +4,7 @@ false * Passed: crm_ticket - Default ticket granted state =#=#=#= Begin test: Set ticket granted state =#=#=#= =#=#=#= Current cib after: Set ticket granted state =#=#=#= - + @@ -70,7 +70,7 @@ false * Passed: crm_ticket - Query ticket granted state (XML) =#=#=#= Begin test: Delete ticket granted state =#=#=#= =#=#=#= Current cib after: Delete ticket granted state =#=#=#= - + @@ -93,7 +93,7 @@ false * Passed: crm_ticket - Delete ticket granted state =#=#=#= Begin test: Make a ticket standby =#=#=#= =#=#=#= Current cib after: Make a ticket standby =#=#=#= - + @@ -120,7 +120,7 @@ true * Passed: crm_ticket - Query ticket standby state =#=#=#= Begin test: Activate a ticket =#=#=#= =#=#=#= Current cib after: Activate a ticket =#=#=#= - + @@ -157,7 +157,7 @@ ticketA revoked (standby=false) =#=#=#= Begin test: Add a second ticket =#=#=#= false =#=#=#= Current cib after: Add a second ticket =#=#=#= - + @@ -180,7 +180,7 @@ false * Passed: crm_ticket - Add a second ticket =#=#=#= Begin test: Set second ticket granted state =#=#=#= =#=#=#= Current cib after: Set second ticket granted state =#=#=#= - + @@ -219,7 +219,7 @@ ticketB revoked * Passed: crm_ticket - List tickets (XML) =#=#=#= Begin test: Delete second ticket =#=#=#= =#=#=#= Current cib after: Delete second ticket =#=#=#= - + @@ -242,7 +242,7 @@ ticketB revoked * Passed: cibadmin - Delete second ticket =#=#=#= Begin test: Delete ticket standby state =#=#=#= =#=#=#= Current cib after: Delete ticket standby state =#=#=#= - + @@ -265,7 +265,7 @@ ticketB revoked * Passed: crm_ticket - Delete ticket standby state =#=#=#= Begin test: Add a constraint to a ticket =#=#=#= =#=#=#= Current cib after: Add a constraint to a ticket =#=#=#= - + @@ -312,7 +312,7 @@ Constraints XML: * Passed: crm_ticket - Query ticket constraints (XML) =#=#=#= Begin test: Delete ticket constraint =#=#=#= =#=#=#= Current cib after: Delete ticket constraint =#=#=#= - + diff --git a/cts/cli/regression.upgrade.exp b/cts/cli/regression.upgrade.exp index b4e69f6195a..3f3455a8e82 100644 --- a/cts/cli/regression.upgrade.exp +++ b/cts/cli/regression.upgrade.exp @@ -1,6 +1,6 @@ =#=#=#= Begin test: Set fencing-enabled=false =#=#=#= =#=#=#= Current cib after: Set fencing-enabled=false =#=#=#= - + @@ -17,7 +17,7 @@ * Passed: crm_attribute - Set fencing-enabled=false =#=#=#= Begin test: Configure the initial resource =#=#=#= =#=#=#= Current cib after: Configure the initial resource =#=#=#= - + @@ -78,7 +78,7 @@ pcmk__update_schema debug: Schema pacemaker-3.10 validates pcmk__update_schema debug: Schema pacemaker-4.0 validates pcmk__update_schema info: Transformed the configuration schema to pacemaker-4.0 =#=#=#= Current cib after: Upgrade to latest CIB schema (trigger 2.10.xsl + the wrapping) =#=#=#= - + @@ -111,7 +111,7 @@ pcmk__update_schema info: Transformed the configuration schema to pacemaker-4.0 =#=#=#= Begin test: Query a resource instance attribute (shall survive) =#=#=#= outputpower =#=#=#= Current cib after: Query a resource instance attribute (shall survive) =#=#=#= - + diff --git a/cts/cli/regression.validity.exp b/cts/cli/regression.validity.exp index 03b61cee5b3..fd566608b3f 100644 --- a/cts/cli/regression.validity.exp +++ b/cts/cli/regression.validity.exp @@ -9,7 +9,7 @@ cibadmin: CIB API call failed: Update does not conform to the configured schema =#=#=#= Begin test: Try to use rsc_order first-action value disallowed by schema =#=#=#= cibadmin: CIB API call failed: Update does not conform to the configured schema =#=#=#= Current cib after: Try to use rsc_order first-action value disallowed by schema =#=#=#= - + @@ -28,7 +28,7 @@ cibadmin: CIB API call failed: Update does not conform to the configured schema =#=#=#= Begin test: Try to use configuration legal only with schema after configured one =#=#=#= cibadmin: CIB API call failed: Update does not conform to the configured schema =#=#=#= Current cib after: Try to use configuration legal only with schema after configured one =#=#=#= - + @@ -49,7 +49,7 @@ cibadmin: CIB API call failed: Update does not conform to the configured schema * Passed: cibadmin - Disable schema validation =#=#=#= Begin test: Set invalid rsc_order first-action value (schema validation disabled) =#=#=#= =#=#=#= Current cib after: Set invalid rsc_order first-action value (schema validation disabled) =#=#=#= - + diff --git a/cts/cts-cli.in b/cts/cts-cli.in index 6f2422b3587..eadb6a69ba1 100644 --- a/cts/cts-cli.in +++ b/cts/cts-cli.in @@ -193,9 +193,9 @@ def reset_shadow_cib_version(): """Set various version numbers in a shadow CIB file back to 0.""" with fileinput.input(files=[shadow_path()], inplace=True) as f: for line in f: - line = re.sub('epoch="[0-9]*"', 'epoch="1"', line) - line = re.sub('num_updates="[0-9]*"', 'num_updates="0"', line) - line = re.sub('admin_epoch="[0-9]*"', 'admin_epoch="0"', line) + line = re.sub(r'\badmin_epoch="[0-9]*"', 'admin_epoch="0"', line) + line = re.sub(r'\bepoch="[0-9]*"', 'epoch="1"', line) + line = re.sub(r'\bnum_updates="[0-9]*"', 'num_updates="0"', line) print(line, end='') @@ -1417,7 +1417,7 @@ class CrmAttributeRegressionTest(RegressionTest): "crm_attribute --query -n cpu -N node1 -z"), # This update will fail because it has version numbers Test("Replace operation should fail", - """cibadmin -Q | sed -e 's/epoch="[^"]*"/epoch="1"/' | cibadmin -R -p""", + """cibadmin -Q | sed -e 's/ epoch="[^"]*"/ epoch="1"/' | cibadmin -R -p""", expected_rc=ExitStatus.OLD), ] @@ -2346,7 +2346,7 @@ class CrmSimulateRegressionTest(RegressionTest): no_version_cib = good_cib.replace('validate-with="pacemaker-1.2" ', "") - no_version_bad_cib = bad_version_cib.replace('epoch="3"', 'epoch="30"').replace("start", "break") + no_version_bad_cib = re.sub(r'\bepoch="3"', 'epoch="30"', bad_version_cib).replace("start", "break") basic_tests = [ Test("Show allocation scores with crm_simulate", diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c index 34aa7cd1936..a7f798cd1a8 100644 --- a/daemons/attrd/attrd_corosync.c +++ b/daemons/attrd/attrd_corosync.c @@ -489,6 +489,12 @@ broadcast_unseen_local_values(void) } } +/*! + * \internal + * \brief Initialize \c attrd_cluster and connect to the cluster layer + * + * \return Standard Pacemaker return code + */ int attrd_cluster_connect(void) { @@ -514,11 +520,19 @@ attrd_cluster_connect(void) return rc; } +/*! + * \internal + * \brief Disconnect from the cluster layer and free \c attrd_cluster + */ void attrd_cluster_disconnect(void) { + if (attrd_cluster == NULL) { + return; + } + pcmk_cluster_disconnect(attrd_cluster); - pcmk_cluster_free(attrd_cluster); + g_clear_pointer(&attrd_cluster, pcmk_cluster_free); } void diff --git a/daemons/attrd/attrd_utils.c b/daemons/attrd/attrd_utils.c index 8ba5bc43b20..951a344da4e 100644 --- a/daemons/attrd/attrd_utils.c +++ b/daemons/attrd/attrd_utils.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -57,14 +57,6 @@ attrd_shutdown(int nsig) // Tell various functions not to do anthing shutting_down = true; - // Don't respond to signals while shutting down - mainloop_destroy_signal(SIGTERM); - mainloop_destroy_signal(SIGCHLD); - mainloop_destroy_signal(SIGPIPE); - mainloop_destroy_signal(SIGUSR1); - mainloop_destroy_signal(SIGUSR2); - mainloop_destroy_signal(SIGTRAP); - attrd_free_waitlist(); attrd_free_confirmations(); @@ -73,15 +65,11 @@ attrd_shutdown(int nsig) peer_protocol_vers = NULL; } - if ((mloop == NULL) || !g_main_loop_is_running(mloop)) { - /* If there's no main loop active, just exit. This should be possible - * only if we get SIGTERM in brief windows at start-up and shutdown. - */ - crm_exit(CRM_EX_OK); - } else { - g_main_loop_quit(mloop); - g_main_loop_unref(mloop); - } + // There should be no way to get here without the main loop running + CRM_CHECK((mloop != NULL) && g_main_loop_is_running(mloop), + crm_exit(CRM_EX_OK)); + + g_main_loop_quit(mloop); } /*! @@ -102,6 +90,7 @@ void attrd_run_mainloop(void) { g_main_loop_run(mloop); + g_clear_pointer(&mloop, g_main_loop_unref); } /* strlen("value") */ diff --git a/daemons/based/Makefile.am b/daemons/based/Makefile.am index aee006cd118..28f7681931b 100644 --- a/daemons/based/Makefile.am +++ b/daemons/based/Makefile.am @@ -15,7 +15,9 @@ halibdir = $(CRM_DAEMON_DIR) halib_PROGRAMS = pacemaker-based noinst_HEADERS = based_callbacks.h +noinst_HEADERS += based_corosync.h noinst_HEADERS += based_io.h +noinst_HEADERS += based_ipc.h noinst_HEADERS += based_messages.h noinst_HEADERS += based_notify.h noinst_HEADERS += based_operation.h @@ -33,6 +35,7 @@ pacemaker_based_LDADD += $(CLUSTERLIBS) $(PAM_LIBS) pacemaker_based_SOURCES = pacemaker-based.c pacemaker_based_SOURCES += based_callbacks.c +pacemaker_based_SOURCES += based_corosync.c pacemaker_based_SOURCES += based_io.c pacemaker_based_SOURCES += based_ipc.c pacemaker_based_SOURCES += based_messages.c diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index 346841ca44a..4c7ae1cf56b 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -10,27 +10,20 @@ #include #include // EACCES, ECONNREFUSED -#include // PRIu64 #include #include // NULL, size_t #include // uint32_t, uint64_t #include // free #include // LOG_INFO, LOG_DEBUG #include // time_t -#include // close #include // gboolean, gpointer, g_*, etc. #include // xmlNode -#include // xmlXPath* -#include // QB_FALSE -#include // qb_ipcs_connection_t #include // LOG_TRACE #include // cib_call_options values #include // cib__* -#include // pcmk_cluster_disconnect #include // pcmk__cluster_send_message -#include // pcmk_find_cib_element #include // pcmk__s, pcmk__str_eq #include // crm_ipc_*, pcmk_ipc_* #include // CRM_LOG_ASSERT, CRM_CHECK @@ -41,22 +34,102 @@ #include "pacemaker-based.h" -#define EXIT_ESCALATION_MS 10000 - -static uint64_t ping_seq = 0; +static mainloop_timer_t *digest_timer = NULL; +static long long ping_seq = 0; static char *ping_digest = NULL; static bool ping_modified_since = false; +/*! + * \internal + * \brief Request CIB digests from all peer nodes + * + * This is used as a callback that runs 5 seconds after we modify the CIB on the + * DC. It sends a ping request to all cluster nodes. They will respond by + * sending their current digests and version info, which we will validate in + * process_ping_reply(). If their digest doesn't match, we'll sync our own CIB + * to them. This helps ensure consistency across the cluster after a CIB update. + * + * \param[in] data Ignored + * + * \return \c G_SOURCE_REMOVE (to destroy the timeout) + * + * \note It's not clear why we wait 5 seconds rather than sending the ping + * request immediately after a performing a modifying op. Perhaps it's to + * avoid overwhelming other nodes with ping requests when there are a lot + * of modifying requests in a short period. The timer restarts after + * every successful modifying op, so we send ping requests **at most** + * every 5 seconds. Or perhaps it's a remnant of legacy mode (pre-1.1.12). + * In any case, the other nodes shouldn't need time to process the + * modifying op before responding to the ping request. The ping request is + * sent after the op is sent, so it should also be received after the op + * is received. + */ +static gboolean +digest_timer_cb(gpointer data) +{ + xmlNode *ping = NULL; + + if (based_shutting_down()) { + return G_SOURCE_REMOVE; + } + + if (!based_get_local_node_dc()) { + // Only the DC sends a ping + return G_SOURCE_REMOVE; + } + + if (++ping_seq < 0) { + ping_seq = 0; + } + + g_clear_pointer(&ping_digest, free); + ping_modified_since = false; + + ping = pcmk__xe_create(NULL, PCMK__XE_PING); + pcmk__xe_set(ping, PCMK__XA_T, PCMK__VALUE_CIB); + pcmk__xe_set(ping, PCMK__XA_CIB_OP, CRM_OP_PING); + pcmk__xe_set_ll(ping, PCMK__XA_CIB_PING_ID, ping_seq); + pcmk__xe_set(ping, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); + + pcmk__trace("Requesting peer digests (%lld)", ping_seq); + pcmk__cluster_send_message(NULL, pcmk_ipc_based, ping); + + pcmk__xml_free(ping); + return G_SOURCE_REMOVE; +} + +/*! + * \internal + * \brief Initialize data structures used for CIB manager callbacks + */ +void +based_callbacks_init(void) +{ + if (digest_timer == NULL) { + digest_timer = mainloop_timer_add("based_digest_timer", 5000, false, + digest_timer_cb, NULL); + } +} + +/*! + * \internal + * \brief Free data structures used for CIB manager callbacks + */ +void +based_callbacks_cleanup(void) +{ + g_clear_pointer(&digest_timer, mainloop_timer_del); + g_clear_pointer(&ping_digest, free); +} + /*! * \internal * \brief Create reply XML for a CIB request * - * \param[in] op CIB operation type - * \param[in] call_id CIB call ID - * \param[in] client_id CIB client ID - * \param[in] call_options Group of enum cib_call_options flags - * \param[in] rc Request return code - * \param[in] call_data Request output data + * \param[in] request CIB request + * \param[in] rc Request return code (standard Pacemaker return code) + * \param[in] call_data Request output data (may be entire live CIB or result + * CIB in case of error) * * \return Reply XML (guaranteed not to be \c NULL) * @@ -64,24 +137,30 @@ static bool ping_modified_since = false; * \p pcmk__xml_free(). */ static xmlNode * -create_cib_reply(const char *op, const char *call_id, const char *client_id, - uint32_t call_options, int rc, xmlNode *call_data) +create_cib_reply(const xmlNode *request, int rc, xmlNode *call_data) { xmlNode *reply = pcmk__xe_create(NULL, PCMK__XE_CIB_REPLY); pcmk__xe_set(reply, PCMK__XA_T, PCMK__VALUE_CIB); - pcmk__xe_set(reply, PCMK__XA_CIB_OP, op); - pcmk__xe_set(reply, PCMK__XA_CIB_CALLID, call_id); - pcmk__xe_set(reply, PCMK__XA_CIB_CLIENTID, client_id); - pcmk__xe_set_int(reply, PCMK__XA_CIB_CALLOPT, call_options); - pcmk__xe_set_int(reply, PCMK__XA_CIB_RC, rc); - if (call_data != NULL) { - xmlNode *wrapper = pcmk__xe_create(reply, PCMK__XE_CIB_CALLDATA); + /* We could simplify by copying all attributes from request. We would just + * have to ensure that there are never "private" attributes that we want to + * hide from external clients with notify callbacks. + */ + pcmk__xe_set(reply, PCMK__XA_CIB_OP, + pcmk__xe_get(request, PCMK__XA_CIB_OP)); + + pcmk__xe_set(reply, PCMK__XA_CIB_CALLID, + pcmk__xe_get(request, PCMK__XA_CIB_CALLID)); - pcmk__trace("Attaching reply output"); - pcmk__xml_copy(wrapper, call_data); - } + pcmk__xe_set(reply, PCMK__XA_CIB_CLIENTID, + pcmk__xe_get(request, PCMK__XA_CIB_CLIENTID)); + + pcmk__xe_set(reply, PCMK__XA_CIB_CALLOPT, + pcmk__xe_get(request, PCMK__XA_CIB_CALLOPT)); + + pcmk__xe_set_int(reply, PCMK__XA_CIB_RC, pcmk_rc2legacy(rc)); + cib__set_calldata(reply, call_data); crm_log_xml_explicit(reply, "cib:reply"); return reply; @@ -154,185 +233,92 @@ do_local_notify(const xmlNode *xml, const char *client_id, bool sync_reply, /*! * \internal - * \brief Request CIB digests from all peer nodes + * \brief Process a reply to a \c CRM_OP_PING request * - * This is used as a callback that runs 5 seconds after we modify the CIB. It - * sends a ping request to all cluster nodes. They will respond by sending their - * current digests and version info, which we will validate in - * process_ping_reply(). This serves as a check of consistency across the - * cluster after a CIB update. + * See \c digest_timer_cb() for details on how the ping process works, and see + * \c based_process_ping() for the construction of the ping reply. * - * \param[in] data Ignored + * We ignore the reply if we are no longer the DC, if the reply is malformed or + * received out of sequence, or if we may have modified the CIB since the last + * time we sent a ping request. * - * \return \c G_SOURCE_REMOVE (to destroy the timeout) + * Otherwise, we compare the CIB digest received in the reply against the digest + * of the local CIB. If the digests don't match, we sync our CIB to the node + * that sent the reply. This helps to ensure that all other nodes' views of the + * CIB eventually match the DC's view of the CIB. + * + * \param[in] reply Ping reply */ -static gboolean -digest_timer_cb(gpointer data) -{ - char *buf = NULL; - xmlNode *ping = NULL; - - if (!based_is_primary) { - return G_SOURCE_REMOVE; - } - - ping_seq++; - g_clear_pointer(&ping_digest, free); - ping_modified_since = false; - - buf = pcmk__assert_asprintf("%" PRIu64, ping_seq); - - ping = pcmk__xe_create(NULL, PCMK__XE_PING); - pcmk__xe_set(ping, PCMK__XA_T, PCMK__VALUE_CIB); - pcmk__xe_set(ping, PCMK__XA_CIB_OP, CRM_OP_PING); - pcmk__xe_set(ping, PCMK__XA_CIB_PING_ID, buf); - pcmk__xe_set(ping, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); - - pcmk__trace("Requesting peer digests (%s)", buf); - pcmk__cluster_send_message(NULL, pcmk_ipc_based, ping); - - free(buf); - pcmk__xml_free(ping); - return G_SOURCE_REMOVE; -} - static void -process_ping_reply(xmlNode *reply) +process_ping_reply(const xmlNode *reply) { - uint64_t seq = 0; const char *host = pcmk__xe_get(reply, PCMK__XA_SRC); - xmlNode *wrapper = pcmk__xe_first_child(reply, PCMK__XE_CIB_CALLDATA, NULL, - NULL); - xmlNode *pong = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); - - const char *seq_s = pcmk__xe_get(pong, PCMK__XA_CIB_PING_ID); + xmlNode *pong = cib__get_calldata(reply); + long long seq = 0; const char *digest = pcmk__xe_get(pong, PCMK_XA_DIGEST); - if (seq_s == NULL) { - pcmk__debug("Ignoring ping reply with no " PCMK__XA_CIB_PING_ID); + xmlNode *remote_versions = cib__get_calldata(pong); + + int rc = pcmk__xe_get_ll(pong, PCMK__XA_CIB_PING_ID, &seq); + + if (rc != pcmk_rc_ok) { + pcmk__debug("Ignoring ping reply with unset or invalid " + PCMK__XA_CIB_PING_ID ": %s", pcmk_rc_str(rc)); return; + } - } else { - long long seq_ll; - int rc = pcmk__scan_ll(seq_s, &seq_ll, 0LL); - - if (rc != pcmk_rc_ok) { - pcmk__debug("Ignoring ping reply with invalid " PCMK__XA_CIB_PING_ID - " '%s': %s", - seq_s, pcmk_rc_str(rc)); - return; - } - seq = (uint64_t) seq_ll; + if (!based_get_local_node_dc()) { + pcmk__trace("Ignoring ping reply %lld from %s because we are no longer " + "DC", seq, host); + return; } - if(digest == NULL) { - pcmk__trace("Ignoring ping reply %s from %s with no digest", seq_s, + if (digest == NULL) { + pcmk__trace("Ignoring ping reply %lld from %s with no digest", seq, host); + return; + } - } else if(seq != ping_seq) { - pcmk__trace("Ignoring out of sequence ping reply %s from %s", seq_s, + if (seq != ping_seq) { + pcmk__trace("Ignoring out-of-sequence ping reply %lld from %s", seq, host); + return; + } - } else if(ping_modified_since) { - pcmk__trace("Ignoring ping reply %s from %s: cib updated since", seq_s, + if (ping_modified_since) { + pcmk__trace("Ignoring ping reply %lld from %s: CIB updated since", seq, host); - - } else { - if(ping_digest == NULL) { - pcmk__trace("Calculating new digest"); - ping_digest = pcmk__digest_xml(the_cib, true); - } - - pcmk__trace("Processing ping reply %s from %s (%s)", seq_s, host, - digest); - if (!pcmk__str_eq(ping_digest, digest, pcmk__str_casei)) { - xmlNode *wrapper = pcmk__xe_first_child(pong, PCMK__XE_CIB_CALLDATA, - NULL, NULL); - xmlNode *remote_cib = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); - - const char *admin_epoch_s = NULL; - const char *epoch_s = NULL; - const char *num_updates_s = NULL; - - if (remote_cib != NULL) { - admin_epoch_s = pcmk__xe_get(remote_cib, PCMK_XA_ADMIN_EPOCH); - epoch_s = pcmk__xe_get(remote_cib, PCMK_XA_EPOCH); - num_updates_s = pcmk__xe_get(remote_cib, PCMK_XA_NUM_UPDATES); - } - - pcmk__notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s " - "%p", - pcmk__xe_get(the_cib, PCMK_XA_ADMIN_EPOCH), - pcmk__xe_get(the_cib, PCMK_XA_EPOCH), - pcmk__xe_get(the_cib, PCMK_XA_NUM_UPDATES), - ping_digest, host, - pcmk__s(admin_epoch_s, "_"), - pcmk__s(epoch_s, "_"), - pcmk__s(num_updates_s, "_"), - digest, remote_cib); - - if(remote_cib && remote_cib->children) { - // Additional debug - pcmk__xml_mark_changes(the_cib, remote_cib); - pcmk__log_xml_changes(LOG_INFO, remote_cib); - pcmk__trace("End of differences"); - } - - pcmk__xml_free(remote_cib); - sync_our_cib(reply, false); - } + return; } -} -static void -parse_local_options(const pcmk__client_t *client, - const cib__operation_t *operation, - const char *host, const char *op, bool *local_notify, - bool *needs_reply, bool *process, bool *needs_forward) -{ - // Process locally and notify local client - *process = true; - *needs_reply = false; - *local_notify = true; - *needs_forward = false; - - if (pcmk__is_set(operation->flags, cib__op_attr_local)) { - /* Always process locally if cib__op_attr_local is set. - * - * @COMPAT: Currently host is ignored. At a compatibility break, throw - * an error (from based_process_request() or earlier) if host is not - * NULL or OUR_NODENAME. - */ - pcmk__trace("Processing always-local %s op from client %s", op, - pcmk__client_name(client)); + if (ping_digest == NULL) { + ping_digest = pcmk__digest_xml(based_cib, true); + } - if (!pcmk__str_eq(host, OUR_NODENAME, - pcmk__str_casei|pcmk__str_null_matches)) { + pcmk__trace("Processing ping reply %lld from %s (%s)", seq, host, digest); - pcmk__warn("Operation '%s' is always local but its target host is " - "set to '%s'", - op, host); - } + if (pcmk__str_eq(ping_digest, digest, pcmk__str_casei)) { return; } - if (pcmk__is_set(operation->flags, cib__op_attr_modifies) - || !pcmk__str_eq(host, OUR_NODENAME, - pcmk__str_casei|pcmk__str_null_matches)) { + pcmk__notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s", + pcmk__xe_get(based_cib, PCMK_XA_ADMIN_EPOCH), + pcmk__xe_get(based_cib, PCMK_XA_EPOCH), + pcmk__xe_get(based_cib, PCMK_XA_NUM_UPDATES), + ping_digest, host, + pcmk__xe_get(remote_versions, PCMK_XA_ADMIN_EPOCH), + pcmk__xe_get(remote_versions, PCMK_XA_EPOCH), + pcmk__xe_get(remote_versions, PCMK_XA_NUM_UPDATES), digest); - // Forward modifying and non-local requests via cluster - *process = false; - *needs_reply = false; - *local_notify = false; - *needs_forward = true; - - pcmk__trace("%s op from %s needs to be forwarded to %s", op, - pcmk__client_name(client), pcmk__s(host, "all nodes")); - return; - } + sync_our_cib(reply, false); +} - if (stand_alone) { +static void +log_local_options(const pcmk__client_t *client, const char *host, + const char *op) +{ + if (based_stand_alone()) { pcmk__trace("Processing %s op from client %s (stand-alone)", op, pcmk__client_name(client)); @@ -344,21 +330,20 @@ parse_local_options(const pcmk__client_t *client, } static bool -parse_peer_options(const cib__operation_t *operation, xmlNode *request, +parse_peer_options(const cib__operation_t *operation, pcmk__request_t *request, bool *local_notify, bool *needs_reply, bool *process) { - /* TODO: What happens when an update comes in after node A - * requests the CIB from node B, but before it gets the reply (and - * sends out the replace operation)? - * - * (This may no longer be relevant since legacy mode was dropped; need to - * trace code more closely to check.) + /* Don't send replies for modifying ops because they already get forwarded + * to all nodes. Also don't send a reply if the client specifically told us + * not to. */ - const char *host = NULL; - const char *delegated = pcmk__xe_get(request, PCMK__XA_CIB_DELEGATED_FROM); - const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); - const char *originator = pcmk__xe_get(request, PCMK__XA_SRC); - const char *reply_to = pcmk__xe_get(request, PCMK__XA_CIB_ISREPLYTO); + const bool can_reply = !operation->modifies_cib; + + const char *host = pcmk__xe_get(request->xml, PCMK__XA_CIB_HOST); + const char *delegated = pcmk__xe_get(request->xml, + PCMK__XA_CIB_DELEGATED_FROM); + const char *originator = pcmk__xe_get(request->xml, PCMK__XA_SRC); + const char *reply_to = pcmk__xe_get(request->xml, PCMK__XA_CIB_ISREPLYTO); bool is_reply = pcmk__str_eq(reply_to, OUR_NODENAME, pcmk__str_casei); @@ -366,22 +351,46 @@ parse_peer_options(const cib__operation_t *operation, xmlNode *request, originator = "peer"; } - if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) { - // sync_our_cib() sets PCMK__XA_CIB_ISREPLYTO - if (reply_to) { - delegated = reply_to; - } - goto skip_is_reply; + if (is_reply && pcmk__str_eq(request->op, CRM_OP_PING, pcmk__str_none)) { + process_ping_reply(request->xml); + return false; + } - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SYNC_TO_ALL, - pcmk__str_none)) { - // Nothing to do + if (pcmk__str_eq(request->op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { + /* @COMPAT We stopped sending shutdown requests as of 3.0.2. During a + * rolling upgrade, the requesting node expects a reply. We might as + * well continue sending one until we no longer support rolling upgrades + * from versions earlier than 3.0.2. (If the requesting node doesn't + * receive a reply, it simply exits with CRM_EX_ERROR after 10 seconds.) + */ + *needs_reply = true; + return true; + } - } else if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { - process_ping_reply(request); - return false; + if (is_reply + && pcmk__str_eq(request->op, PCMK__CIB_REQUEST_SYNC, pcmk__str_none)) { + + pcmk__trace("Will notify local clients for %s reply from %s", + request->op, originator); + *process = false; + *local_notify = true; + return true; + } + + if ((reply_to != NULL) + && pcmk__str_eq(request->op, PCMK__CIB_REQUEST_REPLACE, + pcmk__str_none)) { + + // sync_our_cib() sets PCMK__XA_CIB_ISREPLYTO + delegated = reply_to; - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_UPGRADE, pcmk__str_none)) { + /* @FIXME can_reply is always false here because cib__op_replace has + * modifies_cib=true. Should we be sending a reply here? + */ + *needs_reply = can_reply; + + } else if (pcmk__str_eq(request->op, PCMK__CIB_REQUEST_UPGRADE, + pcmk__str_none)) { /* Only the DC (node with the oldest software) should process * this operation if PCMK__XA_CIB_SCHEMA_MAX is unset. * @@ -392,89 +401,58 @@ parse_peer_options(const cib__operation_t *operation, xmlNode *request, * Except this time PCMK__XA_CIB_SCHEMA_MAX will be set which puts a * limit on how far newer nodes will go */ - const char *max = pcmk__xe_get(request, PCMK__XA_CIB_SCHEMA_MAX); - const char *upgrade_rc = pcmk__xe_get(request, PCMK__XA_CIB_UPGRADE_RC); + const char *max = pcmk__xe_get(request->xml, PCMK__XA_CIB_SCHEMA_MAX); + const char *upgrade_rc = pcmk__xe_get(request->xml, + PCMK__XA_CIB_UPGRADE_RC); pcmk__trace("Parsing upgrade %s for %s with max=%s and upgrade_rc=%s", (is_reply? "reply" : "request"), - (based_is_primary? "primary" : "secondary"), + (based_get_local_node_dc()? "DC" : "non-DC node"), pcmk__s(max, "none"), pcmk__s(upgrade_rc, "none")); if (upgrade_rc != NULL) { // Our upgrade request was rejected by DC, notify clients of result - pcmk__xe_set(request, PCMK__XA_CIB_RC, upgrade_rc); - - } else if ((max == NULL) && based_is_primary) { - /* We are the DC, check if this upgrade is allowed */ - goto skip_is_reply; - - } else if(max) { - /* Ok, go ahead and upgrade to 'max' */ - goto skip_is_reply; - - } else { - // Ignore broadcast client requests when we're not primary - return false; + pcmk__assert(is_reply); + pcmk__xe_set(request->xml, PCMK__XA_CIB_RC, upgrade_rc); + + pcmk__trace("Will notify local clients for %s reply from %s", + request->op, originator); + *process = false; + *local_notify = true; + return true; } - } else if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) { - pcmk__info("Detected legacy %s global update from %s", op, originator); - send_sync_request(); - return false; - - } else if (is_reply - && pcmk__is_set(operation->flags, cib__op_attr_modifies)) { - - pcmk__trace("Ignoring legacy %s reply sent from %s to local clients", - op, originator); - return false; - - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { - *local_notify = false; - if (reply_to == NULL) { - *process = true; - } else { // Not possible? - pcmk__debug("Ignoring shutdown request from %s because reply_to=%s", - originator, reply_to); + if ((max == NULL) && !based_get_local_node_dc()) { + // Ignore broadcast client requests when we're not the DC + return false; } - return *process; - } - - if (is_reply) { - pcmk__trace("Will notify local clients for %s reply from %s", op, - originator); - *process = false; - *needs_reply = false; - *local_notify = true; - return true; } - skip_is_reply: - *process = true; - *needs_reply = false; - *local_notify = pcmk__str_eq(delegated, OUR_NODENAME, pcmk__str_casei); - host = pcmk__xe_get(request, PCMK__XA_CIB_HOST); if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei)) { - pcmk__trace("Processing %s request sent to us from %s", op, originator); - *needs_reply = true; + pcmk__trace("Processing %s request sent to us from %s", request->op, + originator); + *needs_reply = can_reply; return true; + } - } else if (host != NULL) { - pcmk__trace("Ignoring %s request intended for CIB manager on %s", op, - host); + if (host != NULL) { + pcmk__trace("Ignoring %s request intended for CIB manager on %s", + request->op, host); return false; + } - } else if (!is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_casei)) { - *needs_reply = true; + if (!is_reply && pcmk__str_eq(request->op, CRM_OP_PING, pcmk__str_none)) { + *needs_reply = can_reply; + return true; } pcmk__trace("Processing %s request broadcast by %s call %s on %s " - "(local clients will%s be notified)", op, - pcmk__s(pcmk__xe_get(request, PCMK__XA_CIB_CLIENTNAME), + "(local clients will%s be notified)", request->op, + pcmk__s(pcmk__xe_get(request->xml, PCMK__XA_CIB_CLIENTNAME), "client"), - pcmk__s(pcmk__xe_get(request, PCMK__XA_CIB_CALLID), + pcmk__s(pcmk__xe_get(request->xml, PCMK__XA_CIB_CALLID), "without ID"), originator, (*local_notify? "" : "not")); return true; @@ -485,277 +463,109 @@ parse_peer_options(const cib__operation_t *operation, xmlNode *request, * \brief Forward a CIB request to the appropriate target host(s) * * \param[in] request CIB request to forward + * + * \note \p request is modified within the function, but its initial state is + * restored before returning. This probably doesn't matter, however. */ static void -forward_request(xmlNode *request) +forward_request(pcmk__request_t *request) { - const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); - const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); - const char *host = pcmk__xe_get(request, PCMK__XA_CIB_HOST); - const char *originator = pcmk__xe_get(request, PCMK__XA_SRC); - const char *client_name = pcmk__xe_get(request, PCMK__XA_CIB_CLIENTNAME); - const char *call_id = pcmk__xe_get(request, PCMK__XA_CIB_CALLID); + const char *host = pcmk__xe_get(request->xml, PCMK__XA_CIB_HOST); pcmk__node_status_t *peer = NULL; - int log_level = LOG_INFO; - - if (pcmk__str_eq(op, PCMK__CIB_REQUEST_NOOP, pcmk__str_none)) { - log_level = LOG_DEBUG; - } - - do_crm_log(log_level, - "Forwarding %s operation for section %s to %s (origin=%s/%s/%s)", - pcmk__s(op, "invalid"), - pcmk__s(section, "all"), - pcmk__s(host, "all"), - pcmk__s(originator, "local"), - pcmk__s(client_name, "unspecified"), - pcmk__s(call_id, "unspecified")); - - pcmk__xe_set(request, PCMK__XA_CIB_DELEGATED_FROM, OUR_NODENAME); - if (host != NULL) { peer = pcmk__get_node(0, host, NULL, pcmk__node_search_cluster_member); } - pcmk__cluster_send_message(peer, pcmk_ipc_based, request); - // Return the request to its original state - pcmk__xe_remove_attr(request, PCMK__XA_CIB_DELEGATED_FROM); -} - -/*! - * \internal - * \brief Get a CIB operation's input from the request XML - * - * \param[in] request CIB request XML - * \param[in] type CIB operation type - * \param[out] section Where to store CIB section name - * - * \return Input XML for CIB operation - * - * \note If not \c NULL, the return value is a non-const pointer to part of - * \p request. The caller should not free it directly. - */ -static xmlNode * -prepare_input(const xmlNode *request, enum cib__op_type type, - const char **section) -{ - xmlNode *wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA, - NULL, NULL); - xmlNode *input = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); - - if (type == cib__op_apply_patch) { - *section = NULL; - } else { - *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); - } - - // Grab the specified section - if ((*section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) { - input = pcmk_find_cib_element(input, *section); - } - - return input; -} - -static bool -contains_config_change(xmlNode *diff) -{ - xmlXPathObject *xpath_obj = NULL; - bool changed = false; - - if (diff == NULL) { - return false; - } - - xpath_obj = pcmk__xpath_search(diff->doc, - "//" PCMK_XE_CHANGE - "[contains(@" PCMK_XA_PATH ", " - "'/" PCMK_XE_CRM_CONFIG "/')]"); - - changed = (pcmk__xpath_num_results(xpath_obj) > 0); - xmlXPathFreeObject(xpath_obj); - return changed; + // Set PCMK__XA_CIB_DELEGATED_FROM only temporarily + pcmk__xe_set(request->xml, PCMK__XA_CIB_DELEGATED_FROM, OUR_NODENAME); + pcmk__cluster_send_message(peer, pcmk_ipc_based, request->xml); + pcmk__xe_remove_attr(request->xml, PCMK__XA_CIB_DELEGATED_FROM); } static int -cib_process_command(xmlNode *request, const cib__operation_t *operation, - cib__op_fn_t op_function, xmlNode **reply, bool privileged) +based_perform_op_rw(pcmk__request_t *request, const cib__operation_t *operation, + cib__op_fn_t op_function, xmlNode **output) { + const char *feature_set = pcmk__xe_get(based_cib, + PCMK_XA_CRM_FEATURE_SET); + xmlNode *result_cib = based_cib; xmlNode *cib_diff = NULL; - xmlNode *input = NULL; - xmlNode *output = NULL; - xmlNode *result_cib = NULL; - - uint32_t call_options = cib_none; - - const char *op = NULL; - const char *section = NULL; - const char *call_id = pcmk__xe_get(request, PCMK__XA_CIB_CALLID); - const char *client_id = pcmk__xe_get(request, PCMK__XA_CIB_CLIENTID); - const char *client_name = pcmk__xe_get(request, PCMK__XA_CIB_CLIENTNAME); - const char *originator = pcmk__xe_get(request, PCMK__XA_SRC); - - int rc = pcmk_rc_ok; + const char *originator = pcmk__xe_get(request->xml, PCMK__XA_SRC); bool config_changed = false; - bool manage_counters = true; - - static mainloop_timer_t *digest_timer = NULL; - - pcmk__assert(cib_status == pcmk_rc_ok); - - if (digest_timer == NULL) { - digest_timer = mainloop_timer_add("based_digest_timer", 5000, false, - digest_timer_cb, NULL); - } - - *reply = NULL; - - /* Start processing the request... */ - op = pcmk__xe_get(request, PCMK__XA_CIB_OP); - rc = pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &call_options, - cib_none); - if (rc != pcmk_rc_ok) { - pcmk__warn("Couldn't parse options from request: %s", pcmk_rc_str(rc)); - } - - if (!privileged - && pcmk__is_set(operation->flags, cib__op_attr_privileged)) { - rc = EACCES; - pcmk__trace("Failed due to lack of privileges: %s", pcmk_rc_str(rc)); - goto done; - } - - input = prepare_input(request, operation->type, §ion); - - if (!pcmk__is_set(operation->flags, cib__op_attr_modifies)) { - rc = cib_perform_op(NULL, op, call_options, op_function, true, section, - request, input, false, &config_changed, &the_cib, - &result_cib, NULL, &output); - - CRM_CHECK(result_cib == NULL, pcmk__xml_free(result_cib)); - goto done; - } + int rc = pcmk_rc_ok; - /* @COMPAT: Handle a valid write action (legacy) + /* result_cib must not be modified after cib__perform_op_rw() returns. * - * @TODO: Re-evaluate whether this is all truly legacy. The cib_force_diff - * portion is. However, PCMK__XA_CIB_UPDATE may be set by a sync operation - * even in non-legacy mode, and manage_counters tells xml_create_patchset() - * whether to update version/epoch info. + * It's not important whether the client variant is cib_native or + * cib_remote. */ - if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) { - manage_counters = false; - cib__set_call_options(call_options, "call", cib_force_diff); - pcmk__trace("Global update detected"); - - CRM_LOG_ASSERT(pcmk__str_any_of(op, - PCMK__CIB_REQUEST_APPLY_PATCH, - PCMK__CIB_REQUEST_REPLACE, - NULL)); - } + rc = cib__perform_op_rw(cib_undefined, op_function, request->xml, + &config_changed, &result_cib, &cib_diff, output); - ping_modified_since = true; - - // result_cib must not be modified after cib_perform_op() returns - rc = cib_perform_op(NULL, op, call_options, op_function, false, section, - request, input, manage_counters, &config_changed, - &the_cib, &result_cib, &cib_diff, &output); - - /* Always write to disk for successful ops with the flag set. This also - * negates the need to detect ordering changes. + /* On validation error, include the schema-violating result CIB in any reply + * or notification that we will send. */ - if ((rc == pcmk_rc_ok) - && pcmk__is_set(operation->flags, cib__op_attr_writes_through)) { - - config_changed = true; + if (rc == pcmk_rc_schema_validation) { + pcmk__assert((result_cib != based_cib) && (*output == NULL)); + *output = result_cib; + goto done; } - if ((rc == pcmk_rc_ok) - && !pcmk__any_flags_set(call_options, cib_dryrun|cib_transaction)) { - - if (result_cib != the_cib) { - if (pcmk__is_set(operation->flags, cib__op_attr_writes_through)) { - config_changed = true; - } - - pcmk__trace("Activating %s->%s%s", - pcmk__xe_get(the_cib, PCMK_XA_NUM_UPDATES), - pcmk__xe_get(result_cib, PCMK_XA_NUM_UPDATES), - (config_changed? " changed" : "")); + // Discard result for failure or dry run + if ((rc != pcmk_rc_ok) + || pcmk__any_flags_set(request->call_options, cib_dryrun)) { - rc = based_activate_cib(result_cib, config_changed, op); - if (rc != pcmk_rc_ok) { - pcmk__err("Failed to activate new CIB: %s", pcmk_rc_str(rc)); - } + if (result_cib != based_cib) { + pcmk__xml_free(result_cib); } - if ((rc == pcmk_rc_ok) && contains_config_change(cib_diff)) { - cib_read_config(config_hash, result_cib); - } + goto done; + } - /* @COMPAT Nodes older than feature set 3.19.0 don't support - * transactions. In a mixed-version cluster with nodes <3.19.0, we must - * sync the updated CIB, so that the older nodes receive the changes. - * Any node that has already applied the transaction will ignore the - * synced CIB. - * - * To ensure the updated CIB is synced from only one node, we sync it - * from the originator. + if (result_cib != based_cib) { + /* Write to disk on config change or successful upgrade (which may + * update PCMK_XA_VALIDATE_WITH without changing the configuration), + * unless the request is part of a transaction. Since a transaction + * is atomic, intermediate results must not be written to disk. */ - if ((operation->type == cib__op_commit_transact) - && pcmk__str_eq(originator, OUR_NODENAME, pcmk__str_casei) - && (pcmk__compare_versions(pcmk__xe_get(the_cib, - PCMK_XA_CRM_FEATURE_SET), - "3.19.0") < 0)) { - - sync_our_cib(request, true); - } - - mainloop_timer_start(digest_timer); - - } else if (rc == pcmk_rc_schema_validation) { - pcmk__assert(result_cib != the_cib); + const bool to_disk = !pcmk__is_set(request->call_options, + cib_transaction) + && (config_changed + || (operation->type == cib__op_upgrade)); - if (output != NULL) { - pcmk__log_xml_info(output, "cib:output"); - pcmk__xml_free(output); - } - - output = result_cib; + rc = based_activate_cib(result_cib, to_disk, request->op); + } - } else { - pcmk__trace("Not activating %d %d %s", rc, - pcmk__is_set(call_options, cib_dryrun), - pcmk__xe_get(result_cib, PCMK_XA_NUM_UPDATES)); + /* @COMPAT Nodes older than feature set 3.19.0 don't support transactions. + * In a mixed-version cluster with nodes <3.19.0, we must sync the updated + * CIB, so that the older nodes receive the changes. Any node that has + * already applied the transaction will ignore the synced CIB. + * + * To ensure the updated CIB is synced from only one node, we sync it from + * the originator. + */ + if ((operation->type == cib__op_commit_transact) + && pcmk__str_eq(originator, OUR_NODENAME, pcmk__str_casei) + && (pcmk__compare_versions(feature_set, "3.19.0") < 0)) { - if (result_cib != the_cib) { - pcmk__xml_free(result_cib); - } + sync_our_cib(request->xml, true); } - if (!pcmk__any_flags_set(call_options, - cib_dryrun|cib_inhibit_notify|cib_transaction)) { - pcmk__trace("Sending notifications %d", - pcmk__is_set(call_options, cib_dryrun)); - based_diff_notify(op, pcmk_rc2legacy(rc), call_id, client_id, - client_name, originator, input, cib_diff); + if (cib_diff != NULL) { + ping_modified_since = true; } - pcmk__log_xml_patchset(LOG_TRACE, cib_diff); + mainloop_timer_start(digest_timer); - done: - if (!pcmk__is_set(call_options, cib_discard_reply)) { - *reply = create_cib_reply(op, call_id, client_id, call_options, - pcmk_rc2legacy(rc), output); - } +done: + if (!pcmk__any_flags_set(request->call_options, + cib_dryrun|cib_inhibit_notify|cib_transaction)) { - if (output != the_cib) { - pcmk__xml_free(output); + based_diff_notify(request->xml, rc, cib_diff); } - pcmk__trace("done"); pcmk__xml_free(cib_diff); return rc; } @@ -785,25 +595,9 @@ log_op_result(const xmlNode *request, const cib__operation_t *operation, int rc, int epoch = 0; int num_updates = 0; - if (!pcmk__is_set(operation->flags, cib__op_attr_modifies)) { + if (!operation->modifies_cib) { level = LOG_TRACE; - } else if (pcmk__xe_attr_is_true(request, PCMK__XA_CIB_UPDATE)) { - switch (rc) { - case pcmk_rc_ok: - level = LOG_INFO; - break; - - case pcmk_rc_old_data: - case pcmk_rc_diff_resync: - case pcmk_rc_diff_failed: - level = LOG_TRACE; - break; - - default: - level = LOG_ERR; - } - } else if (rc != pcmk_rc_ok) { level = LOG_WARNING; } @@ -812,14 +606,9 @@ log_op_result(const xmlNode *request, const cib__operation_t *operation, int rc, originator = pcmk__s(originator, "local"); client_name = pcmk__s(client_name, "client"); - /* @FIXME the_cib should always be non-NULL, but that's currently not the - * case during shutdown - */ - if (the_cib != NULL) { - pcmk__xe_get_int(the_cib, PCMK_XA_ADMIN_EPOCH, &admin_epoch); - pcmk__xe_get_int(the_cib, PCMK_XA_EPOCH, &epoch); - pcmk__xe_get_int(the_cib, PCMK_XA_NUM_UPDATES, &num_updates); - } + pcmk__xe_get_int(based_cib, PCMK_XA_ADMIN_EPOCH, &admin_epoch); + pcmk__xe_get_int(based_cib, PCMK_XA_EPOCH, &epoch); + pcmk__xe_get_int(based_cib, PCMK_XA_NUM_UPDATES, &num_updates); do_crm_log(level, "Completed %s operation for section %s: %s (rc=%d, " @@ -854,111 +643,104 @@ send_peer_reply(xmlNode *msg, const char *originator) /*! * \internal - * \brief Handle an IPC or CPG message containing a request + * \brief Handle a request from a CIB manager client or peer * - * \param[in,out] request Request XML - * \param[in] privileged If \c true, operations with - * \c cib__op_attr_privileged can be run - * \param[in] client IPC client that sent request (\c NULL if request - * came from CPG) + * \param[in,out] request CIB manager request * * \return Standard Pacemaker return code */ int -based_process_request(xmlNode *request, bool privileged, - const pcmk__client_t *client) +based_handle_request(pcmk__request_t *request) { // @TODO: Break into multiple smaller functions - uint32_t call_options = cib_none; - bool process = true; // Whether to process request locally now - bool needs_reply = true; // Whether to build a reply + bool needs_reply = false; // Whether to build a reply bool local_notify = false; // Whether to notify (local) requester - bool needs_forward = false; // Whether to forward request somewhere else - - xmlNode *reply = NULL; int rc = pcmk_rc_ok; - const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); - const char *originator = pcmk__xe_get(request, PCMK__XA_SRC); - const char *host = pcmk__xe_get(request, PCMK__XA_CIB_HOST); - const char *call_id = pcmk__xe_get(request, PCMK__XA_CIB_CALLID); - const char *client_id = pcmk__xe_get(request, PCMK__XA_CIB_CLIENTID); - const char *client_name = pcmk__s(pcmk__xe_get(request, PCMK__XA_CIB_CLIENTNAME), + const char *originator = pcmk__xe_get(request->xml, PCMK__XA_SRC); + const char *host = pcmk__xe_get(request->xml, PCMK__XA_CIB_HOST); + const char *call_id = pcmk__xe_get(request->xml, PCMK__XA_CIB_CALLID); + const char *reply_to = pcmk__xe_get(request->xml, PCMK__XA_CIB_ISREPLYTO); + + /* These are the client ID and name on the originator node, so we need to + * get these from the request XML. request->ipc_client is NULL if this + * request came from the cluster. + */ + const char *client_id = pcmk__xe_get(request->xml, PCMK__XA_CIB_CLIENTID); + const char *client_name = pcmk__s(pcmk__xe_get(request->xml, + PCMK__XA_CIB_CLIENTNAME), "client"); - const char *reply_to = pcmk__xe_get(request, PCMK__XA_CIB_ISREPLYTO); const cib__operation_t *operation = NULL; cib__op_fn_t op_function = NULL; - rc = pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &call_options, - cib_none); - if (rc != pcmk_rc_ok) { - pcmk__warn("Couldn't parse options from request: %s", pcmk_rc_str(rc)); + xmlNode *output = NULL; + time_t start_time = 0; + + if (based_shutting_down()) { + pcmk__info("Ignoring pending CIB request during shutdown"); + pcmk__reset_request(request); + return ENOTCONN; } if (pcmk__str_empty(host)) { host = NULL; } - if (client == NULL) { + if (request->ipc_client == NULL) { pcmk__trace("Processing peer %s operation from %s/%s on %s intended " - "for %s (reply=%s)", op, client_name, call_id, originator, - pcmk__s(host, "all"), reply_to); + "for %s (reply=%s)", request->op, client_name, call_id, + originator, pcmk__s(host, "all"), reply_to); } else { - pcmk__xe_set(request, PCMK__XA_SRC, OUR_NODENAME); + pcmk__xe_set(request->xml, PCMK__XA_SRC, OUR_NODENAME); pcmk__trace("Processing local %s operation from %s/%s intended for %s", - op, client_name, call_id, pcmk__s(host, "all")); + request->op, client_name, call_id, pcmk__s(host, "all")); } - rc = cib__get_operation(op, &operation); + rc = cib__get_operation(request->op, &operation); if (rc != pcmk_rc_ok) { - /* TODO: construct error reply? */ pcmk__err("Pre-processing of command failed: %s", pcmk_rc_str(rc)); + pcmk__reset_request(request); return rc; } op_function = based_get_op_function(operation); if (op_function == NULL) { - pcmk__err("Operation %s not supported by CIB manager", op); + pcmk__err("Operation %s not supported by CIB manager", request->op); + pcmk__reset_request(request); return EOPNOTSUPP; } - if (client != NULL) { - parse_local_options(client, operation, host, op, &local_notify, - &needs_reply, &process, &needs_forward); - - } else if (!parse_peer_options(operation, request, &local_notify, - &needs_reply, &process)) { - return pcmk_rc_ok; - } - - if (pcmk__is_set(call_options, cib_transaction)) { + if (pcmk__is_set(request->call_options, cib_transaction)) { /* All requests in a transaction are processed locally against a working * CIB copy, and we don't notify for individual requests because the * entire transaction is atomic. - * - * We still call the option parser functions above, for the sake of log - * messages and checking whether we're the target for peer requests. */ - process = true; - needs_reply = false; - local_notify = false; - needs_forward = false; - } + pcmk__trace("Processing %s op from %s/%s on %s locally because it's " + "part of a transaction", request->op, client_name, call_id, + pcmk__xe_get(request->xml, PCMK__XA_SRC)); - if (pcmk__is_set(call_options, cib_discard_reply)) { - /* If the request will modify the CIB, and we are in legacy mode, we - * need to build a reply so we can broadcast a diff, even if the - * requester doesn't want one. - */ - needs_reply = false; - local_notify = false; - pcmk__trace("Client is not interested in the reply"); - } + } else if (request->ipc_client != NULL) { + // Forward modifying and non-local requests via cluster + if (!based_stand_alone() + && (operation->modifies_cib + || !pcmk__str_eq(host, OUR_NODENAME, + pcmk__str_casei|pcmk__str_null_matches))) { + + forward_request(request); + pcmk__reset_request(request); + return pcmk_rc_ok; + } + + // Process locally and notify local client; no peer to reply to + local_notify = true; - if (needs_forward) { - forward_request(request); + log_local_options(request->ipc_client, host, request->op); + + } else if (!parse_peer_options(operation, request, &local_notify, + &needs_reply, &process)) { + pcmk__reset_request(request); return pcmk_rc_ok; } @@ -966,226 +748,44 @@ based_process_request(xmlNode *request, bool privileged, rc = cib_status; pcmk__err("Ignoring request because cluster configuration is invalid " "(please repair and restart): %s", pcmk_rc_str(rc)); - reply = create_cib_reply(op, call_id, client_id, call_options, - pcmk_rc2legacy(rc), the_cib); - - } else if (process) { - time_t start_time = time(NULL); - - rc = cib_process_command(request, operation, op_function, &reply, - privileged); - log_op_result(request, operation, rc, difftime(time(NULL), start_time)); - - if ((reply == NULL) && (needs_reply || local_notify)) { - pcmk__err("Unexpected NULL reply to message"); - pcmk__log_xml_err(request, "null reply"); - goto done; - } - } - - if (pcmk__is_set(operation->flags, cib__op_attr_modifies)) { - pcmk__trace("Completed pre-sync update from %s/%s/%s%s", - pcmk__s(originator, "local"), client_name, call_id, - (local_notify? " with local notification" : "")); - - } else if (needs_reply && !stand_alone && (client == NULL) - && !pcmk__is_set(call_options, cib_discard_reply)) { - send_peer_reply(reply, originator); - } - - if (local_notify && (client_id != NULL)) { - do_local_notify((process? reply : request), client_id, - pcmk__is_set(call_options, cib_sync_call), - (client == NULL)); - } - -done: - pcmk__xml_free(reply); - return rc; -} - -void -based_peer_callback(xmlNode *msg, void *private_data) -{ - const char *reason = NULL; - const char *originator = pcmk__xe_get(msg, PCMK__XA_SRC); - - if (pcmk__peer_cache == NULL) { - reason = "membership not established"; - goto bail; - } - - if (pcmk__xe_get(msg, PCMK__XA_CIB_CLIENTNAME) == NULL) { - pcmk__xe_set(msg, PCMK__XA_CIB_CLIENTNAME, originator); - } - - based_process_request(msg, true, NULL); - return; - - bail: - if (reason) { - const char *op = pcmk__xe_get(msg, PCMK__XA_CIB_OP); - - pcmk__warn("Discarding %s message from %s: %s", op, originator, reason); + output = based_cib; + goto done; } -} - -static gboolean -cib_force_exit(gpointer data) -{ - pcmk__notice("Exiting immediately after %s without shutdown acknowledgment", - pcmk__readable_interval(EXIT_ESCALATION_MS)); - based_terminate(CRM_EX_ERROR); - return FALSE; -} -static void -disconnect_remote_client(gpointer key, gpointer value, gpointer user_data) -{ - pcmk__client_t *a_client = value; - - pcmk__err("Can't disconnect client %s: Not implemented", - pcmk__client_name(a_client)); -} - -static void -initiate_exit(void) -{ - int active = 0; - xmlNode *leaving = NULL; - - active = pcmk__cluster_num_active_nodes(); - if (active < 2) { // This is the last active node - pcmk__info("Exiting without sending shutdown request (no active " - "peers)"); - based_terminate(CRM_EX_OK); - return; + if (!process) { + goto done; } - pcmk__info("Sending shutdown request to %d peers", active); - - leaving = pcmk__xe_create(NULL, PCMK__XE_EXIT_NOTIFICATION); - pcmk__xe_set(leaving, PCMK__XA_T, PCMK__VALUE_CIB); - pcmk__xe_set(leaving, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_SHUTDOWN); - - pcmk__cluster_send_message(NULL, pcmk_ipc_based, leaving); - pcmk__xml_free(leaving); - - pcmk__create_timer(EXIT_ESCALATION_MS, cib_force_exit, NULL); -} - -void -based_shutdown(int nsig) -{ - struct qb_ipcs_stats srv_stats; - - if (!cib_shutdown_flag) { - int disconnects = 0; - qb_ipcs_connection_t *c = NULL; - - cib_shutdown_flag = true; - - c = qb_ipcs_connection_first_get(ipcs_rw); - while (c != NULL) { - qb_ipcs_connection_t *last = c; - - c = qb_ipcs_connection_next_get(ipcs_rw, last); - - pcmk__debug("Disconnecting r/w client %p...", last); - qb_ipcs_disconnect(last); - qb_ipcs_connection_unref(last); - disconnects++; - } - - c = qb_ipcs_connection_first_get(ipcs_ro); - while (c != NULL) { - qb_ipcs_connection_t *last = c; - - c = qb_ipcs_connection_next_get(ipcs_ro, last); - - pcmk__debug("Disconnecting r/o client %p...", last); - qb_ipcs_disconnect(last); - qb_ipcs_connection_unref(last); - disconnects++; - } - - c = qb_ipcs_connection_first_get(ipcs_shm); - while (c != NULL) { - qb_ipcs_connection_t *last = c; - - c = qb_ipcs_connection_next_get(ipcs_shm, last); - - pcmk__debug("Disconnecting non-blocking r/w client %p...", last); - qb_ipcs_disconnect(last); - qb_ipcs_connection_unref(last); - disconnects++; - } + start_time = time(NULL); - disconnects += pcmk__ipc_client_count(); + if (!operation->modifies_cib) { + rc = cib__perform_op_ro(op_function, request->xml, &based_cib, &output); - pcmk__debug("Disconnecting %d remote clients", - pcmk__ipc_client_count()); - pcmk__foreach_ipc_client(disconnect_remote_client, NULL); - pcmk__info("Disconnected %d clients", disconnects); + } else { + rc = based_perform_op_rw(request, operation, op_function, &output); } - qb_ipcs_stats_get(ipcs_rw, &srv_stats, QB_FALSE); - - if (pcmk__ipc_client_count() == 0) { - pcmk__info("All clients disconnected (%d)", srv_stats.active_connections); - initiate_exit(); + log_op_result(request->xml, operation, rc, difftime(time(NULL), start_time)); - } else { - pcmk__info("Waiting on %d clients to disconnect (%d)", - pcmk__ipc_client_count(), srv_stats.active_connections); - } -} +done: + if (needs_reply) { + xmlNode *reply = create_cib_reply(request->xml, rc, output); -/*! - * \internal - * \brief Close remote sockets, free the global CIB and quit - * - * \param[in] exit_status What exit status to use (if -1, use CRM_EX_OK, but - * skip disconnecting from the cluster layer) - */ -void -based_terminate(int exit_status) -{ - if (remote_fd > 0) { - close(remote_fd); - remote_fd = 0; - } - if (remote_tls_fd > 0) { - close(remote_tls_fd); - remote_tls_fd = 0; + send_peer_reply(reply, originator); + pcmk__xml_free(reply); } - g_clear_pointer(&the_cib, pcmk__xml_free); - - // Exit immediately on error - if (exit_status > CRM_EX_OK) { - pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); - crm_exit(exit_status); - return; + if (local_notify && (client_id != NULL)) { + do_local_notify((process? reply : request->xml), client_id, + pcmk__is_set(request->call_options, cib_sync_call), + (request->ipc_client == NULL)); } - if ((mainloop != NULL) && g_main_loop_is_running(mainloop)) { - /* Quit via returning from the main loop. If exit_status has the special - * value -1, we skip the disconnect here, and it will be done when the - * main loop returns (this allows the peer status callback to avoid - * messing with the peer caches). - */ - if (exit_status == CRM_EX_OK) { - pcmk_cluster_disconnect(crm_cluster); - } - g_main_loop_quit(mainloop); - return; + if ((output != NULL) && (output->doc != based_cib->doc)) { + pcmk__xml_free(output); } - /* Exit cleanly. Even the peer status callback can disconnect here, because - * we're not returning control to the caller. - */ - pcmk_cluster_disconnect(crm_cluster); - pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); - crm_exit(CRM_EX_OK); + pcmk__xml_free(reply); + pcmk__reset_request(request); + return rc; } diff --git a/daemons/based/based_callbacks.h b/daemons/based/based_callbacks.h index d52bf52431e..29613b2c4d7 100644 --- a/daemons/based/based_callbacks.h +++ b/daemons/based/based_callbacks.h @@ -10,24 +10,13 @@ #ifndef BASED_CALLBACKS__H #define BASED_CALLBACKS__H -#include - #include // xmlNode -#include // qb_* #include // pcmk__client_t -extern struct qb_ipcs_service_handlers ipc_ro_callbacks; -extern struct qb_ipcs_service_handlers ipc_rw_callbacks; - -extern qb_ipcs_service_t *ipcs_ro; -extern qb_ipcs_service_t *ipcs_rw; -extern qb_ipcs_service_t *ipcs_shm; +void based_callbacks_init(void); +void based_callbacks_cleanup(void); -void based_peer_callback(xmlNode *msg, void *private_data); -int based_process_request(xmlNode *request, bool privileged, - const pcmk__client_t *client); -void based_shutdown(int nsig); -void based_terminate(int exit_status); +int based_handle_request(pcmk__request_t *request); #endif // BASED_CALLBACKS__H diff --git a/daemons/based/based_corosync.c b/daemons/based/based_corosync.c new file mode 100644 index 00000000000..5e9b972e248 --- /dev/null +++ b/daemons/based/based_corosync.c @@ -0,0 +1,183 @@ +/* + * Copyright 2004-2026 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include // PRIu32 +#include // NULL, size_t +#include // uint32_t +#include // free + +#include // cpg_* +#include // gpointer +#include // xmlNode + +#include // SUPPORT_COROSYNC +#include // pcmk_cluster_* +#include // pcmk__cluster_*, etc. +#include // pcmk__err, pcmk__xml_free, etc. +#include // CRM_EX_DISCONNECT, pcmk_rc_ok + +#include "pacemaker-based.h" + +static pcmk_cluster_t *cluster = NULL; + +static void +based_peer_message(pcmk__node_status_t *peer, xmlNode *xml) +{ + int rc = pcmk_rc_ok; + + if (based_shutting_down()) { + pcmk__info("Ignoring CPG message from %s[%" PRIu32 "] during shutdown", + peer->name, peer->cluster_layer_id); + return; + + } else { + pcmk__request_t request = { + .ipc_client = NULL, + .ipc_id = 0, + .ipc_flags = 0, + .peer = peer->name, + .xml = xml, + .call_options = cib_none, + .result = PCMK__UNKNOWN_RESULT, + }; + + rc = pcmk__xe_get_flags(xml, PCMK__XA_CIB_CALLOPT, + (uint32_t *) &request.call_options, cib_none); + if (rc != pcmk_rc_ok) { + pcmk__warn("Couldn't parse options from request: %s", + pcmk_rc_str(rc)); + } + + request.op = pcmk__xe_get_copy(request.xml, PCMK__XA_CIB_OP); + CRM_CHECK(request.op != NULL, return); + + if (pcmk__is_set(request.call_options, cib_sync_call)) { + pcmk__set_request_flags(&request, pcmk__request_sync); + } + + if (pcmk__xe_get(request.xml, PCMK__XA_CIB_CLIENTNAME) == NULL) { + pcmk__xe_set(request.xml, PCMK__XA_CIB_CLIENTNAME, + pcmk__xe_get(request.xml, PCMK__XA_SRC)); + } + + based_handle_request(&request); + } +} + +#if SUPPORT_COROSYNC +/*! + * \internal + * \brief Callback for when a peer message is received + * + * \param[in] handle Cluster connection + * \param[in] group_name Group that \p nodeid is a member of + * \param[in] nodeid Peer node that sent \p msg + * \param[in] pid Process that sent \p msg + * \param[in,out] msg Received message + * \param[in] msg_len Length of \p msg + */ +static void +based_cpg_dispatch(cpg_handle_t handle, const struct cpg_name *group_name, + uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) +{ + xmlNode *xml = NULL; + const char *from = NULL; + char *data = pcmk__cpg_message_data(handle, nodeid, pid, msg, &from); + + if (data == NULL) { + return; + } + + xml = pcmk__xml_parse(data); + if (xml == NULL) { + pcmk__err("Bad message received from %s[%" PRIu32 "]: '%.120s'", from, + nodeid, data); + + } else { + pcmk__xe_set(xml, PCMK__XA_SRC, from); + based_peer_message(pcmk__get_node(nodeid, from, NULL, + pcmk__node_search_cluster_member), + xml); + } + + pcmk__xml_free(xml); + free(data); +} + +static void +based_cpg_destroy(gpointer user_data) +{ + if (based_shutting_down()) { + pcmk__info("Corosync disconnection complete"); + return; + } + + pcmk__crit("Exiting after losing connection to cluster layer"); + based_quit_main_loop(CRM_EX_DISCONNECT); +} +#endif + +/*! + * \internal + * \brief Initialize the cluster object and connect to the cluster layer + * + * \return Standard Pacemaker return code + */ +int +based_cluster_connect(void) +{ + int rc = pcmk_rc_ok; + + cluster = pcmk_cluster_new(); + +#if SUPPORT_COROSYNC + if (pcmk_get_cluster_layer() == pcmk_cluster_layer_corosync) { + pcmk_cluster_set_destroy_fn(cluster, based_cpg_destroy); + pcmk_cpg_set_deliver_fn(cluster, based_cpg_dispatch); + pcmk_cpg_set_confchg_fn(cluster, pcmk__cpg_confchg_cb); + } +#endif // SUPPORT_COROSYNC + + rc = pcmk_cluster_connect(cluster); + if (rc != pcmk_rc_ok) { + pcmk__err("Cluster connection failed"); + } + + return rc; +} + +/*! + * \internal + * \brief Disconnect from the cluster layer and free the cluster object + */ +void +based_cluster_disconnect(void) +{ + if (cluster == NULL) { + return; + } + + pcmk_cluster_disconnect(cluster); + g_clear_pointer(&cluster, pcmk_cluster_free); +} + +/*! + * \internal + * \brief Get the local node name at the cluster layer + * + * \return Local cluster-layer node name, or \c NULL if there is no active + * cluster connection + */ +const char * +based_cluster_node_name(void) +{ + return (cluster != NULL)? cluster->priv->node_name : NULL; +} diff --git a/daemons/based/based_corosync.h b/daemons/based/based_corosync.h new file mode 100644 index 00000000000..d48e55b783e --- /dev/null +++ b/daemons/based/based_corosync.h @@ -0,0 +1,17 @@ +/* + * Copyright 2025-2026 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef BASED_COROSYNC__H +#define BASED_COROSYNC__H + +int based_cluster_connect(void); +void based_cluster_disconnect(void); +const char *based_cluster_node_name(void); + +#endif // BASED_COROSYNC__H diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c index 623603d6013..d85c94b39c2 100644 --- a/daemons/based/based_io.c +++ b/daemons/based/based_io.c @@ -31,7 +31,7 @@ #include // createEmptyCib #include // pcmk__assert_asprintf, PCMK__XE_*, etc. #include // CRM_CHECK -#include // mainloop_add_signal +#include // mainloop_* #include // pcmk_legacy2rc, pcmk_rc_* #include // pcmk_common_cleanup #include // PCMK_XA_*, PCMK_XE_* @@ -93,6 +93,10 @@ write_cib_async(gpointer user_data) pid_t pid = 0; int blackbox_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0); + if (based_shutting_down()) { + pcmk__info("Skipping CIB write during shutdown"); + } + /* Disable blackbox logging before the fork to avoid two processes writing * to the same shared memory. The disable should not be done in the child, * because this would close shared memory files in the parent. @@ -125,10 +129,10 @@ write_cib_async(gpointer user_data) return -1; } - /* Write the CIB. Note that this modifies the_cib, but this child is about - * to exit. The parent's copy of the_cib won't be affected. + /* Write the CIB. Note that this modifies based_cib, but this child is about + * to exit. The parent's copy of based_cib won't be affected. */ - rc = cib_file_write_with_digest(the_cib, cib_root, "cib.xml"); + rc = cib_file_write_with_digest(based_cib, cib_root, "cib.xml"); rc = pcmk_legacy2rc(rc); pcmk_common_cleanup(); @@ -176,12 +180,12 @@ based_enable_writes(int nsig) /*! * \internal - * \brief Initialize data structures for \c pacemaker-based I/O + * \brief Initialize data structures used for CIB manager I/O */ void based_io_init(void) { - writes_enabled = !stand_alone; + writes_enabled = !based_stand_alone(); if (writes_enabled && pcmk__env_option_enabled(PCMK__SERVER_BASED, PCMK__ENV_VALGRIND_ENABLED)) { @@ -198,6 +202,16 @@ based_io_init(void) write_trigger = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_async, NULL); } +/*! + * \internal + * \brief Free data structures used for CIB manager I/O + */ +void +based_io_cleanup(void) +{ + g_clear_pointer(&write_trigger, mainloop_destroy_trigger); +} + /*! * \internal * \brief Rename a CIB or digest file after digest mismatch @@ -501,7 +515,7 @@ set_empty_status(xmlNode *cib_xml) { xmlNode *status = pcmk__xe_first_child(cib_xml, PCMK_XE_STATUS, NULL, NULL); - if (!stand_alone) { + if (!based_stand_alone()) { g_clear_pointer(&status, pcmk__xml_free); } @@ -584,7 +598,7 @@ based_read_cib(void) // The DC should set appropriate value for PCMK_XA_DC_UUID pcmk__xe_remove_attr(cib_xml, PCMK_XA_DC_UUID); - if (!stand_alone) { + if (!based_stand_alone()) { pcmk__log_xml_trace(cib_xml, "on-disk"); } @@ -599,10 +613,10 @@ based_read_cib(void) * \internal * \brief Activate new CIB XML * - * This function frees the existing \c the_cib and points it to \p new_cib. + * This function frees the existing \c based_cib and points it to \p new_cib. * * \param[in] new_cib CIB XML to activate (must not be \c NULL or equal to - * \c the_cib) + * \c based_cib) * \param[in] to_disk If \c true and if the CIB status is OK and writes are * enabled, trigger the new CIB to be written to disk * \param[in] op Operation that triggered the activation (for logging @@ -611,15 +625,15 @@ based_read_cib(void) * \return Standard Pacemaker return code * * \note This function takes ownership of \p new_cib by assigning it to - * \c the_cib. The caller should not free it. + * \c based_cib. The caller should not free it. */ int based_activate_cib(xmlNode *new_cib, bool to_disk, const char *op) { - CRM_CHECK((new_cib != NULL) && (new_cib != the_cib), return ENODATA); + CRM_CHECK((new_cib != NULL) && (new_cib != based_cib), return ENODATA); - pcmk__xml_free(the_cib); - the_cib = new_cib; + pcmk__xml_free(based_cib); + based_cib = new_cib; if (to_disk && writes_enabled && (cib_status == pcmk_rc_ok)) { pcmk__debug("Triggering CIB write for %s op", op); diff --git a/daemons/based/based_io.h b/daemons/based/based_io.h index f9bb5011a1a..706d476d026 100644 --- a/daemons/based/based_io.h +++ b/daemons/based/based_io.h @@ -15,6 +15,8 @@ #include // xmlNode void based_io_init(void); +void based_io_cleanup(void); + void based_enable_writes(int nsig); xmlNode *based_read_cib(void); int based_activate_cib(xmlNode *new_cib, bool to_disk, const char *op); diff --git a/daemons/based/based_ipc.c b/daemons/based/based_ipc.c index 55606f12fd4..4299576f965 100644 --- a/daemons/based/based_ipc.c +++ b/daemons/based/based_ipc.c @@ -10,7 +10,6 @@ #include #include // ECONNREFUSED, ENOMEM -#include #include // NULL, size_t #include // int32_t, uint32_t #include // gid_t, uid_t @@ -28,9 +27,7 @@ #include "pacemaker-based.h" -qb_ipcs_service_t *ipcs_ro = NULL; -qb_ipcs_service_t *ipcs_rw = NULL; -qb_ipcs_service_t *ipcs_shm = NULL; +static qb_ipcs_service_t *ipcs = NULL; /*! * \internal @@ -45,7 +42,7 @@ qb_ipcs_service_t *ipcs_shm = NULL; static int32_t based_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) { - if (cib_shutdown_flag) { + if (based_shutting_down()) { pcmk__info("Ignoring new IPC client [%d] during shutdown", pcmk__client_pid(c)); return -ECONNREFUSED; @@ -62,25 +59,24 @@ based_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) * \internal * \brief Handle a message from an IPC connection * - * \param[in,out] c Established IPC connection - * \param[in] data The message data read from the connection - this - * can be a complete IPC message or just a part of - * one if it's very large - * \param[in] privileged If \c true, operations with - * \c cib__op_attr_privileged can be run + * \param[in,out] c Established IPC connection + * \param[in] data The message data read from the connection - this can be + * a complete IPC message or just a part of one if it's + * very large + * \param[in] size Unused * * \return 0 in all cases */ static int32_t -dispatch_common(qb_ipcs_connection_t *c, void *data, bool privileged) +based_ipc_dispatch(qb_ipcs_connection_t *c, void *data, size_t size) { - int rc = pcmk_rc_ok; uint32_t id = 0; uint32_t flags = 0; uint32_t call_options = cib_none; xmlNode *msg = NULL; pcmk__client_t *client = pcmk__find_client(c); const char *op = NULL; + int rc = pcmk_rc_ok; // Sanity-check, and parse XML from IPC data CRM_CHECK(client != NULL, return 0); @@ -89,9 +85,6 @@ dispatch_common(qb_ipcs_connection_t *c, void *data, bool privileged) return 0; } - pcmk__trace("Dispatching %sprivileged request from client %s", - (privileged? "" : "un"), client->id); - rc = pcmk__ipc_msg_append(&client->buffer, data); if (rc == pcmk_rc_ipc_more) { @@ -130,18 +123,13 @@ dispatch_common(qb_ipcs_connection_t *c, void *data, bool privileged) if (client->name == NULL) { const char *value = pcmk__xe_get(msg, PCMK__XA_CIB_CLIENTNAME); - if (value == NULL) { - client->name = pcmk__itoa(client->pid); - } else { - client->name = pcmk__str_copy(value); - } + client->name = pcmk__assert_asprintf("%s.%u", pcmk__s(value, "unknown"), + client->pid); } rc = pcmk__xe_get_flags(msg, PCMK__XA_CIB_CALLOPT, &call_options, cib_none); if (rc != pcmk_rc_ok) { - pcmk__warn("Couldn't parse options from request from IPC client %s: %s", - client->name, pcmk_rc_str(rc)); - pcmk__log_xml_info(msg, "bad-call-opts"); + pcmk__warn("Couldn't parse options from request: %s", pcmk_rc_str(rc)); } /* Requests with cib_transaction set should not be sent to based directly @@ -151,12 +139,11 @@ dispatch_common(qb_ipcs_connection_t *c, void *data, bool privileged) pcmk__warn("Ignoring CIB request from IPC client %s with " "cib_transaction flag set outside of any transaction", client->name); - pcmk__log_xml_info(msg, "no-transaction"); return 0; } if (pcmk__is_set(call_options, cib_sync_call)) { - CRM_LOG_ASSERT(flags & crm_ipc_client_response); + pcmk__assert(pcmk__is_set(flags, crm_ipc_client_response)); // If false, the client has two synchronous events in flight CRM_LOG_ASSERT(client->request_id == 0); @@ -171,8 +158,6 @@ dispatch_common(qb_ipcs_connection_t *c, void *data, bool privileged) CRM_LOG_ASSERT(client->user != NULL); pcmk__update_acl_user(msg, PCMK__XA_CIB_USER, client->user); - pcmk__log_xml_trace(msg, "ipc-request"); - op = pcmk__xe_get(msg, PCMK__XA_CIB_OP); if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) { @@ -201,50 +186,32 @@ dispatch_common(qb_ipcs_connection_t *c, void *data, bool privileged) } pcmk__ipc_send_ack(client, id, flags, PCMK__XE_ACK, NULL, status); - return 0; + + } else { + pcmk__request_t request = { + .ipc_client = client, + .ipc_id = id, + .ipc_flags = flags, + .peer = NULL, + .xml = msg, + .call_options = call_options, + .result = PCMK__UNKNOWN_RESULT, + }; + + request.op = pcmk__xe_get_copy(request.xml, PCMK__XA_CIB_OP); + CRM_CHECK(request.op != NULL, return 0); + + if (pcmk__is_set(request.call_options, cib_sync_call)) { + pcmk__set_request_flags(&request, pcmk__request_sync); + } + + based_handle_request(&request); } - based_process_request(msg, privileged, client); pcmk__xml_free(msg); return 0; } -/*! - * \internal - * \brief Handle a message from a read-only IPC connection - * - * \param[in,out] c Established IPC connection - * \param[in] data The message data read from the connection - this can be - * a complete IPC message or just a part of one if it's - * very large - * \param[in] size Unused - * - * \return 0 in all cases - */ -static int32_t -based_ipc_dispatch_ro(qb_ipcs_connection_t *c, void *data, size_t size) -{ - return dispatch_common(c, data, false); -} - -/*! - * \internal - * \brief Handle a message from a read/write IPC connection - * - * \param[in,out] c Established IPC connection - * \param[in] data The message data read from the connection - this can be - * a complete IPC message or just a part of one if it's - * very large - * \param[in] size Unused - * - * \return 0 in all cases - */ -static int32_t -based_ipc_dispatch_rw(qb_ipcs_connection_t *c, void *data, size_t size) -{ - return dispatch_common(c, data, true); -} - /*! * \internal * \brief Destroy a client IPC connection @@ -279,29 +246,47 @@ based_ipc_destroy(qb_ipcs_connection_t *c) { pcmk__trace("Destroying client connection %p", c); based_ipc_closed(c); - - /* Shut down if this was the last client to leave. - * - * @TODO Is it correct to do this for destroy but not for closed? Other - * daemons handle closed and destroyed connections in the same way. - */ - if (cib_shutdown_flag) { - based_shutdown(0); - } } -struct qb_ipcs_service_handlers ipc_ro_callbacks = { +static struct qb_ipcs_service_handlers ipc_callbacks = { .connection_accept = based_ipc_accept, .connection_created = NULL, - .msg_process = based_ipc_dispatch_ro, + .msg_process = based_ipc_dispatch, .connection_closed = based_ipc_closed, .connection_destroyed = based_ipc_destroy, }; -struct qb_ipcs_service_handlers ipc_rw_callbacks = { - .connection_accept = based_ipc_accept, - .connection_created = NULL, - .msg_process = based_ipc_dispatch_rw, - .connection_closed = based_ipc_closed, - .connection_destroyed = based_ipc_destroy, -}; +/*! + * \internal + * \brief Set up \c based IPC communication + */ +void +based_ipc_init(void) +{ + pcmk__serve_based_ipc(&ipcs, &ipc_callbacks); +} + +/*! + * \internal + * \brief Clean up \c based IPC communication + */ +void +based_ipc_cleanup(void) +{ + if (ipcs != NULL) { + pcmk__drop_all_clients(ipcs); + g_clear_pointer(&ipcs, qb_ipcs_destroy); + } + + /* Drop remote clients here because they're part of the IPC client table and + * must be dropped before \c pcmk__client_cleanup() + */ + based_drop_remote_clients(); + + /* @TODO This is where we would call a based_unregister_handlers() to align + * with other daemons' IPC cleanup functions. Such a function does not yet + * exist; based doesn't use pcmk__request_t yet. + */ + + pcmk__client_cleanup(); +} diff --git a/daemons/based/based_ipc.h b/daemons/based/based_ipc.h new file mode 100644 index 00000000000..ed3cdb72981 --- /dev/null +++ b/daemons/based/based_ipc.h @@ -0,0 +1,16 @@ +/* + * Copyright 2026 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef BASED_IPC__H +#define BASED_IPC__H + +void based_ipc_init(void); +void based_ipc_cleanup(void); + +#endif // BASED_IPC__H diff --git a/daemons/based/based_messages.c b/daemons/based/based_messages.c index 64b0a81aa98..6a7d1932588 100644 --- a/daemons/based/based_messages.c +++ b/daemons/based/based_messages.c @@ -29,112 +29,20 @@ #include "pacemaker-based.h" -/* Maximum number of diffs to ignore while waiting for a resync */ -#define MAX_DIFF_RETRY 5 - -bool based_is_primary = false; - -xmlNode *the_cib = NULL; - -/* Set to 1 when a sync is requested, incremented when a diff is ignored, - * reset to 0 when a sync is received - */ -static int sync_in_progress = 0; - -/*! - * \internal - * \brief Process a \c PCMK__CIB_REQUEST_ABS_DELETE - * - * \param[in] op Ignored - * \param[in] options Ignored - * \param[in] section Ignored - * \param[in] req Ignored - * \param[in] input Ignored - * \param[in] existing_cib Ignored - * \param[in] result_cib Ignored - * \param[in] answer Ignored - * - * \return \c -EINVAL - * - * \note This is unimplemented and simply returns an error. - */ int -based_process_abs_delete(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +based_process_commit_transact(xmlNode *req, xmlNode **cib, xmlNode **answer) { - /* @COMPAT Remove when PCMK__CIB_REQUEST_ABS_DELETE is removed. Note that - * external clients with Pacemaker versions < 3.0.0 can send it. - */ - return -EINVAL; -} - -int -based_process_apply_patch(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) -{ - int rc = pcmk_ok; - - if (sync_in_progress > MAX_DIFF_RETRY) { - /* Don't ignore diffs forever; the last request may have been lost. - * If the diff fails, we'll ask for another full resync. - */ - sync_in_progress = 0; - } - - // The primary instance should never ignore a diff - if (sync_in_progress && !based_is_primary) { - int source[] = { 0, 0, 0 }; - int target[] = { 0, 0, 0 }; - - pcmk__xml_patchset_versions(input, source, target); - - sync_in_progress++; - pcmk__notice("Not applying diff %d.%d.%d -> %d.%d.%d (sync in " - "progress)", - source[0], source[1], source[2], - target[0], target[1], target[2]); - return -pcmk_err_diff_resync; - } - - rc = cib__process_apply_patch(op, options, section, req, input, - existing_cib, result_cib, answer); - pcmk__trace("result: %s (%d), %s", pcmk_strerror(rc), rc, - (based_is_primary? "primary": "secondary")); - - if ((rc == -pcmk_err_diff_resync) && !based_is_primary) { - pcmk__xml_free(*result_cib); - *result_cib = NULL; - send_sync_request(); - - } else if (rc == -pcmk_err_diff_resync) { - rc = -pcmk_err_diff_failed; - if (options & cib_force_diff) { - pcmk__warn("Not requesting full refresh in R/W mode"); - } - } - - return rc; -} - -int -based_process_commit_transact(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, - xmlNode *existing_cib, xmlNode **result_cib, - xmlNode **answer) -{ - /* On success, our caller will activate *result_cib locally, trigger a - * replace notification if appropriate, and sync *result_cib to all nodes. - * On failure, our caller will free *result_cib. + /* On success, our caller will activate *cib locally, trigger a replace + * notification if appropriate, and sync *cib to all nodes. On failure, our + * caller will free *cib. */ int rc = pcmk_rc_ok; + xmlNode *input = cib__get_calldata(req); const char *client_id = pcmk__xe_get(req, PCMK__XA_CIB_CLIENTID); const char *origin = pcmk__xe_get(req, PCMK__XA_SRC); pcmk__client_t *client = pcmk__find_client_by_id(client_id); - rc = based_commit_transaction(input, client, origin, result_cib); - + rc = based_commit_transaction(input, client, origin, cib); if (rc != pcmk_rc_ok) { char *source = based_transaction_source_str(client, origin); @@ -142,137 +50,96 @@ based_process_commit_transact(const char *op, int options, const char *section, pcmk_rc_str(rc)); free(source); } - return pcmk_rc2legacy(rc); -} - -int -based_process_is_primary(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) -{ - pcmk__trace("Processing \"%s\" event", op); - // @COMPAT Pacemaker Remote clients <3.0.0 may send this - return (based_is_primary? pcmk_ok : -EPERM); + return rc; } // @COMPAT: Remove when PCMK__CIB_REQUEST_NOOP is removed int -based_process_noop(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +based_process_noop(xmlNode *req, xmlNode **cib, xmlNode **answer) { - pcmk__trace("Processing \"%s\" event", op); - *answer = NULL; - return pcmk_ok; + return pcmk_rc_ok; } int -based_process_ping(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +based_process_ping(xmlNode *req, xmlNode **cib, xmlNode **answer) { + /* existing_cib and *cib should be identical. In the absence of ACL + * filtering, they should also match the_cib. However, they may be copies + * filtered based on the current CIB user's ACLs. In that case, our log + * messages can use info from the full CIB, and the answer can include the + * digest of the full CIB. But the answer should hide the version attributes + * if they're not visible to the CIB user. + */ const char *host = pcmk__xe_get(req, PCMK__XA_SRC); const char *seq = pcmk__xe_get(req, PCMK__XA_CIB_PING_ID); - char *digest = pcmk__digest_xml(the_cib, true); + char *digest = pcmk__digest_xml(based_cib, true); + xmlNode *shallow = NULL; - xmlNode *wrapper = NULL; - - pcmk__trace("Processing \"%s\" event %s from %s", op, seq, host); *answer = pcmk__xe_create(NULL, PCMK__XE_PING_RESPONSE); pcmk__xe_set(*answer, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); pcmk__xe_set(*answer, PCMK_XA_DIGEST, digest); pcmk__xe_set(*answer, PCMK__XA_CIB_PING_ID, seq); - wrapper = pcmk__xe_create(*answer, PCMK__XE_CIB_CALLDATA); - - if (the_cib != NULL) { - pcmk__if_tracing( - { - /* Append additional detail so the receiver can log the - * differences - */ - pcmk__xml_copy(wrapper, the_cib); - }, - { - // Always include at least the version details - const char *name = (const char *) the_cib->name; - xmlNode *shallow = pcmk__xe_create(wrapper, name); - - pcmk__xe_copy_attrs(shallow, the_cib, pcmk__xaf_none); - } - ); - } + // Use *cib so that ACL filtering is applied to the answer + shallow = pcmk__xe_create(NULL, (const char *) (*cib)->name); + pcmk__xe_copy_attrs(shallow, *cib, pcmk__xaf_none); + cib__set_calldata(*answer, shallow); + pcmk__xml_free(shallow); pcmk__info("Reporting our current digest to %s: %s for %s.%s.%s", host, digest, - pcmk__xe_get(existing_cib, PCMK_XA_ADMIN_EPOCH), - pcmk__xe_get(existing_cib, PCMK_XA_EPOCH), - pcmk__xe_get(existing_cib, PCMK_XA_NUM_UPDATES)); + pcmk__xe_get(based_cib, PCMK_XA_ADMIN_EPOCH), + pcmk__xe_get(based_cib, PCMK_XA_EPOCH), + pcmk__xe_get(based_cib, PCMK_XA_NUM_UPDATES)); free(digest); - return pcmk_ok; + return pcmk_rc_ok; } int -based_process_primary(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode ** answer) +based_process_primary(xmlNode *req, xmlNode **cib, xmlNode **answer) { - pcmk__trace("Processing \"%s\" event", op); + // This should always be processed locally and never addressed to any host + CRM_CHECK(pcmk__xe_get(req, PCMK__XA_CIB_HOST) == NULL, return EOPNOTSUPP); - if (!based_is_primary) { + if (!based_get_local_node_dc()) { pcmk__info("We are now in R/W mode"); - based_is_primary = true; + based_set_local_node_dc(true); } else { pcmk__debug("We are still in R/W mode"); } - return pcmk_ok; + return pcmk_rc_ok; } int -based_process_replace(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +based_process_schemas(xmlNode *req, xmlNode **cib, xmlNode **answer) { - int rc = cib__process_replace(op, options, section, req, input, - existing_cib, result_cib, answer); - - if ((rc == pcmk_ok) && pcmk__xe_is(input, PCMK_XE_CIB)) { - sync_in_progress = 0; - } - return rc; -} - -int -based_process_schemas(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) -{ - xmlNode *wrapper = NULL; xmlNode *data = NULL; const char *after_ver = NULL; GList *schemas = NULL; GList *already_included = NULL; + // This should always be processed locally and never addressed to any host + CRM_CHECK(pcmk__xe_get(req, PCMK__XA_CIB_HOST) == NULL, return EOPNOTSUPP); + *answer = pcmk__xe_create(NULL, PCMK__XA_SCHEMAS); - wrapper = pcmk__xe_first_child(req, PCMK__XE_CIB_CALLDATA, NULL, NULL); - data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); + data = cib__get_calldata(req); if (data == NULL) { pcmk__warn("No data specified in request"); - return -EPROTO; + return EPROTO; } after_ver = pcmk__xe_get(data, PCMK_XA_VERSION); if (after_ver == NULL) { pcmk__warn("No version specified in request"); - return -EPROTO; + return EPROTO; } /* The client requested all schemas after the latest one we know about, which @@ -280,7 +147,7 @@ based_process_schemas(const char *op, int options, const char *section, * with no schemas. */ if (pcmk__str_eq(after_ver, pcmk__highest_schema_name(), pcmk__str_none)) { - return pcmk_ok; + return pcmk_rc_ok; } schemas = pcmk__schema_files_later_than(after_ver); @@ -291,75 +158,55 @@ based_process_schemas(const char *op, int options, const char *section, g_list_free_full(schemas, free); g_list_free_full(already_included, free); - return pcmk_ok; + return pcmk_rc_ok; } int -based_process_secondary(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +based_process_secondary(xmlNode *req, xmlNode **cib, xmlNode **answer) { - pcmk__trace("Processing \"%s\" event", op); + // This should always be processed locally and never addressed to any host + CRM_CHECK(pcmk__xe_get(req, PCMK__XA_CIB_HOST) == NULL, return EOPNOTSUPP); - if (based_is_primary) { + if (based_get_local_node_dc()) { pcmk__info("We are now in R/O mode"); - based_is_primary = false; + based_set_local_node_dc(false); } else { pcmk__debug("We are still in R/O mode"); } - return pcmk_ok; + return pcmk_rc_ok; } int -based_process_shutdown(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +based_process_shutdown(xmlNode *req, xmlNode **cib, xmlNode **answer) { - const char *host = pcmk__xe_get(req, PCMK__XA_SRC); - - *answer = NULL; - - if (pcmk__xe_get(req, PCMK__XA_CIB_ISREPLYTO) == NULL) { - pcmk__info("Peer %s is requesting to shut down", host); - return pcmk_ok; - } - - if (!cib_shutdown_flag) { - pcmk__err("Peer %s mistakenly thinks we wanted to shut down", host); - return -EINVAL; - } - - pcmk__info("Exiting after %s acknowledged our shutdown request", host); - based_terminate(CRM_EX_OK); - return pcmk_ok; + /* @COMPAT Remove when PCMK__CIB_REQUEST_SHUTDOWN is removed. Nodes with + * Pacemaker versions earlier than 3.0.2 send a shutdown request and expect + * a reply. + */ + return pcmk_rc_ok; } int -based_process_sync_to_all(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +based_process_sync(xmlNode *req, xmlNode **cib, xmlNode **answer) { return sync_our_cib(req, true); } int -based_process_sync_to_one(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +based_process_upgrade(xmlNode *req, xmlNode **cib, xmlNode **answer) { - return sync_our_cib(req, false); -} - -int -based_process_upgrade(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) -{ - int rc = pcmk_ok; + int rc = pcmk_rc_ok; - *answer = NULL; + xmlNode *scratch = NULL; + const char *host = pcmk__xe_get(req, PCMK__XA_SRC); + const char *client_id = pcmk__xe_get(req, PCMK__XA_CIB_CLIENTID); + const char *call_opts = pcmk__xe_get(req, PCMK__XA_CIB_CALLOPT); + const char *call_id = pcmk__xe_get(req, PCMK__XA_CIB_CALLID); + const char *original_schema = NULL; + const char *new_schema = NULL; + pcmk__node_status_t *origin = NULL; if (pcmk__xe_get(req, PCMK__XA_CIB_SCHEMA_MAX) != NULL) { /* The originator of an upgrade request sends it to the DC, without @@ -367,107 +214,79 @@ based_process_upgrade(const char *op, int options, const char *section, * re-broadcasts the request with PCMK__XA_CIB_SCHEMA_MAX, and each node * performs the upgrade (and notifies its local clients) here. */ - return cib__process_upgrade(op, options, section, req, input, - existing_cib, result_cib, answer); - - } else { - xmlNode *scratch = pcmk__xml_copy(NULL, existing_cib); - const char *host = pcmk__xe_get(req, PCMK__XA_SRC); - const char *original_schema = NULL; - const char *new_schema = NULL; - const char *client_id = pcmk__xe_get(req, PCMK__XA_CIB_CLIENTID); - const char *call_opts = pcmk__xe_get(req, PCMK__XA_CIB_CALLOPT); - const char *call_id = pcmk__xe_get(req, PCMK__XA_CIB_CALLID); - - pcmk__trace("Processing \"%s\" event", op); - original_schema = pcmk__xe_get(existing_cib, PCMK_XA_VALIDATE_WITH); - if (original_schema == NULL) { - pcmk__info("Rejecting upgrade request from %s: No " - PCMK_XA_VALIDATE_WITH, - host); - return -pcmk_err_cib_corrupt; - } + return cib__process_upgrade(req, cib, answer); + } - rc = pcmk__update_schema(&scratch, NULL, true, true); - rc = pcmk_rc2legacy(rc); - new_schema = pcmk__xe_get(scratch, PCMK_XA_VALIDATE_WITH); + scratch = pcmk__xml_copy(NULL, *cib); - if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) { - xmlNode *up = pcmk__xe_create(NULL, __func__); + original_schema = pcmk__xe_get(*cib, PCMK_XA_VALIDATE_WITH); + if (original_schema == NULL) { + pcmk__info("Rejecting upgrade request from %s: No " + PCMK_XA_VALIDATE_WITH, host); + return pcmk_rc_cib_corrupt; + } - rc = pcmk_ok; - pcmk__notice("Upgrade request from %s verified", host); + rc = pcmk__update_schema(&scratch, NULL, true); + new_schema = pcmk__xe_get(scratch, PCMK_XA_VALIDATE_WITH); - pcmk__xe_set(up, PCMK__XA_T, PCMK__VALUE_CIB); - pcmk__xe_set(up, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_UPGRADE); - pcmk__xe_set(up, PCMK__XA_CIB_SCHEMA_MAX, new_schema); - pcmk__xe_set(up, PCMK__XA_CIB_DELEGATED_FROM, host); - pcmk__xe_set(up, PCMK__XA_CIB_CLIENTID, client_id); - pcmk__xe_set(up, PCMK__XA_CIB_CALLOPT, call_opts); - pcmk__xe_set(up, PCMK__XA_CIB_CALLID, call_id); + if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) { + xmlNode *up = pcmk__xe_create(NULL, __func__); - pcmk__cluster_send_message(NULL, pcmk_ipc_based, up); + rc = pcmk_rc_ok; + pcmk__notice("Upgrade request from %s verified", host); - pcmk__xml_free(up); + pcmk__xe_set(up, PCMK__XA_T, PCMK__VALUE_CIB); + pcmk__xe_set(up, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_UPGRADE); + pcmk__xe_set(up, PCMK__XA_CIB_SCHEMA_MAX, new_schema); + pcmk__xe_set(up, PCMK__XA_CIB_DELEGATED_FROM, host); + pcmk__xe_set(up, PCMK__XA_CIB_CLIENTID, client_id); + pcmk__xe_set(up, PCMK__XA_CIB_CALLOPT, call_opts); + pcmk__xe_set(up, PCMK__XA_CIB_CALLID, call_id); - } else if(rc == pcmk_ok) { - rc = -pcmk_err_schema_unchanged; - } + pcmk__cluster_send_message(NULL, pcmk_ipc_based, up); - if (rc != pcmk_ok) { - // Notify originating peer so it can notify its local clients - pcmk__node_status_t *origin = NULL; - - origin = pcmk__search_node_caches(0, host, NULL, - pcmk__node_search_cluster_member); - - pcmk__info("Rejecting upgrade request from %s: %s " - QB_XS " rc=%d peer=%s", - host, pcmk_strerror(rc), rc, - ((origin != NULL)? origin->name : "lost")); - - if (origin) { - xmlNode *up = pcmk__xe_create(NULL, __func__); - - pcmk__xe_set(up, PCMK__XA_T, PCMK__VALUE_CIB); - pcmk__xe_set(up, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_UPGRADE); - pcmk__xe_set(up, PCMK__XA_CIB_DELEGATED_FROM, host); - pcmk__xe_set(up, PCMK__XA_CIB_ISREPLYTO, host); - pcmk__xe_set(up, PCMK__XA_CIB_CLIENTID, client_id); - pcmk__xe_set(up, PCMK__XA_CIB_CALLOPT, call_opts); - pcmk__xe_set(up, PCMK__XA_CIB_CALLID, call_id); - pcmk__xe_set_int(up, PCMK__XA_CIB_UPGRADE_RC, rc); - if (!pcmk__cluster_send_message(origin, pcmk_ipc_based, up)) { - pcmk__warn("Could not send CIB upgrade result to %s", host); - } - pcmk__xml_free(up); - } - } - pcmk__xml_free(scratch); + pcmk__xml_free(up); + goto done; } - return rc; -} -void -send_sync_request(void) -{ - xmlNode *sync_me = pcmk__xe_create(NULL, "sync-me"); - pcmk__node_status_t *peer = NULL; - - pcmk__info("Requesting re-sync from all peers"); - sync_in_progress = 1; + if (rc == pcmk_rc_ok) { + rc = pcmk_rc_schema_unchanged; + } - pcmk__xe_set(sync_me, PCMK__XA_T, PCMK__VALUE_CIB); - pcmk__xe_set(sync_me, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_SYNC_TO_ONE); - pcmk__xe_set(sync_me, PCMK__XA_CIB_DELEGATED_FROM, OUR_NODENAME); + // Notify originating peer so it can notify its local clients + origin = pcmk__search_node_caches(0, host, NULL, + pcmk__node_search_cluster_member); + + pcmk__info("Rejecting upgrade request from %s: %s " QB_XS " rc=%d peer=%s", + host, pcmk_rc_str(rc), rc, + ((origin != NULL)? origin->name : "lost")); + + if (origin != NULL) { + xmlNode *up = pcmk__xe_create(NULL, __func__); + + pcmk__xe_set(up, PCMK__XA_T, PCMK__VALUE_CIB); + pcmk__xe_set(up, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_UPGRADE); + pcmk__xe_set(up, PCMK__XA_CIB_DELEGATED_FROM, host); + pcmk__xe_set(up, PCMK__XA_CIB_ISREPLYTO, host); + pcmk__xe_set(up, PCMK__XA_CIB_CLIENTID, client_id); + pcmk__xe_set(up, PCMK__XA_CIB_CALLOPT, call_opts); + pcmk__xe_set(up, PCMK__XA_CIB_CALLID, call_id); + pcmk__xe_set_int(up, PCMK__XA_CIB_UPGRADE_RC, pcmk_rc2legacy(rc)); + if (!pcmk__cluster_send_message(origin, pcmk_ipc_based, up)) { + pcmk__warn("Could not send CIB upgrade result to %s", host); + } + pcmk__xml_free(up); + } - pcmk__cluster_send_message(peer, pcmk_ipc_based, sync_me); - pcmk__xml_free(sync_me); +done: + pcmk__xml_free(scratch); + return rc; } static xmlNode * -cib_msg_copy(xmlNode *msg) +cib_msg_copy(const xmlNode *msg) { + // @FIXME Copying CIB_CALLOPT seems problematic if it has cib_discard_reply static const char *field_list[] = { PCMK__XA_T, PCMK__XA_CIB_CLIENTID, @@ -499,18 +318,16 @@ cib_msg_copy(xmlNode *msg) } int -sync_our_cib(xmlNode *request, bool all) +sync_our_cib(const xmlNode *request, bool all) { - int result = pcmk_ok; + int rc = pcmk_rc_ok; char *digest = NULL; const char *host = pcmk__xe_get(request, PCMK__XA_SRC); const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); pcmk__node_status_t *peer = NULL; xmlNode *replace_request = NULL; - xmlNode *wrapper = NULL; - CRM_CHECK(the_cib != NULL, return -EINVAL); - CRM_CHECK(all || (host != NULL), return -EINVAL); + CRM_CHECK(all || (host != NULL), return EINVAL); pcmk__debug("Syncing CIB to %s", (all? "all peers" : host)); @@ -531,19 +348,18 @@ sync_our_cib(xmlNode *request, bool all) pcmk__xe_set_bool(replace_request, PCMK__XA_CIB_UPDATE, true); pcmk__xe_set(replace_request, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); - digest = pcmk__digest_xml(the_cib, true); + digest = pcmk__digest_xml(based_cib, true); pcmk__xe_set(replace_request, PCMK_XA_DIGEST, digest); - wrapper = pcmk__xe_create(replace_request, PCMK__XE_CIB_CALLDATA); - pcmk__xml_copy(wrapper, the_cib); + cib__set_calldata(replace_request, based_cib); if (!all) { peer = pcmk__get_node(0, host, NULL, pcmk__node_search_cluster_member); } if (!pcmk__cluster_send_message(peer, pcmk_ipc_based, replace_request)) { - result = -ENOTCONN; + rc = ENOTCONN; } pcmk__xml_free(replace_request); free(digest); - return result; + return rc; } diff --git a/daemons/based/based_messages.h b/daemons/based/based_messages.h index fee6e089cb8..e1167850186 100644 --- a/daemons/based/based_messages.h +++ b/daemons/based/based_messages.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -14,72 +14,18 @@ #include // xmlNode * -extern bool based_is_primary; -extern xmlNode *the_cib; - -int based_process_abs_delete(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, - xmlNode *existing_cib, xmlNode **result_cib, - xmlNode **answer); - -int based_process_apply_patch(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, - xmlNode *existing_cib, xmlNode **result_cib, - xmlNode **answer); - -int based_process_commit_transact(const char *op, int options, - const char *section, xmlNode *req, - xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int based_process_is_primary(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, - xmlNode *existing_cib, xmlNode **result_cib, - xmlNode **answer); - -int based_process_noop(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int based_process_ping(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int based_process_primary(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int based_process_replace(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int based_process_schemas(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int based_process_secondary(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int based_process_shutdown(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int based_process_sync_to_all(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, - xmlNode *existing_cib, xmlNode **result_cib, - xmlNode **answer); - -int based_process_sync_to_one(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, - xmlNode *existing_cib, xmlNode **result_cib, - xmlNode **answer); - -int based_process_upgrade(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -void send_sync_request(void); -int sync_our_cib(xmlNode *request, bool all); +int based_process_apply_patch(xmlNode *req, xmlNode **cib, xmlNode **answer); +int based_process_commit_transact(xmlNode *req, xmlNode **cib, + xmlNode **answer); +int based_process_noop(xmlNode *req, xmlNode **cib, xmlNode **answer); +int based_process_ping(xmlNode *req, xmlNode **cib, xmlNode **answer); +int based_process_primary(xmlNode *req, xmlNode **cib, xmlNode **answer); +int based_process_schemas(xmlNode *req, xmlNode **cib, xmlNode **answer); +int based_process_secondary(xmlNode *req, xmlNode **cib, xmlNode **answer); +int based_process_shutdown(xmlNode *req, xmlNode **cib, xmlNode **answer); +int based_process_sync(xmlNode *req, xmlNode **cib, xmlNode **answer); +int based_process_upgrade(xmlNode *req, xmlNode **cib, xmlNode **answer); + +int sync_our_cib(const xmlNode *request, bool all); #endif // BASED_MESSAGES__H diff --git a/daemons/based/based_notify.c b/daemons/based/based_notify.c index 9d2d0c41d4e..21458325b79 100644 --- a/daemons/based/based_notify.c +++ b/daemons/based/based_notify.c @@ -199,9 +199,7 @@ cib_notify_send(const xmlNode *xml) } void -based_diff_notify(const char *op, int result, const char *call_id, - const char *client_id, const char *client_name, - const char *origin, xmlNode *update, xmlNode *diff) +based_diff_notify(const xmlNode *request, int rc, xmlNode *diff) { xmlNode *update_msg = NULL; xmlNode *wrapper = NULL; @@ -212,14 +210,27 @@ based_diff_notify(const char *op, int result, const char *call_id, update_msg = pcmk__xe_create(NULL, PCMK__XE_NOTIFY); + /* We could simplify by copying all attributes from request. We would just + * have to ensure that there are never "private" attributes that we want to + * hide from external clients with notify callbacks. + */ pcmk__xe_set(update_msg, PCMK__XA_T, PCMK__VALUE_CIB_NOTIFY); pcmk__xe_set(update_msg, PCMK__XA_SUBT, PCMK__VALUE_CIB_DIFF_NOTIFY); - pcmk__xe_set(update_msg, PCMK__XA_CIB_OP, op); - pcmk__xe_set(update_msg, PCMK__XA_CIB_CLIENTID, client_id); - pcmk__xe_set(update_msg, PCMK__XA_CIB_CLIENTNAME, client_name); - pcmk__xe_set(update_msg, PCMK__XA_CIB_CALLID, call_id); - pcmk__xe_set(update_msg, PCMK__XA_SRC, origin); - pcmk__xe_set_int(update_msg, PCMK__XA_CIB_RC, result); + + pcmk__xe_set(update_msg, PCMK__XA_CIB_OP, + pcmk__xe_get(request, PCMK__XA_CIB_OP)); + + pcmk__xe_set(update_msg, PCMK__XA_CIB_CLIENTID, + pcmk__xe_get(request, PCMK__XA_CIB_CLIENTID)); + + pcmk__xe_set(update_msg, PCMK__XA_CIB_CLIENTNAME, + pcmk__xe_get(request, PCMK__XA_CIB_CLIENTNAME)); + + pcmk__xe_set(update_msg, PCMK__XA_CIB_CALLID, + pcmk__xe_get(request, PCMK__XA_CIB_CALLID)); + + pcmk__xe_set(update_msg, PCMK__XA_SRC, pcmk__xe_get(request, PCMK__XA_SRC)); + pcmk__xe_set_int(update_msg, PCMK__XA_CIB_RC, pcmk_rc2legacy(rc)); wrapper = pcmk__xe_create(update_msg, PCMK__XE_CIB_UPDATE_RESULT); pcmk__xml_copy(wrapper, diff); diff --git a/daemons/based/based_notify.h b/daemons/based/based_notify.h index 1735cac54d9..aec79e7b0b7 100644 --- a/daemons/based/based_notify.h +++ b/daemons/based/based_notify.h @@ -16,8 +16,6 @@ int based_update_notify_flags(const xmlNode *xml, pcmk__client_t *client); -void based_diff_notify(const char *op, int result, const char *call_id, - const char *client_id, const char *client_name, - const char *origin, xmlNode *update, xmlNode *diff); +void based_diff_notify(const xmlNode *request, int rc, xmlNode *diff); #endif // BASED_NOTIFY__H diff --git a/daemons/based/based_operation.c b/daemons/based/based_operation.c index 9189a374354..1e0699c775c 100644 --- a/daemons/based/based_operation.c +++ b/daemons/based/based_operation.c @@ -17,25 +17,22 @@ #include "pacemaker-based.h" static const cib__op_fn_t op_functions[] = { - [cib__op_abs_delete] = based_process_abs_delete, - [cib__op_apply_patch] = based_process_apply_patch, + [cib__op_apply_patch] = cib__process_apply_patch, [cib__op_bump] = cib__process_bump, [cib__op_commit_transact] = based_process_commit_transact, [cib__op_create] = cib__process_create, [cib__op_delete] = cib__process_delete, [cib__op_erase] = cib__process_erase, - [cib__op_is_primary] = based_process_is_primary, [cib__op_modify] = cib__process_modify, [cib__op_noop] = based_process_noop, [cib__op_ping] = based_process_ping, [cib__op_primary] = based_process_primary, [cib__op_query] = cib__process_query, - [cib__op_replace] = based_process_replace, + [cib__op_replace] = cib__process_replace, [cib__op_schemas] = based_process_schemas, [cib__op_secondary] = based_process_secondary, [cib__op_shutdown] = based_process_shutdown, - [cib__op_sync_to_all] = based_process_sync_to_all, - [cib__op_sync_to_one] = based_process_sync_to_one, + [cib__op_sync] = based_process_sync, [cib__op_upgrade] = based_process_upgrade, }; diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c index 174eda04cd5..5261ea273dd 100644 --- a/daemons/based/based_remote.c +++ b/daemons/based/based_remote.c @@ -11,7 +11,6 @@ #include // htons #include // errno, EAGAIN -#include // getgrgid, getgrnam, group #include // PRIx64 #include // sockaddr_in, INADDR_ANY #include @@ -46,8 +45,8 @@ static pcmk__tls_t *tls = NULL; -int remote_fd = 0; -int remote_tls_fd = 0; +static mainloop_io_t *tcp_listener = NULL; +static mainloop_io_t *tls_listener = NULL; // @TODO This is rather short for someone to type their password #define REMOTE_AUTH_TIMEOUT 10000 @@ -85,35 +84,108 @@ remote_auth_timeout_cb(gpointer data) /*! * \internal - * \brief Check whether a given user is a member of \c CRM_DAEMON_GROUP + * \brief Read (more) TLS handshake data from a client * - * \param[in] user User name + * \param[in,out] client IPC client * - * \return \c true if \p user is a member of \c CRM_DAEMON_GROUP, or \c false - * otherwise + * \retval 0 on success or more data needed + * \retval -1 on error + */ +static int +based_read_handshake_data(pcmk__client_t *client) +{ + int rc = pcmk__read_handshake_data(client); + + if (rc == EAGAIN) { + /* No more data is available at the moment. Just return for now; we'll + * get invoked again once the client sends more. + */ + return 0; + } + + if (rc != pcmk_rc_ok) { + return -1; + } + + if (client->remote->auth_timeout != 0) { + g_source_remove(client->remote->auth_timeout); + client->remote->auth_timeout = 0; + } + + pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete); + pcmk__debug("Completed TLS handshake with remote client %s", + pcmk__client_name(client)); + + /* Now that the handshake is done, see if any client TLS certificate is + * close to its expiration date and log if so. If a TLS certificate is not + * in use, this function will just return so we don't need to check for the + * session type here. + */ + pcmk__tls_check_cert_expiration(client->remote->tls_session); + + // Require the client to authenticate within this time + client->remote->auth_timeout = pcmk__create_timer(REMOTE_AUTH_TIMEOUT, + remote_auth_timeout_cb, + client); + return 0; +} + +/*! + * \internal + * \brief Parse a remote client auth message + * + * This first validates that the message is a well-formed remote client + * authentication request and then extracts the username and password + * attributes. + * + * \param[in] msg Message from remote client + * \param[out] user Where to store username + * \param[out] password Where to store password + * \param[in] client_name Remote client name (for logging only) + * + * \return \c true if \p msg is a well-formed authentication request, or + * \c false otherwise. + * + * \note \p *user and \p *password are set to \c NULL on error. */ static bool -is_daemon_group_member(const char *user) +parse_auth_message(const xmlNode *msg, const char **user, const char **password, + const char *client_name) { - const struct group *group = getgrnam(CRM_DAEMON_GROUP); + const char *op = NULL; - if (group == NULL) { - pcmk__err("Rejecting remote client: " CRM_DAEMON_GROUP " is not a " - "valid group"); + if (msg == NULL) { + pcmk__warn("Rejecting remote client %s: Unrecognizable message", + client_name); return false; } - for (const char *const *member = (const char *const *) group->gr_mem; - *member != NULL; member++) { + if (!pcmk__xe_is(msg, PCMK__XE_CIB_COMMAND)) { + pcmk__warn("Rejecting remote client %s: Expected " + "element '" PCMK__XE_CIB_COMMAND "', got '%s'", client_name, + (const char *) msg->name); + return false; + } - if (pcmk__str_eq(user, *member, pcmk__str_none)) { - return true; - } + op = pcmk__xe_get(msg, PCMK_XA_OP); + if (!pcmk__str_eq(op, "authenticate", pcmk__str_none)) { + pcmk__warn("Rejecting remote client %s: Expected " + PCMK_XA_OP "='authenticate', got " PCMK_XA_OP "='%s'", op); + return false; } - pcmk__notice("Rejecting remote client: User %s is not a member of group %s", - user, CRM_DAEMON_GROUP); - return false; + *user = pcmk__xe_get(msg, PCMK_XA_USER); + *password = pcmk__xe_get(msg, PCMK__XA_PASSWORD); + + if ((*user == NULL) || (*password == NULL)) { + pcmk__warn("Rejecting remote client %s: No %s given", client_name, + ((*user == NULL)? "username" : "password")); + *user = NULL; + *password = NULL; + return false; + } + + return true; } #ifdef HAVE_PAM @@ -177,14 +249,15 @@ construct_pam_passwd(int num_msg, const struct pam_message **msg, * \internal * \brief Verify the username and password passed for a remote CIB connection * - * \param[in] user Username passed for remote CIB connection - * \param[in] passwd Password passed for remote CIB connection + * \param[in] user Username passed for remote CIB connection + * \param[in] passwd Password passed for remote CIB connection + * \param[in] client_name Remote client name (for logging only) * * \return \c true if the username and password are accepted, otherwise \c false * \note This function rejects all credentials when built without PAM support. */ static bool -authenticate_user(const char *user, const char *passwd) +authenticate_user(const char *user, const char *passwd, const char *client_name) { #ifdef HAVE_PAM int rc = 0; @@ -207,16 +280,17 @@ authenticate_user(const char *user, const char *passwd) rc = pam_start(pam_name, user, &p_conv, &pam_h); if (rc != PAM_SUCCESS) { - pcmk__warn("Rejecting remote client for user %s because PAM " - "initialization failed: %s", - user, pam_strerror(pam_h, rc)); + pcmk__warn("Rejecting remote client %s because PAM initialization " + "failed for user %s: %s", client_name, user, + pam_strerror(pam_h, rc)); goto bail; } // Check user credentials rc = pam_authenticate(pam_h, PAM_SILENT); if (rc != PAM_SUCCESS) { - pcmk__notice("Access for remote user %s denied: %s", user, + pcmk__notice("Rejecting remote client %s because PAM authentication " + "failed for user %s: %s", client_name, user, pam_strerror(pam_h, rc)); goto bail; } @@ -227,33 +301,34 @@ authenticate_user(const char *user, const char *passwd) */ rc = pam_get_item(pam_h, PAM_USER, &p_user); if (rc != PAM_SUCCESS) { - pcmk__warn("Rejecting remote client for user %s because PAM failed to " - "return final user name: %s", + pcmk__warn("Rejecting remote client %s because PAM failed to return " + "the authenticated user name for user %s: %s", client_name, user, pam_strerror(pam_h, rc)); goto bail; } + if (p_user == NULL) { - pcmk__warn("Rejecting remote client for user %s because PAM returned " - "no final user name", - user); + pcmk__warn("Rejecting remote client %s because PAM returned no " + "authenticated user name for user %s", client_name, user); goto bail; } // @TODO Why do we require these to match? if (!pcmk__str_eq(p_user, user, pcmk__str_none)) { - pcmk__warn("Rejecting remote client for user %s because PAM returned " - "different final user name %s", - user, p_user); + pcmk__warn("Rejecting remote client %s because PAM returned " + "non-matching authenticated user name %s for user %s", + client_name, user, p_user); goto bail; } // Check user account restrictions (expiration, etc.) rc = pam_acct_mgmt(pam_h, PAM_SILENT); if (rc != PAM_SUCCESS) { - pcmk__notice("Access for remote user %s denied: %s", user, - pam_strerror(pam_h, rc)); + pcmk__warn("Rejecting remote client %s because PAM denied access to " + "user %s", client_name, user, pam_strerror(pam_h, rc)); goto bail; } + pass = true; bail: @@ -261,95 +336,106 @@ authenticate_user(const char *user, const char *passwd) return pass; #else // @TODO Implement for non-PAM environments - pcmk__warn("Rejecting remote user %s because this build does not have PAM " - "support", - user); + pcmk__warn("Rejecting remote client %s (user %s) because this build does " + "not have PAM support", client_name, user); return false; #endif } +/*! + * \internal + * \brief Try to authenticate a remote client based on the message in its buffer + * + * Read the first message from the client's buffer. Validate that it's a well- + * formed remote client authentication request. Parse the username and password. + * Ensure that the user is a member of \c CRM_DAEMON_GROUP and use the + * credentials to authenticate the user via PAM (if available). Finally, on + * success, set the \c pcmk__client_authenticated flag, and send a reply + * informing the client of its success and its client ID. + * + * \param[in,out] client Remote CIB manager client + * + * \return \c true if \p client authenticated successfully, or \c false + * otherwise + */ static bool -cib_remote_auth(xmlNode * login) +based_remote_client_auth(pcmk__client_t *client) { + // @TODO If we want to debug/trace-log an auth message, strip password first const char *user = NULL; - const char *pass = NULL; - const char *tmp = NULL; + const char *password = NULL; + const char *client_name = pcmk__client_name(client); + xmlNode *msg = NULL; + xmlNode *cib_result = NULL; - if (login == NULL) { - return false; + msg = pcmk__remote_message_xml(client->remote); + if (!parse_auth_message(msg, &user, &password, client_name)) { + // Error already logged + goto done; } - if (!pcmk__xe_is(login, PCMK__XE_CIB_COMMAND)) { - pcmk__warn("Rejecting remote client: Unrecognizable message (element " - "'%s' not '" PCMK__XE_CIB_COMMAND "')", - login->name); - pcmk__log_xml_debug(login, "bad"); - return false; + if (!pcmk__is_user_in_group(user, CRM_DAEMON_GROUP)) { + pcmk__notice("Rejecting remote client %s: User %s is not a member of " + "group %s", client_name, user, CRM_DAEMON_GROUP); + goto done; } - tmp = pcmk__xe_get(login, PCMK_XA_OP); - if (!pcmk__str_eq(tmp, "authenticate", pcmk__str_casei)) { - pcmk__warn("Rejecting remote client: Unrecognizable message (operation " - "'%s' not 'authenticate')", - tmp); - pcmk__log_xml_debug(login, "bad"); - return false; + if (!authenticate_user(user, password, client_name)) { + // Error already logged + goto done; } - user = pcmk__xe_get(login, PCMK_XA_USER); - pass = pcmk__xe_get(login, PCMK__XA_PASSWORD); - if (!user || !pass) { - pcmk__warn("Rejecting remote client: No %s given", - ((user == NULL)? "username" : "password")); - pcmk__log_xml_debug(login, "bad"); - return false; + // @FIXME Should this be done regardless of whether auth succeeds? + if (client->remote->auth_timeout != 0) { + g_source_remove(client->remote->auth_timeout); + client->remote->auth_timeout = 0; + } + + pcmk__set_client_flags(client, pcmk__client_authenticated); + + // @TODO What sets PCMK_XA_NAME? Added by commit 22832641. + client->name = pcmk__xe_get_copy(msg, PCMK_XA_NAME); + if (client->name == NULL) { + client->name = pcmk__str_copy(client->id); } - pcmk__log_xml_debug(login, "auth"); + client->user = pcmk__str_copy(user); + + // Setting client->name may have changed the return value + client_name = pcmk__client_name(client); + + pcmk__notice("Remote connection accepted for authenticated user %s " + QB_XS " client %s", client->user, client_name); + + // Notify client of success and of its ID + cib_result = pcmk__xe_create(NULL, PCMK__XE_CIB_RESULT); + pcmk__xe_set(cib_result, PCMK__XA_CIB_OP, CRM_OP_REGISTER); + pcmk__xe_set(cib_result, PCMK__XA_CIB_CLIENTID, client->id); - return is_daemon_group_member(user) && authenticate_user(user, pass); + pcmk__remote_send_xml(client->remote, cib_result); + +done: + pcmk__xml_free(msg); + pcmk__xml_free(cib_result); + return pcmk__is_set(client->flags, pcmk__client_authenticated); } static void -cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command) +based_remote_client_message(pcmk__client_t *client, xmlNode *msg) { int rc = pcmk_rc_ok; uint32_t call_options = cib_none; - const char *op = pcmk__xe_get(command, PCMK__XA_CIB_OP); + const char *op = pcmk__xe_get(msg, PCMK__XA_CIB_OP); - if (!pcmk__xe_is(command, PCMK__XE_CIB_COMMAND)) { - pcmk__log_xml_trace(command, "bad"); + if (!pcmk__xe_is(msg, PCMK__XE_CIB_COMMAND)) { + pcmk__debug("Unrecognizable remote data from client %s", + pcmk__client_name(client)); return; } - if (client->name == NULL) { - client->name = pcmk__str_copy(client->id); - } - - /* unset dangerous options */ - pcmk__xe_remove_attr(command, PCMK__XA_SRC); - pcmk__xe_remove_attr(command, PCMK__XA_CIB_HOST); - pcmk__xe_remove_attr(command, PCMK__XA_CIB_UPDATE); - - pcmk__xe_set(command, PCMK__XA_T, PCMK__VALUE_CIB); - pcmk__xe_set(command, PCMK__XA_CIB_CLIENTID, client->id); - pcmk__xe_set(command, PCMK__XA_CIB_CLIENTNAME, client->name); - pcmk__xe_set(command, PCMK__XA_CIB_USER, client->user); - - if (pcmk__xe_get(command, PCMK__XA_CIB_CALLID) == NULL) { - char *call_uuid = pcmk__generate_uuid(); - - /* fix the command */ - pcmk__xe_set(command, PCMK__XA_CIB_CALLID, call_uuid); - free(call_uuid); - } - - rc = pcmk__xe_get_flags(command, PCMK__XA_CIB_CALLOPT, &call_options, - cib_none); + rc = pcmk__xe_get_flags(msg, PCMK__XA_CIB_CALLOPT, &call_options, cib_none); if (rc != pcmk_rc_ok) { - pcmk__warn("Couldn't parse options from request from remote client %s: " - "%s", client->name, pcmk_rc_str(rc)); - pcmk__log_xml_info(command, "bad-call-opts"); + pcmk__warn("Couldn't parse options from request: %s", pcmk_rc_str(rc)); } /* Requests with cib_transaction set should not be sent to based directly @@ -358,26 +444,89 @@ cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command) if (pcmk__is_set(call_options, cib_transaction)) { pcmk__warn("Ignoring CIB request from remote client %s with " "cib_transaction flag set outside of any transaction", - client->name); - pcmk__log_xml_info(command, "no-transaction"); + pcmk__client_name(client)); return; } - pcmk__log_xml_trace(command, "remote-request"); + /* Unset dangerous options. + * + * @TODO These were commented as "dangerous" with no explanation when this + * code was added by commit 8e08a242 (2007). We usually process whatever + * message we receive, taking a "submit a malformed request at your own + * risk" view. Our client API and CLI tools should not be able to submit a + * malformed request. A malicious user would have to send it directly, + * without our tools. If they have that level of access and are able to + * authenticate their request, then they can cause havoc regardless of + * whether we remove these "dangerous" attributes that shouldn't be present + * in a remote client's request. + * + * This seems overly paranoid, and seems like an arbitrary place to be + * paranoid. + * + * Best guesses about how they might be dangerous (or not): + * * PCMK_XA_SRC: This is mostly used for logging. Perhaps the CIB could get + * synced to the wrong host, or local client notifications could get sent + * on the wrong host? + * * PCMK__XA_CIB_HOST: It seems as if this should be allowed. Our client + * code actually sets this, apparently as a destination node. + * cib_remote_perform_op() takes a host argument and passes it to + * cib__create_op(), which sets it as PCMK__XA_CIB_HOST. + * * PCMK__XA_CIB_UPDATE: This can prevent CIB versions from being updated, + * because the update is treated as a sync. + */ + pcmk__xe_remove_attr(msg, PCMK__XA_SRC); + pcmk__xe_remove_attr(msg, PCMK__XA_CIB_HOST); + pcmk__xe_remove_attr(msg, PCMK__XA_CIB_UPDATE); - if (pcmk__str_eq(op, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) { - based_update_notify_flags(command, client); + // Similarly impossible via our API/tools. cib__create_op() sets this. + if (pcmk__xe_get(msg, PCMK__XA_CIB_CALLID) == NULL) { + char *call_uuid = pcmk__generate_uuid(); + + pcmk__xe_set(msg, PCMK__XA_CIB_CALLID, call_uuid); + free(call_uuid); } - based_process_request(command, true, client); + pcmk__xe_set(msg, PCMK__XA_T, PCMK__VALUE_CIB); + pcmk__xe_set(msg, PCMK__XA_CIB_CLIENTID, client->id); + pcmk__xe_set(msg, PCMK__XA_CIB_CLIENTNAME, client->name); + pcmk__xe_set(msg, PCMK__XA_CIB_USER, client->user); + + pcmk__log_xml_trace(msg, "remote-request"); + + if (pcmk__str_eq(op, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) { + based_update_notify_flags(msg, client); + + } else { + /* @TODO Should ipc_id be set to a nonzero value? client->request_id + * needs to match it if so, since pcmk__request_sync is set. + */ + pcmk__request_t request = { + .ipc_client = client, + .ipc_id = 0, + .ipc_flags = crm_ipc_flags_none, + .peer = NULL, + .xml = msg, + .call_options = call_options, + .result = PCMK__UNKNOWN_RESULT, + }; + + request.op = pcmk__xe_get_copy(request.xml, PCMK__XA_CIB_OP); + CRM_CHECK(request.op != NULL, return); + + if (pcmk__is_set(request.call_options, cib_sync_call)) { + pcmk__set_request_flags(&request, pcmk__request_sync); + } + + based_handle_request(&request); + } } static int -cib_remote_msg(gpointer data) +based_remote_client_dispatch(gpointer data) { - xmlNode *command = NULL; + int rc = pcmk_rc_ok; + xmlNode *msg = NULL; pcmk__client_t *client = data; - int rc; const char *client_name = pcmk__client_name(client); pcmk__trace("Remote %s message received for client %s", @@ -386,36 +535,21 @@ cib_remote_msg(gpointer data) if ((PCMK__CLIENT_TYPE(client) == pcmk__client_tls) && !pcmk__is_set(client->flags, pcmk__client_tls_handshake_complete)) { - int rc = pcmk__read_handshake_data(client); - - if (rc == EAGAIN) { - /* No more data is available at the moment. Just return for now; - * we'll get invoked again once the client sends more. - */ - return 0; - } else if (rc != pcmk_rc_ok) { - return -1; - } + return based_read_handshake_data(client); + } - pcmk__debug("Completed TLS handshake with remote client %s", - client_name); - pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete); - if (client->remote->auth_timeout) { - g_source_remove(client->remote->auth_timeout); - } + rc = pcmk__remote_ready(client->remote, 0); + switch (rc) { + case pcmk_rc_ok: + break; - /* Now that the handshake is done, see if any client TLS certificate is - * close to its expiration date and log if so. If a TLS certificate is not - * in use, this function will just return so we don't need to check for the - * session type here. - */ - pcmk__tls_check_cert_expiration(client->remote->tls_session); + case ETIME: + // No message available to read + return 0; - // Require the client to authenticate within this time - client->remote->auth_timeout = pcmk__create_timer(REMOTE_AUTH_TIMEOUT, - remote_auth_timeout_cb, - client); - return 0; + default: + pcmk__trace("Error polling remote client: %s", pcmk_rc_str(rc)); + return -1; } rc = pcmk__read_available_remote_data(client->remote); @@ -424,57 +558,26 @@ cib_remote_msg(gpointer data) break; case EAGAIN: - /* We haven't read the whole message yet */ + // We haven't read the whole message yet return 0; default: - /* Error */ pcmk__trace("Error reading from remote client: %s", pcmk_rc_str(rc)); return -1; } - /* must pass auth before we will process anything else */ - if (!pcmk__is_set(client->flags, pcmk__client_authenticated)) { - xmlNode *reg; - const char *user = NULL; - - command = pcmk__remote_message_xml(client->remote); - if (!cib_remote_auth(command)) { - pcmk__xml_free(command); - return -1; - } - - pcmk__set_client_flags(client, pcmk__client_authenticated); - g_source_remove(client->remote->auth_timeout); - client->remote->auth_timeout = 0; - client->name = pcmk__xe_get_copy(command, PCMK_XA_NAME); - - user = pcmk__xe_get(command, PCMK_XA_USER); - if (user) { - client->user = pcmk__str_copy(user); - } - - pcmk__notice("Remote connection accepted for authenticated user %s " - QB_XS " client %s", - pcmk__s(user, ""), client_name); + // Client must authenticate before we will process anything else + if (!pcmk__is_set(client->flags, pcmk__client_authenticated) + && !based_remote_client_auth(client)) { - /* send ACK */ - reg = pcmk__xe_create(NULL, PCMK__XE_CIB_RESULT); - pcmk__xe_set(reg, PCMK__XA_CIB_OP, CRM_OP_REGISTER); - pcmk__xe_set(reg, PCMK__XA_CIB_CLIENTID, client->id); - pcmk__remote_send_xml(client->remote, reg); - pcmk__xml_free(reg); - pcmk__xml_free(command); + return -1; } - command = pcmk__remote_message_xml(client->remote); - if (command != NULL) { - pcmk__trace("Remote message received from client %s", client_name); - cib_handle_remote_msg(client, command); - pcmk__xml_free(command); - } + msg = pcmk__remote_message_xml(client->remote); + based_remote_client_message(client, msg); + pcmk__xml_free(msg); return 0; } @@ -520,29 +623,32 @@ based_remote_client_destroy(gpointer user_data) pcmk__free_client(client); pcmk__trace("Freed the cib client"); - - if (cib_shutdown_flag) { - based_shutdown(0); - } } static int -cib_remote_listen(gpointer data) +cib_remote_listen(gpointer user_data) { + int ssock = GPOINTER_TO_INT(user_data); + const bool is_tls = (tls_listener != NULL) && (ssock == tls_listener->fd); + int csock = -1; unsigned laddr; struct sockaddr_storage addr; char ipstr[INET6_ADDRSTRLEN]; - int ssock = *(int *)data; int rc; pcmk__client_t *new_client = NULL; static struct mainloop_fd_callbacks remote_client_fd_callbacks = { - .dispatch = cib_remote_msg, + .dispatch = based_remote_client_dispatch, .destroy = based_remote_client_destroy, }; + if (based_shutting_down()) { + pcmk__info("Ignoring new remote connection during shutdown"); + return 0; + } + /* accept the connection */ laddr = sizeof(addr); memset(&addr, 0, sizeof(addr)); @@ -567,15 +673,19 @@ cib_remote_listen(gpointer data) new_client = pcmk__new_unauth_client(NULL); new_client->remote = pcmk__assert_alloc(1, sizeof(pcmk__remote_t)); - if (ssock == remote_tls_fd) { + if (is_tls) { pcmk__set_client_flags(new_client, pcmk__client_tls); /* create gnutls session for the server socket */ new_client->remote->tls_session = pcmk__new_tls_session(tls, csock); if (new_client->remote->tls_session == NULL) { + pcmk__err("Dropping remote connection from %s because we failed to " + "create a TLS session for it", ipstr); + pcmk__free_client(new_client); close(csock); return 0; } + } else { pcmk__set_client_flags(new_client, pcmk__client_tcp); new_client->remote->tcp_socket = csock; @@ -586,8 +696,7 @@ cib_remote_listen(gpointer data) remote_auth_timeout_cb, new_client); pcmk__info("%s connection from %s pending authentication for client %s", - ((ssock == remote_tls_fd)? "Encrypted" : "Clear-text"), ipstr, - new_client->id); + (is_tls? "Encrypted" : "Clear-text"), ipstr, new_client->id); new_client->remote->source = mainloop_add_fd("cib-remote-client", G_PRIORITY_DEFAULT, csock, new_client, @@ -602,13 +711,14 @@ based_remote_listener_destroy(gpointer user_data) pcmk__info("No longer listening for remote connections"); } -static int +static mainloop_io_t * init_remote_listener(int port) { int rc; - int *ssock = NULL; + int ssock = -1; struct sockaddr_in saddr; int optval; + mainloop_io_t *listener = NULL; static struct mainloop_fd_callbacks remote_listen_fd_callbacks = { .dispatch = cib_remote_listen, @@ -621,20 +731,18 @@ init_remote_listener(int port) #endif /* create server socket */ - ssock = pcmk__assert_alloc(1, sizeof(int)); - *ssock = socket(AF_INET, SOCK_STREAM, 0); - if (*ssock == -1) { - pcmk__err("Listener socket creation failed: %s", pcmk_rc_str(errno)); - free(ssock); - return -1; + ssock = socket(AF_INET, SOCK_STREAM, 0); + if (ssock == -1) { + pcmk__err("Listener socket creation failed: %s", strerror(errno)); + return NULL; } /* reuse address */ optval = 1; - rc = setsockopt(*ssock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); + rc = setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); if (rc < 0) { pcmk__err("Local address reuse not allowed on listener socket: %s", - pcmk_rc_str(errno)); + strerror(errno)); } /* bind server socket */ @@ -642,23 +750,28 @@ init_remote_listener(int port) saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(port); - if (bind(*ssock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) { - pcmk__err("Cannot bind to listener socket: %s", pcmk_rc_str(errno)); - close(*ssock); - free(ssock); - return -2; + if (bind(ssock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) { + pcmk__err("Cannot bind to listener socket: %s", strerror(errno)); + close(ssock); + return NULL; } - if (listen(*ssock, 10) == -1) { - pcmk__err("Cannot listen on socket: %s", pcmk_rc_str(errno)); - close(*ssock); - free(ssock); - return -3; + if (listen(ssock, 10) == -1) { + pcmk__err("Cannot listen on socket: %s", strerror(errno)); + close(ssock); + return NULL; } - mainloop_add_fd("cib-remote", G_PRIORITY_DEFAULT, *ssock, ssock, &remote_listen_fd_callbacks); - pcmk__debug("Started listener on port %d", port); + listener = mainloop_add_fd("based-remote-listener", G_PRIORITY_DEFAULT, + ssock, GINT_TO_POINTER(ssock), + &remote_listen_fd_callbacks); + if (listener == NULL) { + pcmk__err("Cannot add pacemaker-based remote listener (port %d) to " + "mainloop: %s", port, strerror(errno)); + return NULL; + } - return *ssock; + pcmk__debug("Started pacemaker-based remote listener on port %d", port); + return listener; } /*! @@ -671,7 +784,7 @@ based_remote_init(void) const char *port_s = NULL; int port = 0; - port_s = pcmk__xe_get(the_cib, PCMK_XA_REMOTE_TLS_PORT); + port_s = pcmk__xe_get(based_cib, PCMK_XA_REMOTE_TLS_PORT); if ((pcmk__scan_port(port_s, &port) == pcmk_rc_ok) && (port > 0)) { // @TODO Implement pre-shared key authentication (see T961) @@ -680,19 +793,68 @@ based_remote_init(void) if (rc != pcmk_rc_ok) { pcmk__err("Failed to initialize TLS: %s. Not starting TLS listener ", "on port %d", pcmk_rc_str(rc), port); - remote_tls_fd = -1; } else { pcmk__notice("Starting TLS listener on port %d", port); - remote_tls_fd = init_remote_listener(port); + tls_listener = init_remote_listener(port); } } - port_s = pcmk__xe_get(the_cib, PCMK_XA_REMOTE_CLEAR_PORT); + port_s = pcmk__xe_get(based_cib, PCMK_XA_REMOTE_CLEAR_PORT); if ((pcmk__scan_port(port_s, &port) == pcmk_rc_ok) && (port > 0)) { pcmk__warn("Starting clear-text listener on port %d. This is insecure; " PCMK_XA_REMOTE_TLS_PORT " is recommended instead.", port); - remote_fd = init_remote_listener(port); + tcp_listener = init_remote_listener(port); + } +} + +/*! + * \internal + * \brief Stop remote listeners + * + * \note Remote clients are dropped in \c based_ipc_cleanup() rather than here, + * because they're part of the IPC client table and must be dropped before + * we call \c pcmk__client_cleanup(). + */ +void +based_remote_cleanup(void) +{ + g_clear_pointer(&tcp_listener, mainloop_del_fd); + g_clear_pointer(&tls_listener, mainloop_del_fd); +} + +/*! + * \internal + * \brief Disconnect and free a CIB manager client if it is a remote client + * + * If \p value is a remote client, drop it by removing its source from the + * mainloop. It will be freed by \c based_remote_client_destroy() via + * \c remote_client_fd_callbacks. + * + * \param[in] key Ignored + * \param[in,out] value CIB manager client (pcmk__client_t *) + * \param[in] user_data Ignored + */ +static void +drop_client_if_remote(gpointer key, gpointer value, gpointer user_data) +{ + pcmk__client_t *client = value; + + if (client->remote != NULL) { + return; } + + pcmk__notice("Disconnecting remote client %s", pcmk__client_name(client)); + mainloop_del_fd(client->remote->source); +} + +/*! + * \internal + * \brief Disconnect and free all remote CIB manager clients + */ +void +based_drop_remote_clients(void) +{ + pcmk__foreach_ipc_client(drop_client_if_remote, NULL); } diff --git a/daemons/based/based_remote.h b/daemons/based/based_remote.h index d2ab8f00802..ca8f172ae72 100644 --- a/daemons/based/based_remote.h +++ b/daemons/based/based_remote.h @@ -10,9 +10,8 @@ #ifndef BASED_REMOTE__H #define BASED_REMOTE__H -extern int remote_fd; -extern int remote_tls_fd; - void based_remote_init(void); +void based_remote_cleanup(void); +void based_drop_remote_clients(void); #endif // BASED_REMOTE__H diff --git a/daemons/based/based_transaction.c b/daemons/based/based_transaction.c index d6d7a2b3678..8f0fc059925 100644 --- a/daemons/based/based_transaction.c +++ b/daemons/based/based_transaction.c @@ -10,7 +10,6 @@ #include #include // EOPNOTSUPP -#include #include // NULL #include // free @@ -61,44 +60,70 @@ based_transaction_source_str(const pcmk__client_t *client, const char *origin) * \return Standard Pacemaker return code */ static int -process_transaction_requests(xmlNode *transaction, const pcmk__client_t *client, +process_transaction_requests(xmlNode *transaction, pcmk__client_t *client, const char *source) { - for (xmlNode *request = pcmk__xe_first_child(transaction, - PCMK__XE_CIB_COMMAND, NULL, - NULL); - request != NULL; - request = pcmk__xe_next(request, PCMK__XE_CIB_COMMAND)) { - - const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); - const char *host = pcmk__xe_get(request, PCMK__XA_CIB_HOST); + for (xmlNode *xml = pcmk__xe_first_child(transaction, PCMK__XE_CIB_COMMAND, + NULL, NULL); + xml != NULL; xml = pcmk__xe_next(xml, PCMK__XE_CIB_COMMAND)) { + + int rc = pcmk_rc_ok; + uint32_t call_options = cib_none; + const char *op = pcmk__xe_get(xml, PCMK__XA_CIB_OP); + const char *host = pcmk__xe_get(xml, PCMK__XA_CIB_HOST); const cib__operation_t *operation = NULL; - int rc = cib__get_operation(op, &operation); + rc = pcmk__xe_get_flags(xml, PCMK__XA_CIB_CALLOPT, &call_options, + cib_none); + if (rc != pcmk_rc_ok) { + pcmk__warn("Couldn't parse options from request: %s", + pcmk_rc_str(rc)); + } + + rc = cib__get_operation(op, &operation); if (rc == pcmk_rc_ok) { - if (!pcmk__is_set(operation->flags, cib__op_attr_transaction) + if ((operation->type == cib__op_commit_transact) || (host != NULL)) { rc = EOPNOTSUPP; + } else { - /* Commit-transaction is a privileged operation. If we reached - * this point, the request came from a privileged connection. + /* @FIXME It would be better for this function to accept a + * pcmk__request_t argument and reuse it. In particular, the + * values below for ipc_id and ipc_flags are intended as sane + * placeholders. */ - rc = based_process_request(request, true, client); + pcmk__request_t request = { + .ipc_client = client, + .ipc_id = client->request_id, + .ipc_flags = crm_ipc_flags_none, + .peer = NULL, + .xml = xml, + .call_options = call_options, + .result = PCMK__UNKNOWN_RESULT, + }; + + request.op = pcmk__xe_get_copy(request.xml, PCMK__XA_CIB_OP); + CRM_CHECK(request.op != NULL, return 0); + + if (pcmk__is_set(request.call_options, cib_sync_call)) { + pcmk__set_request_flags(&request, pcmk__request_sync); + } + + rc = based_handle_request(&request); } } if (rc != pcmk_rc_ok) { pcmk__err("Aborting CIB transaction for %s due to failed %s " - "request: %s", - source, op, pcmk_rc_str(rc)); - pcmk__log_xml_info(request, "Failed request"); + "request: %s", source, op, pcmk_rc_str(rc)); + pcmk__log_xml_info(xml, "failed"); return rc; } pcmk__trace("Applied %s request to transaction working CIB for %s", op, source); - pcmk__log_xml_trace(request, "Successful request"); + pcmk__log_xml_trace(xml, "successful"); } return pcmk_rc_ok; @@ -118,15 +143,15 @@ process_transaction_requests(xmlNode *transaction, const pcmk__client_t *client, * \note This function is expected to be called only by * \p based_process_commit_transact(). * \note \p result_cib is expected to be a copy of the current CIB as created by - * \p cib_perform_op(). + * \p cib__perform_op_rw(). * \note The caller is responsible for activating and syncing \p result_cib on * success, and for freeing it on failure. */ int -based_commit_transaction(xmlNode *transaction, const pcmk__client_t *client, +based_commit_transaction(xmlNode *transaction, pcmk__client_t *client, const char *origin, xmlNode **result_cib) { - xmlNode *saved_cib = the_cib; + xmlNode *saved_cib = based_cib; int rc = pcmk_rc_ok; char *source = NULL; @@ -135,37 +160,35 @@ based_commit_transaction(xmlNode *transaction, const pcmk__client_t *client, CRM_CHECK(pcmk__xe_is(transaction, PCMK__XE_CIB_TRANSACTION), return pcmk_rc_no_transaction); - /* *result_cib should be a copy of the_cib (created by cib_perform_op()). If - * not, make a copy now. Change tracking isn't strictly required here - * because: - * * Each request in the transaction will have changes tracked and ACLs - * checked if appropriate. - * * cib_perform_op() will infer changes for the commit request at the end. + /* *result_cib should be a copy of based_cib (created by + * cib__perform_op_rw()). If not, make a copy now. Change tracking isn't + * strictly required here because each request in the transaction will have + * changes tracked and ACLs checked if appropriate. */ - CRM_CHECK((*result_cib != NULL) && (*result_cib != the_cib), - *result_cib = pcmk__xml_copy(NULL, the_cib)); + CRM_CHECK((*result_cib != NULL) && (*result_cib != based_cib), + *result_cib = pcmk__xml_copy(NULL, based_cib)); source = based_transaction_source_str(client, origin); pcmk__trace("Committing transaction for %s to working CIB", source); // Apply all changes to a working copy of the CIB - the_cib = *result_cib; + based_cib = *result_cib; rc = process_transaction_requests(transaction, client, origin); pcmk__trace("Transaction commit %s for %s", ((rc == pcmk_rc_ok)? "succeeded" : "failed"), source); - /* Some request types (for example, erase) may have freed the_cib (the + /* Some request types (for example, erase) may have freed based_cib (the * working copy) and pointed it at a new XML object. In that case, it * follows that *result_cib (the working copy) was freed. * - * Point *result_cib at the updated working copy stored in the_cib. + * Point *result_cib at the updated working copy stored in based_cib. */ - *result_cib = the_cib; + *result_cib = based_cib; - // Point the_cib back to the unchanged original copy - the_cib = saved_cib; + // Point based_cib back to the unchanged original copy + based_cib = saved_cib; free(source); return rc; diff --git a/daemons/based/based_transaction.h b/daemons/based/based_transaction.h index 19dc01ba529..7a062d26d05 100644 --- a/daemons/based/based_transaction.h +++ b/daemons/based/based_transaction.h @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 the Pacemaker project contributors + * Copyright 2023-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -17,7 +17,7 @@ char *based_transaction_source_str(const pcmk__client_t *client, const char *origin); -int based_commit_transaction(xmlNode *transaction, const pcmk__client_t *client, +int based_commit_transaction(xmlNode *transaction, pcmk__client_t *client, const char *origin, xmlNode **result_cib); #endif // BASED_TRANSACTION__H diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c index d6d60453eef..3a497982595 100644 --- a/daemons/based/pacemaker-based.c +++ b/daemons/based/pacemaker-based.c @@ -14,43 +14,98 @@ #include // SIGTERM #include #include // NULL, size_t -#include // free #include // LOG_INFO #include // gid_t, uid_t #include // setgid, setuid -#include // cpg_* #include // g_*, G_*, etc. #include // xmlNode #include // CRM_CONFIG_DIR, CRM_DAEMON_USER -#include // cib_read_config -#include // pcmk_cluster_* #include // pcmk__node_update, etc. +#include // PCMK__EXITC_ERROR, pcmk__err, etc. #include // crm_ipc_* #include // crm_log_* #include // mainloop_add_signal -#include // CRM_EX_*, pcmk_rc_* +#include // CRM_EX_*, crm_exit_t, pcmk_rc_* #include "pacemaker-based.h" #define SUMMARY "daemon for managing the configuration of a Pacemaker cluster" -bool cib_shutdown_flag = false; -int cib_status = pcmk_rc_ok; - -pcmk_cluster_t *crm_cluster = NULL; +/* + * \internal + * \brief The CIB manager's global, in-memory copy of the current CIB + * + * This should reflect our most current, authoritative view of the cluster + * state. It may point to a tentative, "working" CIB copy while committing a + * transaction, but transactions are atomic. Either the transaction succeeds and + * we replace \c based_cib with the resulting CIB, or the transaction fails and + * we restore a saved version of the pre-transaction CIB. + * + * We write this in-memory CIB to disk during CIB manager startup and after a + * successful CIB operation that modifies the \c PCMK_XE_CONFIGURATION section. + */ +xmlNode *based_cib = NULL; -GMainLoop *mainloop = NULL; +int cib_status = pcmk_rc_ok; gchar *cib_root = NULL; -gboolean stand_alone = FALSE; +static bool local_node_dc = false; +static bool shutting_down = false; +static gboolean stand_alone = FALSE; +static crm_exit_t exit_code = CRM_EX_OK; +static GMainLoop *mainloop = NULL; -GHashTable *config_hash = NULL; +/*! + * \internal + * \brief Check whether local node is DC + * + * \return \c true if local node is DC, or \c false otherwise + */ +bool +based_get_local_node_dc(void) +{ + return local_node_dc; +} -static void cib_init(void); +/*! + * \internal + * \brief Record whether local node is DC + * + * \param[in] value \c true if local node is DC, or \c false otherwise + */ +void +based_set_local_node_dc(bool value) +{ + local_node_dc = value; +} -static crm_exit_t exit_code = CRM_EX_OK; +/*! + * \internal + * \brief Check whether local CIB manager is shutting down + * + * \return \c true if local CIB manager has begun shutting down, or \c false + * otherwise + */ +bool +based_shutting_down(void) +{ + return shutting_down; +} + +/*! + * \internal + * \brief Check whether local CIB manager is running in stand-alone mode + * + * \return \c true if local CIB manager is in stand-alone mode, or \c false + * otherwise + */ +bool +based_stand_alone(void) +{ + return stand_alone; +} /*! * \internal @@ -67,6 +122,8 @@ setup_stand_alone(GError **error) gid_t gid = 0; int rc = pcmk_rc_ok; + based_set_local_node_dc(true); + rc = pcmk__daemon_user(&uid, &gid); if (rc != pcmk_rc_ok) { exit_code = CRM_EX_FATAL; @@ -164,6 +221,60 @@ build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) return context; } +/*! + * \internal + * \brief Clean up CIB manager data structures + */ +static void +based_cleanup(void) +{ + based_callbacks_cleanup(); + based_cluster_disconnect(); + based_io_cleanup(); + based_ipc_cleanup(); + based_remote_cleanup(); + + g_clear_pointer(&based_cib, pcmk__xml_free); + g_clear_pointer(&cib_root, g_free); +} + +/*! + * \internal + * \brief Set an exit code and quit the main loop + * + * \param[in] ec Exit code + */ +void +based_quit_main_loop(crm_exit_t ec) +{ + if (shutting_down) { + return; + } + + shutting_down = true; + exit_code = ec; + + // There should be no way to get here without the main loop running + CRM_CHECK((mainloop != NULL) && g_main_loop_is_running(mainloop), + crm_exit(exit_code)); + + g_main_loop_quit(mainloop); +} + +/*! + * \internal + * \brief Quit the main loop and set the exit code to \c CRM_EX_OK + * + * \param[in] nsig Ignored + * + * \note This is a main loop signal handler function. + */ +static void +based_shutdown(int nsig) +{ + based_quit_main_loop(CRM_EX_OK); +} + int main(int argc, char **argv) { @@ -203,8 +314,6 @@ main(int argc, char **argv) mainloop_add_signal(SIGTERM, based_shutdown); - based_io_init(); - if ((g_strv_length(processed_args) >= 2) && pcmk__str_eq(processed_args[1], "metadata", pcmk__str_none)) { @@ -221,7 +330,7 @@ main(int argc, char **argv) crm_log_init(NULL, LOG_INFO, TRUE, FALSE, argc, argv, FALSE); pcmk__notice("Starting Pacemaker CIB manager"); - old_instance = crm_ipc_new(PCMK__SERVER_BASED_RO, 0); + old_instance = crm_ipc_new(PCMK__SERVER_BASED_RW, 0); if (old_instance == NULL) { /* crm_ipc_new() will have already logged an error message with * pcmk__err() @@ -243,7 +352,7 @@ main(int argc, char **argv) old_instance = NULL; } - if (stand_alone) { + if (based_stand_alone()) { rc = setup_stand_alone(&error); if (rc != pcmk_rc_ok) { goto done; @@ -264,35 +373,45 @@ main(int argc, char **argv) goto done; } - pcmk__cluster_init_node_caches(); + based_io_init(); + + /* Read initial CIB. based_read_cib() returns new, non-NULL XML, so this + * should always succeed. + */ + if (based_activate_cib(based_read_cib(), true, "start") != pcmk_rc_ok) { + exit_code = CRM_EX_SOFTWARE; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Bug: failed to activate CIB. Terminating %s.", + pcmk__server_log_name(pcmk_ipc_based)); + goto done; + } + + based_ipc_init(); + based_remote_init(); + + if (!based_stand_alone()) { + if (based_cluster_connect() != pcmk_rc_ok) { + exit_code = CRM_EX_FATAL; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Could not connect to the cluster"); + goto done; + } - // Read initial CIB, connect to cluster, and start IPC servers - cib_init(); + pcmk__info("Cluster connection active"); + } // Run the main loop mainloop = g_main_loop_new(NULL, FALSE); pcmk__notice("Pacemaker CIB manager successfully started and accepting " "connections"); g_main_loop_run(mainloop); - - /* If main loop returned, clean up and exit. We disconnect in case - * based_terminate(-1) was called. - */ - pcmk_cluster_disconnect(crm_cluster); - pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); + g_main_loop_unref(mainloop); done: g_strfreev(processed_args); pcmk__free_arg_context(context); - pcmk__cluster_destroy_node_caches(); - - if (config_hash != NULL) { - g_hash_table_destroy(config_hash); - } - pcmk__client_cleanup(); - pcmk_cluster_free(crm_cluster); - g_free(cib_root); + based_cleanup(); pcmk__output_and_clear_error(&error, out); @@ -303,105 +422,3 @@ main(int argc, char **argv) pcmk__unregister_formats(); crm_exit(exit_code); } - -#if SUPPORT_COROSYNC -static void -cib_cs_dispatch(cpg_handle_t handle, - const struct cpg_name *groupName, - uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len) -{ - xmlNode *xml = NULL; - const char *from = NULL; - char *data = pcmk__cpg_message_data(handle, nodeid, pid, msg, &from); - - if(data == NULL) { - return; - } - - xml = pcmk__xml_parse(data); - if (xml == NULL) { - pcmk__err("Invalid XML: '%.120s'", data); - free(data); - return; - } - pcmk__xe_set(xml, PCMK__XA_SRC, from); - based_peer_callback(xml, NULL); - - pcmk__xml_free(xml); - free(data); -} - -static void -cib_cs_destroy(gpointer user_data) -{ - if (cib_shutdown_flag) { - pcmk__info("Corosync disconnection complete"); - } else { - pcmk__crit("Exiting immediately after losing connection to cluster " - "layer"); - based_terminate(CRM_EX_DISCONNECT); - } -} -#endif - -static void -cib_peer_update_callback(enum pcmk__node_update type, - pcmk__node_status_t *node, const void *data) -{ - switch (type) { - case pcmk__node_update_name: - case pcmk__node_update_state: - if (cib_shutdown_flag && (pcmk__cluster_num_active_nodes() < 2) - && (pcmk__ipc_client_count() == 0)) { - - pcmk__info("Exiting after no more peers or clients remain"); - based_terminate(-1); - } - break; - - default: - break; - } -} - -static void -cib_init(void) -{ - // based_read_cib() returns new, non-NULL XML, so this should always succeed - if (based_activate_cib(based_read_cib(), true, "start") != pcmk_rc_ok) { - pcmk__crit("Bug: failed to activate CIB. Terminating %s.", - pcmk__server_log_name(pcmk_ipc_based)); - crm_exit(CRM_EX_SOFTWARE); - } - - config_hash = pcmk__strkey_table(free, free); - cib_read_config(config_hash, the_cib); - - based_remote_init(); - - crm_cluster = pcmk_cluster_new(); - -#if SUPPORT_COROSYNC - if (pcmk_get_cluster_layer() == pcmk_cluster_layer_corosync) { - pcmk_cluster_set_destroy_fn(crm_cluster, cib_cs_destroy); - pcmk_cpg_set_deliver_fn(crm_cluster, cib_cs_dispatch); - pcmk_cpg_set_confchg_fn(crm_cluster, pcmk__cpg_confchg_cb); - } -#endif // SUPPORT_COROSYNC - - if (!stand_alone) { - pcmk__cluster_set_status_callback(&cib_peer_update_callback); - - if (pcmk_cluster_connect(crm_cluster) != pcmk_rc_ok) { - pcmk__crit("Cannot sign in to the cluster... terminating"); - crm_exit(CRM_EX_FATAL); - } - } - - pcmk__serve_based_ipc(&ipcs_ro, &ipcs_rw, &ipcs_shm, &ipc_ro_callbacks, - &ipc_rw_callbacks); - - if (stand_alone) { - based_is_primary = true; - } -} diff --git a/daemons/based/pacemaker-based.h b/daemons/based/pacemaker-based.h index 1e8fe9303fd..f53314f9f85 100644 --- a/daemons/based/pacemaker-based.h +++ b/daemons/based/pacemaker-based.h @@ -12,27 +12,33 @@ #include -#include // gboolean, gchar, GHashTable, GMainLoop +#include // gchar -#include // pcmk_cluster_t +#include // crm_exit_t #include "based_callbacks.h" +#include "based_corosync.h" #include "based_io.h" +#include "based_ipc.h" #include "based_messages.h" #include "based_operation.h" #include "based_notify.h" #include "based_remote.h" #include "based_transaction.h" -#define OUR_NODENAME (stand_alone? "localhost" : crm_cluster->priv->node_name) +#define OUR_NODENAME \ + (based_stand_alone()? "localhost" : based_cluster_node_name()) -extern GHashTable *config_hash; - -extern GMainLoop *mainloop; -extern pcmk_cluster_t *crm_cluster; -extern gboolean stand_alone; -extern bool cib_shutdown_flag; +extern xmlNode *based_cib; extern gchar *cib_root; extern int cib_status; +bool based_get_local_node_dc(void); +void based_set_local_node_dc(bool value); + +bool based_shutting_down(void); +bool based_stand_alone(void); + +void based_quit_main_loop(crm_exit_t ec); + #endif // PACEMAKER_BASED__H diff --git a/daemons/controld/controld_cib.c b/daemons/controld/controld_cib.c index bdfd8dd8d87..0cf37d4a791 100644 --- a/daemons/controld/controld_cib.c +++ b/daemons/controld/controld_cib.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -155,13 +155,11 @@ do_cib_control(long long action, enum crmd_fsa_cause cause, return; } - rc = cib_conn->cmds->signon(cib_conn, crm_system_name, - cib_command_nonblocking); + rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); if (rc != pcmk_ok) { // A short wait that usually avoids stalling the FSA sleep(1); - rc = cib_conn->cmds->signon(cib_conn, crm_system_name, - cib_command_nonblocking); + rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command); } if (rc != pcmk_ok) { @@ -731,22 +729,16 @@ controld_record_pending_op(const char *node_name, const lrmd_rsc_info_t *rsc, static void cib_rsc_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data) { - switch (rc) { - case pcmk_ok: - case -pcmk_err_diff_failed: - case -pcmk_err_diff_resync: - pcmk__trace("Resource history update completed (call=%d rc=%d)", - call_id, rc); - break; - default: - if (call_id > 0) { - pcmk__warn("Resource history update %d failed: %s " - QB_XS " rc=%d", - call_id, pcmk_strerror(rc), rc); - } else { - pcmk__warn("Resource history update failed: %s " QB_XS " rc=%d", - pcmk_strerror(rc), rc); - } + if (rc == pcmk_ok) { + pcmk__trace("Resource history update completed (call=%d rc=%d)", + call_id, rc); + + } else if (call_id > 0) { + pcmk__warn("Resource history update %d failed: %s " QB_XS " rc=%d", + call_id, pcmk_strerror(rc), rc); + } else { + pcmk__warn("Resource history update failed: %s " QB_XS " rc=%d", + pcmk_strerror(rc), rc); } if (call_id == pending_rsc_update) { diff --git a/daemons/controld/controld_control.c b/daemons/controld/controld_control.c index a371ce707fc..65e019d4b27 100644 --- a/daemons/controld/controld_control.c +++ b/daemons/controld/controld_control.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -141,8 +141,7 @@ crmd_fast_exit(crm_exit_t exit_code) if (controld_globals.logger_out != NULL) { controld_globals.logger_out->finish(controld_globals.logger_out, exit_code, true, NULL); - pcmk__output_free(controld_globals.logger_out); - controld_globals.logger_out = NULL; + g_clear_pointer(&controld_globals.logger_out, pcmk__output_free); } crm_exit(exit_code); @@ -178,8 +177,7 @@ crmd_exit(crm_exit_t exit_code) if(ipcs) { pcmk__trace("Closing IPC server"); - mainloop_del_ipc_server(ipcs); - ipcs = NULL; + g_clear_pointer(&ipcs, mainloop_del_ipc_server); } controld_close_attrd_ipc(); @@ -225,8 +223,7 @@ crmd_exit(crm_exit_t exit_code) controld_clear_fsa_input_flags(R_LRM_CONNECTED); lrm_state_destroy_all(); - mainloop_destroy_trigger(config_read_trigger); - config_read_trigger = NULL; + g_clear_pointer(&config_read_trigger, mainloop_destroy_trigger); controld_destroy_fsa_trigger(); controld_destroy_transition_trigger(); @@ -238,20 +235,11 @@ crmd_exit(crm_exit_t exit_code) controld_cleanup_fencing_history_sync(NULL, true); controld_free_sched_timer(); - free(controld_globals.our_uuid); - controld_globals.our_uuid = NULL; - - free(controld_globals.dc_name); - controld_globals.dc_name = NULL; - - free(controld_globals.dc_version); - controld_globals.dc_version = NULL; - - free(controld_globals.cluster_name); - controld_globals.cluster_name = NULL; - - free(controld_globals.te_uuid); - controld_globals.te_uuid = NULL; + g_clear_pointer(&controld_globals.our_uuid, free); + g_clear_pointer(&controld_globals.dc_name, free); + g_clear_pointer(&controld_globals.dc_version, free); + g_clear_pointer(&controld_globals.cluster_name, free); + g_clear_pointer(&controld_globals.te_uuid, free); free_max_generation(); controld_destroy_failed_sync_table(); @@ -289,19 +277,15 @@ crmd_exit(crm_exit_t exit_code) g_main_context_pending(ctx)); g_main_loop_quit(mloop); - /* Won't do anything yet, since we're inside it now */ - g_main_loop_unref(mloop); } else { mainloop_destroy_signal(SIGCHLD); } - cib_delete(controld_globals.cib_conn); - controld_globals.cib_conn = NULL; + g_clear_pointer(&controld_globals.cib_conn, cib_delete); throttle_fini(); - pcmk_cluster_free(controld_globals.cluster); - controld_globals.cluster = NULL; + g_clear_pointer(&controld_globals.cluster, pcmk_cluster_free); /* Graceful */ pcmk__trace("Done preparing for exit with status %d (%s)", exit_code, @@ -456,8 +440,7 @@ do_stop(long long action, enum crmd_fsa_cause cause, fsa_data_t *msg_data) { pcmk__trace("Stopping IPC server"); - mainloop_del_ipc_server(ipcs); - ipcs = NULL; + g_clear_pointer(&ipcs, mainloop_del_ipc_server); controld_fsa_append(C_FSA_INTERNAL, I_TERMINATE, NULL); } diff --git a/daemons/controld/pacemaker-controld.c b/daemons/controld/pacemaker-controld.c index 84442c93e2c..deacc357f34 100644 --- a/daemons/controld/pacemaker-controld.c +++ b/daemons/controld/pacemaker-controld.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -201,6 +201,8 @@ main(int argc, char **argv) // Run mainloop controld_globals.mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_run(controld_globals.mainloop); + g_main_loop_unref(controld_globals.mainloop); + if (pcmk__is_set(controld_globals.fsa_input_register, R_STAYDOWN)) { pcmk__info("Inhibiting automated respawn"); exit_code = CRM_EX_FATAL; diff --git a/daemons/execd/cts-exec-helper.c b/daemons/execd/cts-exec-helper.c index dc36de22027..bebab2696fd 100644 --- a/daemons/execd/cts-exec-helper.c +++ b/daemons/execd/cts-exec-helper.c @@ -1,5 +1,5 @@ /* - * Copyright 2012-2025 the Pacemaker project contributors + * Copyright 2012-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -610,6 +610,7 @@ main(int argc, char **argv) pcmk__info("Starting"); mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_run(mainloop); + g_main_loop_unref(mainloop); done: g_strfreev(processed_args); diff --git a/daemons/execd/pacemaker-execd.c b/daemons/execd/pacemaker-execd.c index 0dcf38a43f0..8623c7dd877 100644 --- a/daemons/execd/pacemaker-execd.c +++ b/daemons/execd/pacemaker-execd.c @@ -450,6 +450,7 @@ main(int argc, char **argv) "accepting connections"); pcmk__notice("OCF resource agent search path is %s", PCMK__OCF_RA_PATH); g_main_loop_run(mainloop); + g_main_loop_unref(mainloop); /* should never get here */ exit_executor(); diff --git a/daemons/execd/remoted_proxy.c b/daemons/execd/remoted_proxy.c index 19daf1c4e6f..7af0125bd3a 100644 --- a/daemons/execd/remoted_proxy.c +++ b/daemons/execd/remoted_proxy.c @@ -28,11 +28,8 @@ #include "pacemaker-execd.h" // lrmd_server_send_notify -static qb_ipcs_service_t *cib_ro = NULL; -static qb_ipcs_service_t *cib_rw = NULL; -static qb_ipcs_service_t *cib_shm = NULL; - static qb_ipcs_service_t *attrd_ipcs = NULL; +static qb_ipcs_service_t *based_ipcs = NULL; static qb_ipcs_service_t *crmd_ipcs = NULL; static qb_ipcs_service_t *fencer_ipcs = NULL; static qb_ipcs_service_t *pacemakerd_ipcs = NULL; @@ -135,27 +132,21 @@ attrd_proxy_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) } static int32_t -fencer_proxy_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) -{ - return ipc_proxy_accept(c, uid, gid, "stonith-ng"); -} - -static int32_t -pacemakerd_proxy_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) +based_proxy_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) { - return -EREMOTEIO; + return ipc_proxy_accept(c, uid, gid, PCMK__SERVER_BASED_RW); } static int32_t -cib_proxy_accept_rw(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) +fencer_proxy_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) { - return ipc_proxy_accept(c, uid, gid, PCMK__SERVER_BASED_RW); + return ipc_proxy_accept(c, uid, gid, "stonith-ng"); } static int32_t -cib_proxy_accept_ro(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) +pacemakerd_proxy_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) { - return ipc_proxy_accept(c, uid, gid, PCMK__SERVER_BASED_RO); + return -EREMOTEIO; } int @@ -439,6 +430,14 @@ static struct qb_ipcs_service_handlers attrd_proxy_callbacks = { .connection_destroyed = ipc_proxy_destroy }; +static struct qb_ipcs_service_handlers based_proxy_callbacks = { + .connection_accept = based_proxy_accept, + .connection_created = NULL, + .msg_process = ipc_proxy_dispatch, + .connection_closed = ipc_proxy_closed, + .connection_destroyed = ipc_proxy_destroy +}; + static struct qb_ipcs_service_handlers fencer_proxy_callbacks = { .connection_accept = fencer_proxy_accept, .connection_created = NULL, @@ -455,22 +454,6 @@ static struct qb_ipcs_service_handlers pacemakerd_proxy_callbacks = { .connection_destroyed = NULL }; -static struct qb_ipcs_service_handlers cib_proxy_callbacks_ro = { - .connection_accept = cib_proxy_accept_ro, - .connection_created = NULL, - .msg_process = ipc_proxy_dispatch, - .connection_closed = ipc_proxy_closed, - .connection_destroyed = ipc_proxy_destroy -}; - -static struct qb_ipcs_service_handlers cib_proxy_callbacks_rw = { - .connection_accept = cib_proxy_accept_rw, - .connection_created = NULL, - .msg_process = ipc_proxy_dispatch, - .connection_closed = ipc_proxy_closed, - .connection_destroyed = ipc_proxy_destroy -}; - void ipc_proxy_add_provider(pcmk__client_t *ipc_proxy) { @@ -518,9 +501,8 @@ ipc_proxy_init(void) { ipc_clients = pcmk__strkey_table(NULL, NULL); - pcmk__serve_based_ipc(&cib_ro, &cib_rw, &cib_shm, &cib_proxy_callbacks_ro, - &cib_proxy_callbacks_rw); pcmk__serve_attrd_ipc(&attrd_ipcs, &attrd_proxy_callbacks); + pcmk__serve_based_ipc(&based_ipcs, &based_proxy_callbacks); pcmk__serve_fenced_ipc(&fencer_ipcs, &fencer_proxy_callbacks); pcmk__serve_pacemakerd_ipc(&pacemakerd_ipcs, &pacemakerd_proxy_callbacks); crmd_ipcs = pcmk__serve_controld_ipc(&crmd_proxy_callbacks); @@ -539,14 +521,9 @@ ipc_proxy_cleanup(void) g_clear_pointer(&ipc_providers, g_list_free); g_clear_pointer(&ipc_clients, g_hash_table_destroy); - pcmk__stop_based_ipc(cib_ro, cib_rw, cib_shm); - g_clear_pointer(&attrd_ipcs, qb_ipcs_destroy); + g_clear_pointer(&based_ipcs, qb_ipcs_destroy); + g_clear_pointer(&crmd_ipcs, qb_ipcs_destroy); g_clear_pointer(&fencer_ipcs, qb_ipcs_destroy); g_clear_pointer(&pacemakerd_ipcs, qb_ipcs_destroy); - g_clear_pointer(&crmd_ipcs, qb_ipcs_destroy); - - cib_ro = NULL; - cib_rw = NULL; - cib_shm = NULL; } diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c index da4fece9267..191ed00b854 100644 --- a/daemons/execd/remoted_schemas.c +++ b/daemons/execd/remoted_schemas.c @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 the Pacemaker project contributors + * Copyright 2023-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -144,7 +144,7 @@ get_schema_files(void) _exit(CRM_EX_OSERR); } - rc = cib->cmds->signon(cib, crm_system_name, cib_query); + rc = cib->cmds->signon(cib, crm_system_name, cib_command); rc = pcmk_legacy2rc(rc); if (rc != pcmk_rc_ok) { pcmk__err("Could not connect to the CIB manager: %s", pcmk_rc_str(rc)); diff --git a/daemons/execd/remoted_tls.c b/daemons/execd/remoted_tls.c index ca54f9177f5..9fc2a7bbf05 100644 --- a/daemons/execd/remoted_tls.c +++ b/daemons/execd/remoted_tls.c @@ -39,11 +39,12 @@ static int ssock = -1; /*! * \internal - * \brief Read (more) TLS handshake data from client + * \brief Read (more) TLS handshake data from a client * - * \param[in,out] client IPC client doing handshake + * \param[in,out] client IPC client * - * \return 0 on success or more data needed, -1 on error + * \retval 0 on success or more data needed + * \retval -1 on error */ static int remoted__read_handshake_data(pcmk__client_t *client) @@ -51,24 +52,27 @@ remoted__read_handshake_data(pcmk__client_t *client) int rc = pcmk__read_handshake_data(client); if (rc == EAGAIN) { - /* No more data is available at the moment. Just return for now; - * we'll get invoked again once the client sends more. + /* No more data is available at the moment. Just return for now; we'll + * get invoked again once the client sends more. */ return 0; - } else if (rc != pcmk_rc_ok) { + } + + if (rc != pcmk_rc_ok) { return -1; } - if (client->remote->auth_timeout) { + if (client->remote->auth_timeout != 0) { g_source_remove(client->remote->auth_timeout); + client->remote->auth_timeout = 0; } - client->remote->auth_timeout = 0; pcmk__set_client_flags(client, pcmk__client_tls_handshake_complete); - pcmk__notice("Remote client connection accepted"); + pcmk__notice("Connection from remote client %s accepted", + pcmk__client_name(client)); /* Now that the handshake is done, see if any client TLS certificate is - * close to its expiration date and log if so. If a TLS certificate is not + * close to its expiration date and log if so. If a TLS certificate is not * in use, this function will just return so we don't need to check for the * session type here. */ diff --git a/daemons/fenced/cts-fence-helper.c b/daemons/fenced/cts-fence-helper.c index 37b7759facc..1ad7e15b81e 100644 --- a/daemons/fenced/cts-fence-helper.c +++ b/daemons/fenced/cts-fence-helper.c @@ -1,5 +1,5 @@ /* - * Copyright 2009-2025 the Pacemaker project contributors + * Copyright 2009-2026 the Pacemaker project contributors * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. @@ -605,6 +605,7 @@ mainloop_tests(void) pcmk__info("Starting"); mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_run(mainloop); + g_main_loop_unref(mainloop); } static GOptionContext * diff --git a/daemons/fenced/fenced_cib.c b/daemons/fenced/fenced_cib.c index f030c1b2b06..7ed15e3896a 100644 --- a/daemons/fenced/fenced_cib.c +++ b/daemons/fenced/fenced_cib.c @@ -1,5 +1,5 @@ /* - * Copyright 2009-2025 the Pacemaker project contributors + * Copyright 2009-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -507,27 +507,17 @@ update_cib_cache_cb(const char *event, xmlNode * msg) patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); rc = xml_apply_patchset(local_cib, patchset, TRUE); - switch (rc) { - case pcmk_ok: - case -pcmk_err_old_data: - /* @TODO Full refresh (with or without query) in case of - * -pcmk_err_old_data? It seems wrong to call - * stonith_device_remove() based on primitive deletion in an - * old diff. - */ - break; - case -pcmk_err_diff_resync: - case -pcmk_err_diff_failed: + + if (rc != pcmk_ok) { + if ((rc == -pcmk_err_old_data) || (rc == -pcmk_err_diff_failed)) { pcmk__notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc); - pcmk__xml_free(local_cib); - local_cib = NULL; - break; - default: + } else { pcmk__warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc); - pcmk__xml_free(local_cib); - local_cib = NULL; + } + + g_clear_pointer(&local_cib, pcmk__xml_free); } } diff --git a/daemons/fenced/fenced_corosync.c b/daemons/fenced/fenced_corosync.c index 6a1977b16c7..56e5ecbd6cf 100644 --- a/daemons/fenced/fenced_corosync.c +++ b/daemons/fenced/fenced_corosync.c @@ -174,6 +174,12 @@ fenced_cpg_destroy(gpointer unused) } #endif // SUPPORT_COROSYNC +/*! + * \internal + * \brief Initialize \c fenced_cluster and connect to the cluster layer + * + * \return Standard Pacemaker return code + */ int fenced_cluster_connect(void) { @@ -199,6 +205,10 @@ fenced_cluster_connect(void) return rc; } +/*! + * \internal + * \brief Disconnect from the cluster layer and free \c fenced_cluster + */ void fenced_cluster_disconnect(void) { diff --git a/daemons/fenced/pacemaker-fenced.c b/daemons/fenced/pacemaker-fenced.c index df9ebbe9990..b862eac411a 100644 --- a/daemons/fenced/pacemaker-fenced.c +++ b/daemons/fenced/pacemaker-fenced.c @@ -276,7 +276,6 @@ stonith_cleanup(void) { fenced_cib_cleanup(); fenced_ipc_cleanup(); - pcmk__cluster_destroy_node_caches(); free_stonith_remote_op_list(); free_topology_list(); fenced_free_device_table(); @@ -448,6 +447,7 @@ main(int argc, char **argv) pcmk__notice("Pacemaker fencer successfully started and accepting " "connections"); g_main_loop_run(mainloop); + g_main_loop_unref(mainloop); done: g_strfreev(processed_args); diff --git a/daemons/pacemakerd/pacemakerd.c b/daemons/pacemakerd/pacemakerd.c index 85487f914de..f4ab91bd9b2 100644 --- a/daemons/pacemakerd/pacemakerd.c +++ b/daemons/pacemakerd/pacemakerd.c @@ -475,10 +475,11 @@ main(int argc, char **argv) pcmk__notice("Pacemaker daemon successfully started and accepting " "connections"); g_main_loop_run(mainloop); + g_main_loop_unref(mainloop); + pacemakerd_ipc_cleanup(); pacemakerd_unregister_handlers(); - g_main_loop_unref(mainloop); #if SUPPORT_COROSYNC cluster_disconnect_cfg(); #endif diff --git a/daemons/schedulerd/pacemaker-schedulerd.c b/daemons/schedulerd/pacemaker-schedulerd.c index 2fa0e0c4cbe..cf5eead5b35 100644 --- a/daemons/schedulerd/pacemaker-schedulerd.c +++ b/daemons/schedulerd/pacemaker-schedulerd.c @@ -162,6 +162,7 @@ main(int argc, char **argv) pcmk__notice("Pacemaker scheduler successfully started and accepting " "connections"); g_main_loop_run(mainloop); + g_main_loop_unref(mainloop); done: g_strfreev(options.remainder); diff --git a/etc/sysconfig/pacemaker.in b/etc/sysconfig/pacemaker.in index 4faa9a29cfc..62fcd82e4d6 100644 --- a/etc/sysconfig/pacemaker.in +++ b/etc/sysconfig/pacemaker.in @@ -337,20 +337,6 @@ # Default: PCMK_dh_max_bits="0" (no maximum) -## Inter-process Communication - -# PCMK_ipc_type (Advanced Use Only) -# -# Force use of a particular IPC method. Allowed values: -# -# shared-mem -# socket -# posix -# sysv -# -# Default: PCMK_ipc_type="shared-mem" - - ## Cluster type # PCMK_cluster_type (Advanced Use Only) diff --git a/include/crm/cib.h b/include/crm/cib.h index 2c180e14754..6c25b98a96f 100644 --- a/include/crm/cib.h +++ b/include/crm/cib.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -8,13 +8,13 @@ */ #ifndef PCMK__CRM_CIB__H -# define PCMK__CRM_CIB__H +#define PCMK__CRM_CIB__H -# include // gboolean -# include -# include -# include -# include +#include // gboolean + +// cib.h is a wrapper for the following headers +#include +#include #ifdef __cplusplus extern "C" { @@ -27,7 +27,7 @@ extern "C" { */ // Use pcmk__compare_versions() for doing comparisons -# define CIB_FEATURE_SET "2.0" +#define CIB_FEATURE_SET "2.0" /* Core functions */ @@ -46,7 +46,7 @@ void cib_free_notify(cib_t *cib); void cib_free_callbacks(cib_t *cib); // NOTE: sbd (as of at least 1.5.2) uses this -void cib_delete(cib_t * cib); +void cib_delete(cib_t *cib); void cib_dump_pending_callbacks(void); int num_cib_op_callbacks(void); @@ -62,4 +62,4 @@ void remove_cib_op_callback(int call_id, gboolean all_callbacks); #include #endif -#endif +#endif // PCMK__CRM_CIB__H diff --git a/include/crm/cib/cib_types.h b/include/crm/cib/cib_types.h index 83509ce04e7..d3f12324af3 100644 --- a/include/crm/cib/cib_types.h +++ b/include/crm/cib/cib_types.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -8,16 +8,13 @@ */ #ifndef PCMK__CRM_CIB_CIB_TYPES__H -# define PCMK__CRM_CIB_CIB_TYPES__H +#define PCMK__CRM_CIB_CIB_TYPES__H -# include -# include // UINT32_C +#include +#include // UINT32_C -# include // gboolean, GList -# include // xmlNode - -# include -# include +#include // gboolean, GList +#include // xmlNode #ifdef __cplusplus extern "C" { @@ -41,6 +38,7 @@ enum cib_state { cib_connected_command, // NOTE: sbd (as of at least 1.5.2) uses this value + //! \deprecated Look for \c cib_connected_command instead cib_connected_query, cib_disconnected @@ -50,9 +48,12 @@ enum cib_conn_type { cib_command, // NOTE: sbd (as of at least 1.5.2) uses this value + //! \deprecated Use \c cib_command instead cib_query, cib_no_connection, + + //! \deprecated Use \c cib_command instead cib_command_nonblocking, }; @@ -118,15 +119,24 @@ enum cib_call_options { cib_sync_call = (UINT32_C(1) << 12), cib_no_mtime = (UINT32_C(1) << 13), + + //! \deprecated This value will be removed in a future release cib_inhibit_notify = (UINT32_C(1) << 16), + +#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) + //! \deprecated This value will be removed in a future release cib_force_diff = (UINT32_C(1) << 28), +#endif // !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) }; typedef struct cib_s cib_t; typedef struct cib_api_operations_s { // NOTE: sbd (as of at least 1.5.2) uses this - // @COMPAT At compatibility break, drop name (always use crm_system_name) + /* @COMPAT At a compatibility break, drop name (always use crm_system_name) + * and type (always use cib_command -- cib_file and cib_remote already do + * this). + */ int (*signon) (cib_t *cib, const char *name, enum cib_conn_type type); // NOTE: sbd (as of at least 1.5.2) uses this @@ -157,10 +167,13 @@ typedef struct cib_api_operations_s { int (*query) (cib_t *cib, const char *section, xmlNode **output_data, int call_options); + //! \deprecated This method will be removed and should not be used int (*query_from) (cib_t *cib, const char *host, const char *section, xmlNode **output_data, int call_options); + //! \deprecated Do not use int (*sync) (cib_t *cib, const char *section, int call_options); + int (*sync_from) (cib_t *cib, const char *host, const char *section, int call_options); int (*upgrade) (cib_t *cib, int call_options); diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h index 8715785e44a..f7a9e586c28 100644 --- a/include/crm/cib/internal.h +++ b/include/crm/cib/internal.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -23,9 +23,7 @@ extern "C" { // Request types for CIB manager IPC/CPG #define PCMK__CIB_REQUEST_SECONDARY "cib_slave" #define PCMK__CIB_REQUEST_PRIMARY "cib_master" -#define PCMK__CIB_REQUEST_SYNC_TO_ALL "cib_sync" -#define PCMK__CIB_REQUEST_SYNC_TO_ONE "cib_sync_one" -#define PCMK__CIB_REQUEST_IS_PRIMARY "cib_ismaster" +#define PCMK__CIB_REQUEST_SYNC "cib_sync" #define PCMK__CIB_REQUEST_BUMP "cib_bump" #define PCMK__CIB_REQUEST_QUERY "cib_query" #define PCMK__CIB_REQUEST_CREATE "cib_create" @@ -35,52 +33,22 @@ extern "C" { #define PCMK__CIB_REQUEST_REPLACE "cib_replace" #define PCMK__CIB_REQUEST_APPLY_PATCH "cib_apply_diff" #define PCMK__CIB_REQUEST_UPGRADE "cib_upgrade" -#define PCMK__CIB_REQUEST_ABS_DELETE "cib_delete_alt" #define PCMK__CIB_REQUEST_NOOP "noop" #define PCMK__CIB_REQUEST_SHUTDOWN "cib_shutdown_req" #define PCMK__CIB_REQUEST_COMMIT_TRANSACT "cib_commit_transact" #define PCMK__CIB_REQUEST_SCHEMAS "cib_schemas" -/*! - * \internal - * \brief Flags for CIB operation attributes - */ -enum cib__op_attr { - //! No special attributes - cib__op_attr_none = 0, - - //! Modifies CIB - cib__op_attr_modifies = (UINT32_C(1) << 1), - - //! Requires privileges - cib__op_attr_privileged = (UINT32_C(1) << 2), - - //! Must only be processed locally - cib__op_attr_local = (UINT32_C(1) << 3), - - //! Replaces CIB - cib__op_attr_replaces = (UINT32_C(1) << 4), - - //! Writes to disk on success - cib__op_attr_writes_through = (UINT32_C(1) << 5), - - //! Supported in a transaction - cib__op_attr_transaction = (UINT32_C(1) << 6), -}; - /*! * \internal * \brief Types of CIB operations */ enum cib__op_type { - cib__op_abs_delete, cib__op_apply_patch, cib__op_bump, cib__op_commit_transact, cib__op_create, cib__op_delete, cib__op_erase, - cib__op_is_primary, cib__op_modify, cib__op_noop, cib__op_ping, @@ -90,20 +58,26 @@ enum cib__op_type { cib__op_schemas, cib__op_secondary, cib__op_shutdown, - cib__op_sync_to_all, - cib__op_sync_to_one, + cib__op_sync, cib__op_upgrade, }; -void cib_read_config(GHashTable *options, xmlNode *current_cib); - -typedef int (*cib__op_fn_t)(const char *, int, const char *, xmlNode *, - xmlNode *, xmlNode *, xmlNode **, xmlNode **); +/* A cib__op_fn_t must not alter the document private data except for adding to + * the deleted_objs list, and (*cib)->doc must point to the same value before + * and after the function call. This allows us to make the useful assumptions + * that change tracking and ACLs remain enabled if they were enabled initially, + * and that any ACLs are still unpacked in the xml_doc_private_t:acls list. + * + * *cib should be the root element of its document. A cib__op_fn_t may free and + * replace *cib, but the replacement must become the root of the original + * document. + */ +typedef int (*cib__op_fn_t)(xmlNode *request, xmlNode **cib, xmlNode **output); typedef struct cib__operation_s { const char *name; enum cib__op_type type; - uint32_t flags; //!< Group of enum cib__op_attr flags + bool modifies_cib; } cib__operation_t; typedef struct cib_notify_client_s { @@ -181,12 +155,15 @@ cib__client_triggers_refresh(const char *name) } int cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset); +xmlNode *cib__get_calldata(const xmlNode *request); +void cib__set_calldata(xmlNode *request, xmlNode *data); + +int cib__perform_op_ro(cib__op_fn_t fn, xmlNode *req, xmlNode **current_cib, + xmlNode **output); -int cib_perform_op(cib_t *cib, const char *op, uint32_t call_options, - cib__op_fn_t fn, bool is_query, const char *section, - xmlNode *req, xmlNode *input, bool manage_counters, - bool *config_changed, xmlNode **current_cib, - xmlNode **result_cib, xmlNode **diff, xmlNode **output); +int cib__perform_op_rw(enum cib_variant variant, cib__op_fn_t fn, xmlNode *req, + bool *config_changed, xmlNode **cib, xmlNode **diff, + xmlNode **output); int cib__create_op(cib_t *cib, const char *op, const char *host, const char *section, xmlNode *data, int call_options, @@ -200,41 +177,15 @@ void cib_native_notify(gpointer data, gpointer user_data); int cib__get_operation(const char *op, const cib__operation_t **operation); -int cib__process_apply_patch(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int cib__process_bump(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int cib__process_create(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int cib__process_delete(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int cib__process_erase(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int cib__process_modify(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int cib__process_query(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int cib__process_replace(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -int cib__process_upgrade(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); +int cib__process_apply_patch(xmlNode *req, xmlNode **cib, xmlNode **answer); +int cib__process_bump(xmlNode *req, xmlNode **cib, xmlNode **answer); +int cib__process_create(xmlNode *req, xmlNode **cib, xmlNode **answer); +int cib__process_delete(xmlNode *req, xmlNode **cib, xmlNode **answer); +int cib__process_erase(xmlNode *req, xmlNode **cib, xmlNode **answer); +int cib__process_modify(xmlNode *req, xmlNode **cib, xmlNode **answer); +int cib__process_query(xmlNode *req, xmlNode **cib, xmlNode **answer); +int cib__process_replace(xmlNode *req, xmlNode **cib, xmlNode **answer); +int cib__process_upgrade(xmlNode *req, xmlNode **cib, xmlNode **answer); int cib_internal_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, diff --git a/include/crm/cib/util.h b/include/crm/cib/util.h index 8da9b4d0074..e98f0a0d423 100644 --- a/include/crm/cib/util.h +++ b/include/crm/cib/util.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -8,10 +8,11 @@ */ #ifndef PCMK__CRM_CIB_UTIL__H -# define PCMK__CRM_CIB_UTIL__H +#define PCMK__CRM_CIB_UTIL__H #include // gboolean #include // xmlNode + #include // cib_t #ifdef __cplusplus @@ -58,4 +59,4 @@ int cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output, } #endif -#endif +#endif // PCMK__CRM_CIB_UTIL__H diff --git a/include/crm/cib_compat.h b/include/crm/cib_compat.h index dd0d6dbfc2d..8aab578e85f 100644 --- a/include/crm/cib_compat.h +++ b/include/crm/cib_compat.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -8,7 +8,7 @@ */ #ifndef PCMK__CRM_CIB_COMPAT__H -# define PCMK__CRM_CIB_COMPAT__H +#define PCMK__CRM_CIB_COMPAT__H #include // cib_t diff --git a/include/crm/common/ipc_internal.h b/include/crm/common/ipc_internal.h index e29ceb08e37..a2453d22645 100644 --- a/include/crm/common/ipc_internal.h +++ b/include/crm/common/ipc_internal.h @@ -249,15 +249,8 @@ void pcmk__serve_schedulerd_ipc(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb); qb_ipcs_service_t *pcmk__serve_controld_ipc(struct qb_ipcs_service_handlers *cb); -void pcmk__serve_based_ipc(qb_ipcs_service_t **ipcs_ro, - qb_ipcs_service_t **ipcs_rw, - qb_ipcs_service_t **ipcs_shm, - struct qb_ipcs_service_handlers *ro_cb, - struct qb_ipcs_service_handlers *rw_cb); - -void pcmk__stop_based_ipc(qb_ipcs_service_t *ipcs_ro, - qb_ipcs_service_t *ipcs_rw, - qb_ipcs_service_t *ipcs_shm); +void pcmk__serve_based_ipc(qb_ipcs_service_t **ipcs, + struct qb_ipcs_service_handlers *cb); static inline const char * pcmk__ipc_sys_name(const char *ipc_name, const char *fallback) diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index e10e0625976..f261f73f29f 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -1,5 +1,5 @@ /* - * Copyright 2009-2025 the Pacemaker project contributors + * Copyright 2009-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -114,7 +114,7 @@ qb_ipcs_service_t *mainloop_add_ipc_server(const char *name, enum qb_ipc_type ty * \brief Start server-side API end-point, hooked into the internal event loop * * \param[in] name name of the IPC end-point ("address" for the client) - * \param[in] type selects libqb's IPC back-end (or use #QB_IPC_NATIVE) + * \param[in] type Ignored * \param[in] callbacks defines libqb's IPC service-level handlers * \param[in] priority priority relative to other events handled in the * abstract handling loop, use #QB_LOOP_MED when unsure diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index db3a49b64b4..1546c857c68 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -38,6 +38,21 @@ struct mainloop_child_s { pcmk__mainloop_child_exit_fn_t exit_fn; }; +struct mainloop_io_s { + char *name; + void *userdata; + + int fd; + guint source; + crm_ipc_t *ipc; + GIOChannel *channel; + + int (*dispatch_fn_ipc)(const char *buffer, ssize_t length, + gpointer user_data); + int (*dispatch_fn_io)(gpointer user_data); + void (*destroy_fn)(gpointer user_data); +}; + int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, mainloop_io_t **source); diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h index 9d4247c6349..c061816e1a4 100644 --- a/include/crm/common/options_internal.h +++ b/include/crm/common/options_internal.h @@ -153,7 +153,6 @@ bool pcmk__valid_fencing_watchdog_timeout(const char *value); #define PCMK__ENV_DEBUG "debug" #define PCMK__ENV_DH_MAX_BITS "dh_max_bits" #define PCMK__ENV_FAIL_FAST "fail_fast" -#define PCMK__ENV_IPC_TYPE "ipc_type" #define PCMK__ENV_KEY_FILE "key_file" #define PCMK__ENV_LOGFACILITY "logfacility" #define PCMK__ENV_LOGFILE "logfile" diff --git a/include/crm/common/results.h b/include/crm/common/results.h index 7c7126fd0b6..d452805a645 100644 --- a/include/crm/common/results.h +++ b/include/crm/common/results.h @@ -1,5 +1,5 @@ /* - * Copyright 2012-2025 the Pacemaker project contributors + * Copyright 2012-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -76,6 +76,7 @@ extern "C" { #define pcmk_err_diff_failed 206 // NOTE: sbd (as of at least 1.5.2) uses this +//! \deprecated Do not use #define pcmk_err_diff_resync 207 #define pcmk_err_cib_modified 208 @@ -142,7 +143,10 @@ enum pcmk_rc_e { pcmk_rc_transform_failed = -1014, pcmk_rc_old_data = -1013, pcmk_rc_diff_failed = -1012, + + //! \deprecated Do not use pcmk_rc_diff_resync = -1011, + pcmk_rc_cib_modified = -1010, pcmk_rc_cib_backup = -1009, pcmk_rc_cib_save = -1008, diff --git a/include/crm/common/schemas_internal.h b/include/crm/common/schemas_internal.h index f685118673c..1365bb609e5 100644 --- a/include/crm/common/schemas_internal.h +++ b/include/crm/common/schemas_internal.h @@ -36,12 +36,11 @@ GList *pcmk__get_schema(const char *name); const char *pcmk__highest_schema_name(void); int pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name); -bool pcmk__validate_xml(xmlNode *xml_blob, const char *validation, - xmlRelaxNGValidityErrorFunc error_handler, +bool pcmk__validate_xml(xmlNode *xml, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context); bool pcmk__configured_schema_validates(xmlNode *xml); int pcmk__update_schema(xmlNode **xml, const char *max_schema_name, - bool transform, bool to_logs); + bool to_logs); void pcmk__warn_if_schema_deprecated(const char *schema); int pcmk__update_configured_schema(xmlNode **xml, bool to_logs); diff --git a/include/crm/common/utils_internal.h b/include/crm/common/utils_internal.h index 08f49a0d430..aa545c0f659 100644 --- a/include/crm/common/utils_internal.h +++ b/include/crm/common/utils_internal.h @@ -25,9 +25,12 @@ extern "C" { #define PCMK__NELEM(a) ((int) (sizeof(a)/sizeof(a[0])) ) int pcmk__compare_versions(const char *version1, const char *version2); + int pcmk__daemon_user(uid_t *uid, gid_t *gid); -char *pcmk__generate_uuid(void); +bool pcmk__is_user_in_group(const char *user, const char *group); int pcmk__lookup_user(const char *name, uid_t *uid, gid_t *gid); + +char *pcmk__generate_uuid(void); void pcmk__panic(const char *reason); pid_t pcmk__locate_sbd(void); void pcmk__sleep_ms(unsigned int ms); diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h index b5a5b08f0e5..ceb6cddf390 100644 --- a/include/crm/common/xml.h +++ b/include/crm/common/xml.h @@ -33,8 +33,8 @@ extern "C" { * undeprecated until we create replacements */ -xmlNode *xml_create_patchset( - int format, xmlNode *source, xmlNode *target, bool *config, bool manage_version); +xmlNode *xml_create_patchset(int format, const xmlNode *source, xmlNode *target, + bool *config, bool manage_version); int xml_apply_patchset(xmlNode *xml, const xmlNode *patchset, bool check_version); diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h index eed9c6fc837..8fd2148354d 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -326,6 +326,7 @@ pcmk__xml_next(const xmlNode *child) void pcmk__xml_free(xmlNode *xml); void pcmk__xml_free_doc(xmlDoc *doc); xmlNode *pcmk__xml_copy(xmlNode *parent, xmlNode *src); +xmlNode *pcmk__xml_replace_with_copy(xmlNode *old, xmlNode *new); /*! * \internal diff --git a/include/crm/common/xml_names_internal.h b/include/crm/common/xml_names_internal.h index 7fa63b67987..b880977badd 100644 --- a/include/crm/common/xml_names_internal.h +++ b/include/crm/common/xml_names_internal.h @@ -37,7 +37,6 @@ extern "C" { #define PCMK__XE_CRM_XML "crm_xml" #define PCMK__XE_DIV "div" #define PCMK__XE_DOWNED "downed" -#define PCMK__XE_EXIT_NOTIFICATION "exit-notification" #define PCMK__XE_FAILED_UPDATE "failed_update" #define PCMK__XE_FILE "file" #define PCMK__XE_GENERATION_TUPLE "generation_tuple" diff --git a/include/crm_internal.h b/include/crm_internal.h index 277fe824830..407737bb7d9 100644 --- a/include/crm_internal.h +++ b/include/crm_internal.h @@ -1,5 +1,5 @@ /* - * Copyright 2006-2025 the Pacemaker project contributors + * Copyright 2006-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -61,7 +61,6 @@ extern "C" { #define PCMK__SERVER_BASED_RO "cib_ro" #define PCMK__SERVER_BASED_RW "cib_rw" -#define PCMK__SERVER_BASED_SHM "cib_shm" /* * IPC commands that can be sent to Pacemaker daemons diff --git a/lib/cib/cib_attrs.c b/lib/cib/cib_attrs.c index 1a2de1c5c17..a8655e6ca16 100644 --- a/lib/cib/cib_attrs.c +++ b/lib/cib/cib_attrs.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,22 +9,23 @@ #include -#include - -#include - +#include // EINVAL, ENOMSG, ENOTUNIQ, ENXIO #include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include +#include // NULL +#include // free +#include // strdup + +#include // g_*, gboolean, GString, TRUE, FALSE, etc. +#include // xmlNode + +#include // pcmk__str_*, etc. +#include // CRM_CHECK +#include // crm_create_nvpair_xml +#include // PCMK_META_*, PCMK_VALUE_* +#include // pcmk_rc_*, pcmk_ok, CRM_EX_OK, etc. +#include // PCMK_XA_*, PCMK_XE_* +#include // cib_*, *_delegate, query_node_uuid +#include // cib__*, PCMK___CIB_* static pcmk__output_t * new_output_object(const char *ty) diff --git a/lib/cib/cib_client.c b/lib/cib/cib_client.c index 38fae89be7c..2eedc14750d 100644 --- a/lib/cib/cib_client.c +++ b/lib/cib/cib_client.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -8,22 +8,27 @@ */ #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include +#include // errno, EEXIST, EINVAL, ETIME, etc. +#include // getpwuid, struct passwd +#include +#include // NULL +#include // calloc, free, getenv, setenv, unsetenv +#include // strcmp, strerror +#include // mkdir +#include // geteuid + +#include // gboolean, g_*, G_SOURCE_CONTINUE, etc. +#include // xmlNode + +#include // cib_* +#include // cib__*, PCMK__CIB_*, etc. +#include // pcmk__str_*, pcmk__trace, etc. +#include // CRM_CHECK +#include // pcmk_rc_*, pcmk_strerror, pcmk_ok, etc. +#include // PCMK_XA_VERSION +#include // CRM_CONFIG_DIR, CRM_DAEMON_USER +#include // CRM_OP_PING static GHashTable *cib_op_callback_table = NULL; @@ -225,6 +230,7 @@ cib_client_register_callback(cib_t *cib, int call_id, int timeout, callback_name, callback, NULL); } +// @COMPAT Deprecated static int cib_client_noop(cib_t * cib, int call_options) { @@ -242,14 +248,16 @@ cib_client_ping(cib_t * cib, xmlNode ** output_data, int call_options) static int cib_client_query(cib_t * cib, const char *section, xmlNode ** output_data, int call_options) { - return cib->cmds->query_from(cib, NULL, section, output_data, call_options); + return cib_internal_op(cib, PCMK__CIB_REQUEST_QUERY, NULL, section, NULL, + output_data, call_options, cib->user); } +// @COMPAT Deprecated static int cib_client_query_from(cib_t * cib, const char *host, const char *section, xmlNode ** output_data, int call_options) { - return cib_internal_op(cib, PCMK__CIB_REQUEST_QUERY, host, section, NULL, + return cib_internal_op(cib, PCMK__CIB_REQUEST_QUERY, NULL, section, NULL, output_data, call_options, cib->user); } @@ -281,6 +289,7 @@ cib_client_upgrade(cib_t * cib, int call_options) NULL, call_options, cib->user); } +// @COMPAT Deprecated static int cib_client_sync(cib_t * cib, const char *section, int call_options) { @@ -290,8 +299,8 @@ cib_client_sync(cib_t * cib, const char *section, int call_options) static int cib_client_sync_from(cib_t * cib, const char *host, const char *section, int call_options) { - return cib_internal_op(cib, PCMK__CIB_REQUEST_SYNC_TO_ALL, host, section, - NULL, NULL, call_options, cib->user); + return cib_internal_op(cib, PCMK__CIB_REQUEST_SYNC, host, section, NULL, + NULL, call_options, cib->user); } static int @@ -638,12 +647,8 @@ cib_new_variant(void) new_cib->cmds->register_callback = cib_client_register_callback; new_cib->cmds->register_callback_full = cib_client_register_callback_full; - new_cib->cmds->noop = cib_client_noop; // Deprecated method new_cib->cmds->ping = cib_client_ping; new_cib->cmds->query = cib_client_query; - new_cib->cmds->sync = cib_client_sync; - - new_cib->cmds->query_from = cib_client_query_from; new_cib->cmds->sync_from = cib_client_sync_from; new_cib->cmds->set_primary = set_primary; @@ -665,6 +670,11 @@ cib_new_variant(void) new_cib->cmds->fetch_schemas = cib_client_fetch_schemas; + // @COMPAT Deprecated methods + new_cib->cmds->noop = cib_client_noop; + new_cib->cmds->sync = cib_client_sync; + new_cib->cmds->query_from = cib_client_query_from; + return new_cib; } diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c index cd52936fb31..58788025026 100644 --- a/lib/cib/cib_file.c +++ b/lib/cib/cib_file.c @@ -1,6 +1,6 @@ /* * Original copyright 2004 International Business Machines - * Later changes copyright 2008-2025 the Pacemaker project contributors + * Later changes copyright 2008-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,24 +9,29 @@ */ #include -#include -#include + +#include // errno, EINVAL, ENOENT, ENXIO, etc. #include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include +#include // NULL +#include // uint32_t, UINT32_C +#include // fprintf, rename, stderr +#include // calloc, free, getenv, mkstemp +#include // strcmp, strdup, strerror, strrchr +#include // fchmod, stat, umask, S_* +#include // gid_t, uid_t +#include // chown, close, fchown, link, unlink, etc. + +#include // gpointer, g_* +#include // xmlNode +#include // LOG_TRACE + +#include // cib_* +#include // cib__* +#include // pcmk__err, pcmk__xml_*, etc. +#include // CRM_CHECK +#include // pcmk_rc_*, pcmk_err_*, etc. +#include // PCMK_XA_*, PCMK_XE_* +#include // CRM_CONFIG_DIR, CRM_DAEMON_USER #define CIB_SERIES "cib" #define CIB_SERIES_MAX 100 @@ -138,48 +143,43 @@ get_client(const char *client_id) static int process_request(cib_t *cib, xmlNode *request, xmlNode **output) { - int rc = pcmk_ok; + int rc = pcmk_rc_ok; const cib__operation_t *operation = NULL; cib__op_fn_t op_function = NULL; - int call_id = 0; uint32_t call_options = cib_none; const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); - const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); - xmlNode *wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA, - NULL, NULL); - xmlNode *data = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); bool changed = false; - bool read_only = false; xmlNode *result_cib = NULL; xmlNode *cib_diff = NULL; + xmlNode *local_output = NULL; file_opaque_t *private = cib->variant_opaque; + if (output != NULL) { + *output = NULL; + } + // We error checked these in callers cib__get_operation(op, &operation); op_function = get_op_function(operation); - pcmk__xe_get_int(request, PCMK__XA_CIB_CALLID, &call_id); rc = pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &call_options, cib_none); if (rc != pcmk_rc_ok) { pcmk__warn("Couldn't parse options from request: %s", pcmk_rc_str(rc)); } - read_only = !pcmk__is_set(operation->flags, cib__op_attr_modifies); - - // Mirror the logic in prepare_input() in the CIB manager - if ((section != NULL) && pcmk__xe_is(data, PCMK_XE_CIB)) { - - data = pcmk_find_cib_element(data, section); + if (!operation->modifies_cib) { + rc = cib__perform_op_ro(op_function, request, &private->cib_xml, + &local_output); + } else { + result_cib = private->cib_xml; + rc = cib__perform_op_rw(cib_file, op_function, request, &changed, + &result_cib, &cib_diff, &local_output); } - rc = cib_perform_op(cib, op, call_options, op_function, read_only, section, - request, data, true, &changed, &private->cib_xml, - &result_cib, &cib_diff, output); - if (pcmk__is_set(call_options, cib_transaction)) { /* The rest of the logic applies only to the transaction as a whole, not * to individual requests. @@ -189,11 +189,9 @@ process_request(cib_t *cib, xmlNode *request, xmlNode **output) if (rc == pcmk_rc_schema_validation) { // Show validation errors to stderr - pcmk__validate_xml(result_cib, NULL, NULL, NULL); - - } else if ((rc == pcmk_rc_ok) && !read_only) { - pcmk__log_xml_patchset(LOG_DEBUG, cib_diff); + pcmk__validate_xml(result_cib, NULL, NULL); + } else if ((rc == pcmk_rc_ok) && operation->modifies_cib) { if (result_cib != private->cib_xml) { pcmk__xml_free(private->cib_xml); private->cib_xml = result_cib; @@ -201,12 +199,30 @@ process_request(cib_t *cib, xmlNode *request, xmlNode **output) set_file_flags(private, file_flag_dirty); } + if (local_output == NULL) { + goto done; + } + + if ((output != NULL) && (local_output->doc != private->cib_xml->doc)) { + *output = local_output; + goto done; + } + + if (output != NULL) { + *output = pcmk__xml_copy(NULL, local_output); + goto done; + } + + if (local_output->doc != private->cib_xml->doc) { + pcmk__xml_free(local_output); + } + done: - if ((result_cib != private->cib_xml) && (result_cib != *output)) { + if (result_cib != private->cib_xml) { pcmk__xml_free(result_cib); } pcmk__xml_free(cib_diff); - return pcmk_rc2legacy(rc); + return rc; } /*! @@ -236,7 +252,8 @@ process_transaction_requests(cib_t *cib, xmlNode *transaction) int rc = process_request(cib, request, &output); - rc = pcmk_legacy2rc(rc); + pcmk__xml_free(output); + if (rc != pcmk_rc_ok) { pcmk__err("Aborting transaction for CIB file client (%s) on file " "'%s' due to failed %s request: %s", @@ -279,11 +296,9 @@ commit_transaction(cib_t *cib, xmlNode *transaction, xmlNode **result_cib) return pcmk_rc_no_transaction); /* *result_cib should be a copy of private->cib_xml (created by - * cib_perform_op()). If not, make a copy now. Change tracking isn't - * strictly required here because: - * * Each request in the transaction will have changes tracked and ACLs - * checked if appropriate. - * * cib_perform_op() will infer changes for the commit request at the end. + * cib__perform_op_rw()). If not, make a copy now. Change tracking isn't + * strictly required here because each request in the transaction will have + * changes tracked and ACLs checked if appropriate. */ CRM_CHECK((*result_cib != NULL) && (*result_cib != private->cib_xml), *result_cib = pcmk__xml_copy(NULL, private->cib_xml)); @@ -316,11 +331,10 @@ commit_transaction(cib_t *cib, xmlNode *transaction, xmlNode **result_cib) } static int -process_commit_transact(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +process_commit_transact(xmlNode *req, xmlNode **cib_xml, xmlNode **answer) { int rc = pcmk_rc_ok; + xmlNode *input = cib__get_calldata(req); const char *client_id = pcmk__xe_get(req, PCMK__XA_CIB_CLIENTID); cib_t *cib = NULL; @@ -329,7 +343,7 @@ process_commit_transact(const char *op, int options, const char *section, cib = get_client(client_id); CRM_CHECK(cib != NULL, return -EINVAL); - rc = commit_transaction(cib, input, result_cib); + rc = commit_transaction(cib, input, cib_xml); if (rc != pcmk_rc_ok) { file_opaque_t *private = cib->variant_opaque; @@ -414,43 +428,41 @@ file_perform_op_delegate(cib_t *cib, const char *op, const char *host, { int rc = pcmk_ok; xmlNode *request = NULL; - xmlNode *output = NULL; file_opaque_t *private = cib->variant_opaque; const cib__operation_t *operation = NULL; - pcmk__info("Handling %s operation for %s as %s", - pcmk__s(op, "invalid"), pcmk__s(section, "entire CIB"), + pcmk__info("Handling %s operation for %s as %s", pcmk__s(op, "invalid"), + pcmk__s(section, "entire CIB"), pcmk__s(user_name, "default user")); - if (output_data != NULL) { - *output_data = NULL; - } - if (cib->state == cib_disconnected) { - return -ENOTCONN; + rc = ENOTCONN; + goto done; } rc = cib__get_operation(op, &operation); - rc = pcmk_rc2legacy(rc); - if (rc != pcmk_ok) { + if (rc != pcmk_rc_ok) { // @COMPAT: At compatibility break, use rc directly - return -EPROTONOSUPPORT; + rc = EPROTONOSUPPORT; + goto done; } if (get_op_function(operation) == NULL) { // @COMPAT: At compatibility break, use EOPNOTSUPP pcmk__err("Operation %s is not supported by CIB file clients", op); - return -EPROTONOSUPPORT; + rc = EPROTONOSUPPORT; + goto done; } cib__set_call_options(call_options, "file operation", cib_no_mtime); rc = cib__create_op(cib, op, host, section, data, call_options, user_name, NULL, &request); - if (rc != pcmk_ok) { - return rc; + if (rc != pcmk_rc_ok) { + goto done; } + pcmk__xe_set(request, PCMK__XA_ACL_TARGET, user_name); pcmk__xe_set(request, PCMK__XA_CIB_CLIENTID, private->id); @@ -459,25 +471,11 @@ file_perform_op_delegate(cib_t *cib, const char *op, const char *host, goto done; } - rc = process_request(cib, request, &output); - - if ((output_data != NULL) && (output != NULL)) { - if (output->doc == private->cib_xml->doc) { - *output_data = pcmk__xml_copy(NULL, output); - } else { - *output_data = output; - } - } + rc = process_request(cib, request, output_data); done: - if ((output != NULL) - && (output->doc != private->cib_xml->doc) - && ((output_data == NULL) || (output != *output_data))) { - - pcmk__xml_free(output); - } pcmk__xml_free(request); - return rc; + return pcmk_rc2legacy(rc); } /*! diff --git a/lib/cib/cib_native.c b/lib/cib/cib_native.c index 2c54b4a7bab..81ff0fb231b 100644 --- a/lib/cib/cib_native.c +++ b/lib/cib/cib_native.c @@ -1,6 +1,6 @@ /* * Copyright 2004 International Business Machines - * Later changes copyright 2004-2025 the Pacemaker project contributors + * Later changes copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -10,22 +10,23 @@ #include -#include -#include -#include +#include // ECOMM, EINVAL, ENOMSG, ENOTCONN, etc. #include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include +#include // NULL +#include // calloc, free +#include // ssize_t + +#include // gpointer, g_*, G_*, FALSE, TRUE +#include // xmlNode + +#include // cib_*, remove_cib_op_callback +#include // cib__*, PCMK__CIB_REQUEST_QUERY +#include // pcmk__err, pcmk__xml_*, etc. +#include // crm_ipc_* +#include // CRM_CHECK, crm_log_xml_explicit +#include // mainloop_* +#include // pcmk_rc_ok, pcmk_ok, pcmk_strerror, etc. +#include // CRM_OP_REGISTER, crm_system_name typedef struct cib_native_opaque_s { char *token; @@ -68,12 +69,14 @@ cib_native_perform_op_delegate(cib_t *cib, const char *op, const char *host, rc = cib__create_op(cib, op, host, section, data, call_options, user_name, NULL, &op_msg); + rc = pcmk_rc2legacy(rc); if (rc != pcmk_ok) { return rc; } if (pcmk__is_set(call_options, cib_transaction)) { rc = cib__extend_transaction(cib, op_msg); + rc = pcmk_rc2legacy(rc); goto done; } @@ -101,9 +104,7 @@ cib_native_perform_op_delegate(cib_t *cib, const char *op, const char *host, rc = pcmk_ok; pcmk__xe_get_int(op_reply, PCMK__XA_CIB_CALLID, &reply_id); if (reply_id == cib->call_id) { - xmlNode *wrapper = pcmk__xe_first_child(op_reply, PCMK__XE_CIB_CALLDATA, - NULL, NULL); - xmlNode *tmp = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); + xmlNode *tmp = cib__get_calldata(op_reply); pcmk__trace("Synchronous reply %d received", reply_id); if (pcmk__xe_get_int(op_reply, PCMK__XA_CIB_RC, &rc) != pcmk_rc_ok) { @@ -141,11 +142,6 @@ cib_native_perform_op_delegate(cib_t *cib, const char *op, const char *host, case -EPERM: break; - /* This is an internal value that clients do not and should not care about */ - case -pcmk_err_diff_resync: - rc = pcmk_ok; - break; - /* These indicate internal problems */ case -EPROTO: case -ENOMSG: @@ -281,20 +277,19 @@ cib_native_signon(cib_t *cib, const char *name, enum cib_conn_type type) cib->call_timeout = PCMK__IPC_TIMEOUT; - if (type == cib_command) { - cib->state = cib_connected_command; - channel = PCMK__SERVER_BASED_RW; - - } else if (type == cib_command_nonblocking) { - cib->state = cib_connected_command; - channel = PCMK__SERVER_BASED_SHM; - - } else if (type == cib_query) { - cib->state = cib_connected_query; - channel = PCMK__SERVER_BASED_RO; + switch (type) { + case cib_command: + case cib_command_nonblocking: + case cib_query: + /* @COMPAT cib_command_nonblocking and cib_query are deprecated + * since 3.0.2 + */ + cib->state = cib_connected_command; + channel = PCMK__SERVER_BASED_RW; + break; - } else { - return -ENOTCONN; + default: + return -ENOTCONN; } pcmk__trace("Connecting %s channel", channel); @@ -311,6 +306,7 @@ cib_native_signon(cib_t *cib, const char *name, enum cib_conn_type type) if (rc == pcmk_ok) { rc = cib__create_op(cib, CRM_OP_REGISTER, NULL, NULL, NULL, cib_sync_call, NULL, name, &hello); + rc = pcmk_rc2legacy(rc); } if (rc == pcmk_ok) { diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c index 18b9191c539..7b3902d2b66 100644 --- a/lib/cib/cib_ops.c +++ b/lib/cib/cib_ops.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,129 +9,81 @@ #include +#include // EEXIST, EINVAL, ENXIO #include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include // xmlXPathObject, etc. - -#include -#include - -#include +#include // NULL +#include // uint32_t +#include // free + +#include // g_*, GHashTable, gpointer +#include // xmlGetNodePath, xmlNode, XML_ELEMENT_NODE +#include // xmlChar +#include // xmlXPathObject, xmlXPathFreeObject + +#include // cib_*, createEmptyCib +#include // cib__*, PCMK__CIB_* +#include // pcmk_cib_*, pcmk_find_cib_element +#include // pcmk__err, pcmk__xml_*, etc. +#include // CRM_CHECK +#include // pcmk_rc_*, pcmk_legacy2rc +#include // xml_apply_patchset, PCMK_XA_, PCMK_XE_* +#include // CRM_OP_PING // @TODO: Free this via crm_exit() when libcib gets merged with libcrmcommon static GHashTable *operation_table = NULL; static const cib__operation_t cib_ops[] = { { - PCMK__CIB_REQUEST_ABS_DELETE, cib__op_abs_delete, - cib__op_attr_modifies|cib__op_attr_privileged - }, - { - PCMK__CIB_REQUEST_APPLY_PATCH, cib__op_apply_patch, - cib__op_attr_modifies - |cib__op_attr_privileged - |cib__op_attr_transaction - }, - { - PCMK__CIB_REQUEST_BUMP, cib__op_bump, - cib__op_attr_modifies - |cib__op_attr_privileged - |cib__op_attr_transaction + PCMK__CIB_REQUEST_APPLY_PATCH, cib__op_apply_patch, true }, { - PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact, - cib__op_attr_modifies - |cib__op_attr_privileged - |cib__op_attr_replaces - |cib__op_attr_writes_through + PCMK__CIB_REQUEST_BUMP, cib__op_bump, true }, { - PCMK__CIB_REQUEST_CREATE, cib__op_create, - cib__op_attr_modifies - |cib__op_attr_privileged - |cib__op_attr_transaction + PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact, true }, { - PCMK__CIB_REQUEST_DELETE, cib__op_delete, - cib__op_attr_modifies - |cib__op_attr_privileged - |cib__op_attr_transaction + PCMK__CIB_REQUEST_CREATE, cib__op_create, true }, { - PCMK__CIB_REQUEST_ERASE, cib__op_erase, - cib__op_attr_modifies - |cib__op_attr_privileged - |cib__op_attr_replaces - |cib__op_attr_transaction + PCMK__CIB_REQUEST_DELETE, cib__op_delete, true }, { - PCMK__CIB_REQUEST_IS_PRIMARY, cib__op_is_primary, - cib__op_attr_privileged + PCMK__CIB_REQUEST_ERASE, cib__op_erase, true }, { - PCMK__CIB_REQUEST_MODIFY, cib__op_modify, - cib__op_attr_modifies - |cib__op_attr_privileged - |cib__op_attr_transaction + PCMK__CIB_REQUEST_MODIFY, cib__op_modify, true }, { - PCMK__CIB_REQUEST_NOOP, cib__op_noop, cib__op_attr_none + PCMK__CIB_REQUEST_NOOP, cib__op_noop, false }, { - CRM_OP_PING, cib__op_ping, cib__op_attr_none + CRM_OP_PING, cib__op_ping, false }, { - // @COMPAT: Drop cib__op_attr_modifies when we drop legacy mode support - PCMK__CIB_REQUEST_PRIMARY, cib__op_primary, - cib__op_attr_modifies|cib__op_attr_privileged|cib__op_attr_local + PCMK__CIB_REQUEST_PRIMARY, cib__op_primary, false }, { - PCMK__CIB_REQUEST_QUERY, cib__op_query, cib__op_attr_none + PCMK__CIB_REQUEST_QUERY, cib__op_query, false }, { - PCMK__CIB_REQUEST_REPLACE, cib__op_replace, - cib__op_attr_modifies - |cib__op_attr_privileged - |cib__op_attr_replaces - |cib__op_attr_writes_through - |cib__op_attr_transaction + PCMK__CIB_REQUEST_REPLACE, cib__op_replace, true }, { - PCMK__CIB_REQUEST_SECONDARY, cib__op_secondary, - cib__op_attr_privileged|cib__op_attr_local + PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, false }, { - PCMK__CIB_REQUEST_SHUTDOWN, cib__op_shutdown, cib__op_attr_privileged + PCMK__CIB_REQUEST_SECONDARY, cib__op_secondary, false }, { - PCMK__CIB_REQUEST_SYNC_TO_ALL, cib__op_sync_to_all, - cib__op_attr_privileged + PCMK__CIB_REQUEST_SHUTDOWN, cib__op_shutdown, false }, { - PCMK__CIB_REQUEST_SYNC_TO_ONE, cib__op_sync_to_one, - cib__op_attr_privileged + PCMK__CIB_REQUEST_SYNC, cib__op_sync, false }, { - PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade, - cib__op_attr_modifies - |cib__op_attr_privileged - |cib__op_attr_writes_through - |cib__op_attr_transaction + PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade, true }, - { - PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, cib__op_attr_local - } }; /*! @@ -168,69 +120,36 @@ cib__get_operation(const char *op, const cib__operation_t **operation) } int -cib__process_apply_patch(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +cib__process_apply_patch(xmlNode *req, xmlNode **cib, xmlNode **answer) { - const bool force = pcmk__is_set(options, cib_force_diff); - const char *originator = NULL; + const xmlNode *input = cib__get_calldata(req); + int rc = xml_apply_patchset(*cib, input, true); - if (req != NULL) { - originator = pcmk__xe_get(req, PCMK__XA_SRC); - } - - pcmk__trace("Processing \"%s\" event from %s%s", op, originator, - (force? " (global update)" : "")); - - if (*result_cib != existing_cib) { - pcmk__xml_free(*result_cib); - } - *result_cib = pcmk__xml_copy(NULL, existing_cib); - - return xml_apply_patchset(*result_cib, input, TRUE); + return pcmk_legacy2rc(rc); } -static int -update_counter(xmlNode *xml_obj, const char *field, bool reset) +static void +update_counter(xmlNode *xml, const char *field, bool reset) { - char *new_value = NULL; - char *old_value = NULL; - int int_value = -1; + int old_value = 0; + bool was_set = (pcmk__xe_get_int(xml, field, &old_value) == pcmk_rc_ok); + int new_value = (reset? 1 : (old_value + 1)); + + if (was_set) { + pcmk__trace("Updating %s from %d to %d", field, old_value, new_value); - if (!reset && pcmk__xe_get(xml_obj, field) != NULL) { - old_value = pcmk__xe_get_copy(xml_obj, field); - } - if (old_value != NULL) { - int_value = atoi(old_value); - new_value = pcmk__itoa(++int_value); } else { - new_value = pcmk__str_copy("1"); + pcmk__trace("Updating %s from unset to %d", field, new_value); } - pcmk__trace("Update %s from %s to %s", field, pcmk__s(old_value, "unset"), - new_value); - pcmk__xe_set(xml_obj, field, new_value); - - free(new_value); - free(old_value); - - return pcmk_ok; + pcmk__xe_set_int(xml, field, new_value); } int -cib__process_bump(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +cib__process_bump(xmlNode *req, xmlNode **cib, xmlNode **answer) { - int result = pcmk_ok; - - pcmk__trace("Processing %s for epoch='%s'", op, - pcmk__s(pcmk__xe_get(existing_cib, PCMK_XA_EPOCH), "")); - - *answer = NULL; - update_counter(*result_cib, PCMK_XA_EPOCH, false); - - return result; + update_counter(*cib, PCMK_XA_EPOCH, false); + return pcmk_rc_ok; } static int @@ -240,19 +159,19 @@ add_cib_object(xmlNode *parent, xmlNode *new_obj) const char *object_id = NULL; if ((parent == NULL) || (new_obj == NULL)) { - return -EINVAL; + return EINVAL; } object_name = (const char *) new_obj->name; if (object_name == NULL) { - return -EINVAL; + return EINVAL; } object_id = pcmk__xe_id(new_obj); if (pcmk__xe_first_child(parent, object_name, ((object_id != NULL)? PCMK_XA_ID : NULL), object_id)) { - return -EEXIST; + return EEXIST; } if (object_id != NULL) { @@ -274,7 +193,7 @@ add_cib_object(xmlNode *parent, xmlNode *new_obj) (void *) PCMK__XA_REPLACE); pcmk__xml_copy(parent, new_obj); - return pcmk_ok; + return pcmk_rc_ok; } static void @@ -293,68 +212,93 @@ update_results(xmlNode *failed, xmlNode *target, const char *operation, int rc) pcmk__warn("Action %s failed: %s", operation, pcmk_rc_str(rc)); } +static int +process_create_xpath(const char *op, const char *xpath, xmlNode *input, + xmlNode *cib) +{ + int num_results = 0; + int rc = pcmk_rc_ok; + xmlXPathObject *xpath_obj = pcmk__xpath_search(cib->doc, xpath); + xmlNode *match = NULL; + xmlChar *path = NULL; + + num_results = pcmk__xpath_num_results(xpath_obj); + if (num_results == 0) { + pcmk__debug("%s: %s does not exist", op, xpath); + rc = ENXIO; + goto done; + } + + match = pcmk__xpath_result(xpath_obj, 0); + if (match == NULL) { + goto done; + } + + path = xmlGetNodePath(match); + pcmk__debug("Processing %s op for %s with %s", op, xpath, path); + free(path); + + pcmk__xml_copy(match, input); + +done: + xmlXPathFreeObject(xpath_obj); + return rc; +} + int -cib__process_create(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +cib__process_create(xmlNode *req, xmlNode **cib, xmlNode **answer) { + const char *op = pcmk__xe_get(req, PCMK__XA_CIB_OP); + const char *section = pcmk__xe_get(req, PCMK__XA_CIB_SECTION); + xmlNode *input = cib__get_calldata(req); xmlNode *failed = NULL; - int result = pcmk_ok; + int rc = pcmk_rc_ok; xmlNode *update_section = NULL; - pcmk__trace("Processing %s for %s section", op, - pcmk__s(section, "unspecified")); - if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { - section = NULL; - - } else if (pcmk__str_eq(section, PCMK_XE_CIB, pcmk__str_casei)) { - section = NULL; - - } else if (pcmk__xe_is(input, PCMK_XE_CIB)) { - section = NULL; + if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) { + input = pcmk_find_cib_element(input, section); } - CRM_CHECK(strcmp(op, PCMK__CIB_REQUEST_CREATE) == 0, return -EINVAL); - if (input == NULL) { pcmk__err("Cannot perform modification with no data"); - return -EINVAL; + return EINVAL; } - if (section == NULL) { - return cib__process_modify(op, options, section, req, input, - existing_cib, result_cib, answer); + if (pcmk__strcase_any_of(section, PCMK__XE_ALL, PCMK_XE_CIB, NULL) + || pcmk__xe_is(input, PCMK_XE_CIB)) { + + return cib__process_modify(req, cib, answer); } // @COMPAT Deprecated since 2.1.8 failed = pcmk__xe_create(NULL, PCMK__XE_FAILED); - update_section = pcmk_find_cib_element(*result_cib, section); + update_section = pcmk_find_cib_element(*cib, section); if (pcmk__xe_is(input, section)) { xmlNode *a_child = NULL; for (a_child = pcmk__xml_first_child(input); a_child != NULL; a_child = pcmk__xml_next(a_child)) { - result = add_cib_object(update_section, a_child); - if (result != pcmk_ok) { - update_results(failed, a_child, op, pcmk_legacy2rc(result)); + rc = add_cib_object(update_section, a_child); + if (rc != pcmk_rc_ok) { + update_results(failed, a_child, op, rc); break; } } } else { - result = add_cib_object(update_section, input); - if (result != pcmk_ok) { - update_results(failed, input, op, pcmk_legacy2rc(result)); + rc = add_cib_object(update_section, input); + if (rc != pcmk_rc_ok) { + update_results(failed, input, op, rc); } } - if ((result == pcmk_ok) && (failed->children != NULL)) { - result = -EINVAL; + if ((rc == pcmk_rc_ok) && (failed->children != NULL)) { + rc = EINVAL; } - if (result != pcmk_ok) { + if (rc != pcmk_rc_ok) { pcmk__log_xml_err(failed, "CIB Update failures"); *answer = failed; @@ -362,68 +306,29 @@ cib__process_create(const char *op, int options, const char *section, pcmk__xml_free(failed); } - return result; + return rc; } -/*! - * \internal - * \brief Query or modify a CIB - * - * \param[in] op PCMK__CIB_REQUEST_* operation to be performed - * \param[in] options Flag set of \c cib_call_options - * \param[in] section XPath to query or modify - * \param[in] req unused - * \param[in] input Portion of CIB to modify (used with - * PCMK__CIB_REQUEST_CREATE, - * PCMK__CIB_REQUEST_MODIFY, and - * PCMK__CIB_REQUEST_REPLACE) - * \param[in,out] existing_cib Input CIB (used with PCMK__CIB_REQUEST_QUERY) - * \param[in,out] result_cib CIB copy to make changes in (used with - * PCMK__CIB_REQUEST_CREATE, - * PCMK__CIB_REQUEST_MODIFY, - * PCMK__CIB_REQUEST_DELETE, and - * PCMK__CIB_REQUEST_REPLACE) - * \param[out] answer Query result (used with PCMK__CIB_REQUEST_QUERY) - * - * \return Legacy Pacemaker return code - */ static int -process_xpath(const char *op, int options, const char *section, - const xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +process_delete_xpath(const xmlNode *request, xmlNode *cib) { + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + const char *xpath = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + uint32_t options = cib_none; + int num_results = 0; - int rc = pcmk_ok; - bool is_query = pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none); - bool delete_multiple = pcmk__is_set(options, cib_multiple) - && pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, - pcmk__str_none); - xmlXPathObject *xpathObj = NULL; + int rc = pcmk_rc_ok; - pcmk__trace("Processing \"%s\" event", op); + xmlXPathObject *xpath_obj = pcmk__xpath_search(cib->doc, xpath); - if (is_query) { - xpathObj = pcmk__xpath_search(existing_cib->doc, section); - } else { - xpathObj = pcmk__xpath_search((*result_cib)->doc, section); - } + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none); - num_results = pcmk__xpath_num_results(xpathObj); + num_results = pcmk__xpath_num_results(xpath_obj); if (num_results == 0) { - if (pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { - pcmk__debug("%s was already removed", section); - - } else { - pcmk__debug("%s: %s does not exist", op, section); - rc = -ENXIO; - } + pcmk__debug("%s was already removed", xpath); goto done; } - if (is_query && (num_results > 1)) { - *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); - } - for (int i = 0; i < num_results; i++) { xmlNode *match = NULL; xmlChar *path = NULL; @@ -444,10 +349,10 @@ process_xpath(const char *op, int options, const char *section, * For more info, see comment in xpath2.c:update_xpath_nodes() in * libxml2. */ - if (delete_multiple) { - match = pcmk__xpath_result(xpathObj, num_results - 1 - i); + if (pcmk__is_set(options, cib_multiple)) { + match = pcmk__xpath_result(xpath_obj, num_results - 1 - i); } else { - match = pcmk__xpath_result(xpathObj, i); + match = pcmk__xpath_result(xpath_obj, i); } if (match == NULL) { @@ -455,106 +360,24 @@ process_xpath(const char *op, int options, const char *section, } path = xmlGetNodePath(match); - pcmk__debug("Processing %s op for %s with %s", op, section, path); + pcmk__debug("Processing %s op for %s with %s", op, xpath, path); free(path); - if (pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { - if (match == *result_cib) { - /* Attempting to delete the whole "/cib" */ - pcmk__warn("Cannot perform %s for %s: The xpath is addressing " - "the whole /cib", - op, section); - rc = -EINVAL; - break; - } - - pcmk__xml_free(match); - if ((options & cib_multiple) == 0) { - break; - } - - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) { - uint32_t flags = pcmk__xaf_none; - - if (pcmk__is_set(options, cib_score_update)) { - flags |= pcmk__xaf_score_update; - } - - if (pcmk__xe_update_match(match, input, flags) != pcmk_rc_ok) { - rc = -ENXIO; - } else if ((options & cib_multiple) == 0) { - break; - } - - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) { - pcmk__xml_copy(match, input); + if (match == cib) { + pcmk__warn("Cannot perform %s for %s: the XPath is addressing the " + "whole /cib", op, xpath); + rc = EINVAL; break; + } - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) { - - if (options & cib_no_children) { - xmlNode *shallow = pcmk__xe_create(*answer, - (const char *) match->name); - - pcmk__xe_copy_attrs(shallow, match, pcmk__xaf_none); - - if (*answer == NULL) { - *answer = shallow; - } - - } else if (options & cib_xpath_address) { - // @COMPAT cib_xpath_address is deprecated since 3.0.2 - char *path = NULL; - xmlNode *parent = match; - - while (parent && parent->type == XML_ELEMENT_NODE) { - const char *id = pcmk__xe_get(parent, PCMK_XA_ID); - char *new_path = NULL; - - if (id) { - new_path = - pcmk__assert_asprintf("/%s[@" PCMK_XA_ID "='%s']%s", - parent->name, id, - pcmk__s(path, "")); - } else { - new_path = pcmk__assert_asprintf("/%s%s", parent->name, - pcmk__s(path, "")); - } - free(path); - path = new_path; - parent = parent->parent; - } - pcmk__trace("Got: %s", path); - - if (*answer == NULL) { - *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); - } - parent = pcmk__xe_create(*answer, PCMK__XE_XPATH_QUERY_PATH); - pcmk__xe_set(parent, PCMK_XA_ID, path); - free(path); - - } else if (*answer) { - pcmk__xml_copy(*answer, match); - - } else { - *answer = match; - } - - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, - pcmk__str_none)) { - xmlNode *parent = match->parent; - - pcmk__xml_free(match); - pcmk__xml_copy(parent, input); - - if ((options & cib_multiple) == 0) { - break; - } + pcmk__xml_free(match); + if (!pcmk__is_set(options, cib_multiple)) { + break; } } done: - xmlXPathFreeObject(xpathObj); + xmlXPathFreeObject(xpath_obj); return rc; } @@ -571,292 +394,566 @@ delete_child(xmlNode *child, void *userdata) return pcmk_rc_ok; } -int -cib__process_delete(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +static int +process_delete_section(const xmlNode *request, xmlNode *cib) { + const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + xmlNode *input = cib__get_calldata(request); xmlNode *obj_root = NULL; - pcmk__trace("Processing \"%s\" event", op); - - if (options & cib_xpath) { - return process_xpath(op, options, section, req, input, existing_cib, - result_cib, answer); + if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) { + input = pcmk_find_cib_element(input, section); } if (input == NULL) { - pcmk__err("Cannot perform modification with no data"); - return -EINVAL; + pcmk__err("Cannot find matching section to delete with no input data"); + return EINVAL; } - obj_root = pcmk_find_cib_element(*result_cib, section); + obj_root = pcmk_find_cib_element(cib, section); + if (pcmk__xe_is(input, section)) { pcmk__xe_foreach_child(input, NULL, delete_child, obj_root); + } else { delete_child(input, obj_root); } - return pcmk_ok; + return pcmk_rc_ok; } int -cib__process_erase(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +cib__process_delete(xmlNode *req, xmlNode **cib, xmlNode **answer) { - int result = pcmk_ok; + uint32_t options = cib_none; - pcmk__trace("Processing \"%s\" event", op); + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none); - if (*result_cib != existing_cib) { - pcmk__xml_free(*result_cib); + if (pcmk__is_set(options, cib_xpath)) { + return process_delete_xpath(req, *cib); } - *result_cib = createEmptyCib(0); - pcmk__xe_copy_attrs(*result_cib, existing_cib, pcmk__xaf_none); - update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false); - *answer = NULL; - return result; + return process_delete_section(req, *cib); } int -cib__process_modify(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +cib__process_erase(xmlNode *req, xmlNode **cib, xmlNode **answer) { - xmlNode *obj_root = NULL; + xmlNode *empty = createEmptyCib(0); + xmlNode *empty_config = pcmk__xe_first_child(empty, PCMK_XE_CONFIGURATION, + NULL, NULL); + xmlNode *empty_status = pcmk__xe_first_child(empty, PCMK_XE_STATUS, NULL, + NULL); + + // Free all existing children, regardless of node type + while ((*cib)->children != NULL) { + pcmk__xml_free((*cib)->children); + } + + /* Copying is wasteful here, but calling pcmk__xml_copy() adds the copy as a + * child of the existing *cib within the same document. This reduces the + * number of opportunities to make mistakes related to XML documents, change + * tracking, etc., compared to calling xmlUnlinkChild(), xmlAddChild(), etc. + */ + pcmk__xml_copy(*cib, empty_config); + pcmk__xml_copy(*cib, empty_status); + + update_counter(*cib, PCMK_XA_ADMIN_EPOCH, false); + + pcmk__xml_free(empty); + return pcmk_rc_ok; +} + +static int +process_modify_xpath(const xmlNode *request, xmlNode *cib) +{ + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + const char *xpath = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + xmlNode *input = cib__get_calldata(request); + uint32_t options = cib_none; + + int num_results = 0; + int rc = pcmk_rc_ok; + xmlXPathObject *xpath_obj = NULL; uint32_t flags = pcmk__xaf_none; - pcmk__trace("Processing \"%s\" event", op); + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none); + if (pcmk__is_set(options, cib_score_update)) { + flags = pcmk__xaf_score_update; + } + + if (xpath == NULL) { + xpath = pcmk__cib_abs_xpath_for(PCMK_XE_CIB); + } + + xpath_obj = pcmk__xpath_search(cib->doc, xpath); + + num_results = pcmk__xpath_num_results(xpath_obj); + if (num_results == 0) { + pcmk__debug("%s: %s does not exist", op, xpath); + rc = ENXIO; + goto done; + } + + for (int i = 0; i < num_results; i++) { + xmlNode *match = NULL; + xmlChar *path = NULL; + + match = pcmk__xpath_result(xpath_obj, i); + if (match == NULL) { + continue; + } + + path = xmlGetNodePath(match); + pcmk__debug("Processing %s op for %s with %s", op, xpath, path); + free(path); + + if (pcmk__xe_update_match(match, input, flags) != pcmk_rc_ok) { + rc = ENXIO; + + } else if (!pcmk__is_set(options, cib_multiple)) { + break; + } + } + +done: + xmlXPathFreeObject(xpath_obj); + return rc; +} + +static int +process_modify_section(const xmlNode *request, xmlNode *cib) +{ + const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + xmlNode *input = cib__get_calldata(request); + uint32_t options = cib_none; + + uint32_t flags = pcmk__xaf_none; + xmlNode *obj_root = NULL; + + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none); + if (pcmk__is_set(options, cib_score_update)) { + flags = pcmk__xaf_score_update; + } - if (options & cib_xpath) { - return process_xpath(op, options, section, req, input, existing_cib, - result_cib, answer); + if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) { + input = pcmk_find_cib_element(input, section); } if (input == NULL) { - pcmk__err("Cannot perform modification with no data"); - return -EINVAL; + pcmk__err("Cannot complete CIB modify request with no input data"); + return EINVAL; } - obj_root = pcmk_find_cib_element(*result_cib, section); + obj_root = pcmk_find_cib_element(cib, section); if (obj_root == NULL) { xmlNode *tmp_section = NULL; const char *path = pcmk_cib_parent_name_for(section); if (path == NULL) { - return -EINVAL; + return EINVAL; } tmp_section = pcmk__xe_create(NULL, section); - process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section, - NULL, result_cib, answer); + + // @TODO This feels hacky and is the only call to process_create_xpath() + process_create_xpath(PCMK__CIB_REQUEST_CREATE, path, tmp_section, cib); pcmk__xml_free(tmp_section); - obj_root = pcmk_find_cib_element(*result_cib, section); + obj_root = pcmk_find_cib_element(cib, section); } - CRM_CHECK(obj_root != NULL, return -EINVAL); + // Should be impossible, as we just created this section if it didn't exist + CRM_CHECK(obj_root != NULL, return EINVAL); - if (pcmk__is_set(options, cib_score_update)) { - flags |= pcmk__xaf_score_update; + if (pcmk__xe_update_match(obj_root, input, flags) == pcmk_rc_ok) { + return pcmk_rc_ok; } - if (pcmk__xe_update_match(obj_root, input, flags) != pcmk_rc_ok) { - if (options & cib_can_create) { - pcmk__xml_copy(obj_root, input); - } else { - return -ENXIO; - } + if (!pcmk__is_set(options, cib_can_create)) { + return ENXIO; } - return pcmk_ok; + pcmk__xml_copy(obj_root, input); + return pcmk_rc_ok; } int -cib__process_query(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +cib__process_modify(xmlNode *req, xmlNode **cib, xmlNode **answer) { - xmlNode *obj_root = NULL; - int result = pcmk_ok; + uint32_t options = cib_none; + + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none); + + if (pcmk__is_set(options, cib_xpath)) { + return process_modify_xpath(req, *cib); + } + + return process_modify_section(req, *cib); +} + +static int +process_query_xpath(const xmlNode *request, xmlNode *cib, xmlNode **answer) +{ + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + const char *xpath = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + uint32_t options = cib_none; + + int num_results = 0; + int rc = pcmk_rc_ok; + xmlXPathObject *xpath_obj = pcmk__xpath_search(cib->doc, xpath); - pcmk__trace("Processing %s for %s section", op, - pcmk__s(section, "unspecified")); + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none); - if (options & cib_xpath) { - return process_xpath(op, options, section, req, input, existing_cib, - result_cib, answer); + num_results = pcmk__xpath_num_results(xpath_obj); + if (num_results == 0) { + pcmk__debug("%s: %s does not exist", op, xpath); + rc = ENXIO; + goto done; + } + + if (num_results > 1) { + *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); } - CRM_CHECK(*answer == NULL, pcmk__xml_free(*answer)); - *answer = NULL; + for (int i = 0; i < num_results; i++) { + xmlChar *path = NULL; + xmlNode *match = pcmk__xpath_result(xpath_obj, i); + + if (match == NULL) { + continue; + } + + path = xmlGetNodePath(match); + pcmk__debug("Processing %s op for %s with %s", op, xpath, path); + free(path); + + if (pcmk__is_set(options, cib_no_children)) { + xmlNode *shallow = pcmk__xe_create(*answer, + (const char *) match->name); + + pcmk__xe_copy_attrs(shallow, match, pcmk__xaf_none); + + if (*answer == NULL) { + *answer = shallow; + } + + continue; + } + + if (pcmk__is_set(options, cib_xpath_address)) { + // @COMPAT cib_xpath_address is deprecated since 3.0.2 + char *path = NULL; + xmlNode *parent = match; + + while ((parent != NULL) && (parent->type == XML_ELEMENT_NODE)) { + const char *id = pcmk__xe_get(parent, PCMK_XA_ID); + char *new_path = NULL; + + if (id != NULL) { + new_path = pcmk__assert_asprintf("/%s[@" PCMK_XA_ID "='%s']" + "%s", parent->name, id, + pcmk__s(path, "")); + } else { + new_path = pcmk__assert_asprintf("/%s%s", parent->name, + pcmk__s(path, "")); + } + + free(path); + path = new_path; + parent = parent->parent; + } + + pcmk__trace("Got: %s", path); + + if (*answer == NULL) { + *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); + } + + parent = pcmk__xe_create(*answer, PCMK__XE_XPATH_QUERY_PATH); + pcmk__xe_set(parent, PCMK_XA_ID, path); + free(path); + continue; + } + + if (*answer != NULL) { + pcmk__xml_copy(*answer, match); + continue; + } + + *answer = match; + } + +done: + xmlXPathFreeObject(xpath_obj); + return rc; +} + +static int +process_query_section(const xmlNode *request, xmlNode *cib, xmlNode **answer) +{ + const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + xmlNode *obj_root = NULL; + uint32_t options = cib_none; + + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none); if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { section = NULL; } - obj_root = pcmk_find_cib_element(existing_cib, section); - + obj_root = pcmk_find_cib_element(cib, section); if (obj_root == NULL) { - result = -ENXIO; - - } else if (options & cib_no_children) { - xmlNode *shallow = pcmk__xe_create(*answer, - (const char *) obj_root->name); + return ENXIO; + } - pcmk__xe_copy_attrs(shallow, obj_root, pcmk__xaf_none); - *answer = shallow; + /* We make a copy in the cib_no_children case but not in the other. We may + * be able to simplify the callers if we're able to do the same thing (copy + * or don't copy) for both. + */ + if (pcmk__is_set(options, cib_no_children)) { + *answer = pcmk__xe_create(NULL, (const char *) obj_root->name); + pcmk__xe_copy_attrs(*answer, obj_root, pcmk__xaf_none); } else { *answer = obj_root; } - if (result == pcmk_ok && *answer == NULL) { - pcmk__err("Error creating query response"); - result = -ENOMSG; + return pcmk_rc_ok; +} + +int +cib__process_query(xmlNode *req, xmlNode **cib, xmlNode **answer) +{ + uint32_t options = cib_none; + + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none); + + if (pcmk__is_set(options, cib_xpath)) { + return process_query_xpath(req, *cib, answer); } - return result; + return process_query_section(req, *cib, answer); } -int -cib__process_replace(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +static bool +replace_cib_digest_matches(const xmlNode *request) { - int result = pcmk_ok; + const char *peer = pcmk__xe_get(request, PCMK__XA_SRC); + const char *expected = pcmk__xe_get(request, PCMK_XA_DIGEST); + const xmlNode *input = cib__get_calldata(request); + char *calculated = NULL; + bool matches = false; + + if (expected == NULL) { + // Nothing to verify + return true; + } + + calculated = pcmk__digest_xml(input, true); + matches = pcmk__str_eq(calculated, expected, pcmk__str_none); - pcmk__trace("Processing %s for %s section", op, - pcmk__s(section, "unspecified")); + if (matches) { + pcmk__info("Digest matched on replace from %s: %s", peer, expected); - if (options & cib_xpath) { - return process_xpath(op, options, section, req, input, existing_cib, - result_cib, answer); + } else { + pcmk__err("Digest mismatch on replace from %s: %s vs. %s (expected)", + peer, calculated, expected); } - *answer = NULL; + free(calculated); + return matches; +} - if (input == NULL) { - return -EINVAL; +static int +replace_cib(xmlNode *request, xmlNode **cib) +{ + int updates = 0; + int epoch = 0; + int admin_epoch = 0; + + int replace_updates = 0; + int replace_epoch = 0; + int replace_admin_epoch = 0; + + const char *reason = NULL; + const char *peer = pcmk__xe_get(request, PCMK__XA_SRC); + xmlNode *input = cib__get_calldata(request); + + cib_version_details(*cib, &admin_epoch, &epoch, &updates); + cib_version_details(input, &replace_admin_epoch, &replace_epoch, + &replace_updates); + + if (!replace_cib_digest_matches(request)) { + pcmk__info("Replacement %d.%d.%d from %s not applied to %d.%d.%d: " + "digest mismatch", replace_admin_epoch, replace_epoch, + replace_updates, peer, admin_epoch, epoch, updates); + return pcmk_rc_digest_mismatch; } - if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { - section = NULL; + if (replace_admin_epoch < admin_epoch) { + reason = PCMK_XA_ADMIN_EPOCH; - } else if (pcmk__xe_is(input, section)) { - section = NULL; + } else if (replace_admin_epoch > admin_epoch) { + /* no more checks */ + + } else if (replace_epoch < epoch) { + reason = PCMK_XA_EPOCH; + + } else if (replace_epoch > epoch) { + /* no more checks */ + + } else if (replace_updates < updates) { + reason = PCMK_XA_NUM_UPDATES; } - if (pcmk__xe_is(input, PCMK_XE_CIB)) { - int updates = 0; - int epoch = 0; - int admin_epoch = 0; - - int replace_updates = 0; - int replace_epoch = 0; - int replace_admin_epoch = 0; - - const char *reason = NULL; - const char *peer = pcmk__xe_get(req, PCMK__XA_SRC); - const char *digest = pcmk__xe_get(req, PCMK_XA_DIGEST); - - if (digest) { - char *digest_verify = pcmk__digest_xml(input, true); - - if (!pcmk__str_eq(digest_verify, digest, pcmk__str_casei)) { - pcmk__err("Digest mis-match on replace from %s: %s vs. %s " - "(expected)", - peer, digest_verify, digest); - reason = "digest mismatch"; - - } else { - pcmk__info("Digest matched on replace from %s: %s", peer, - digest); - } - free(digest_verify); + if (reason != NULL) { + pcmk__info("Replacement %d.%d.%d from %s not applied to %d.%d.%d: " + "current %s is greater than the replacement", + replace_admin_epoch, replace_epoch, replace_updates, peer, + admin_epoch, epoch, updates, reason); + return pcmk_rc_old_data; + } - } else { - pcmk__trace("No digest to verify"); - } + *cib = pcmk__xml_replace_with_copy(*cib, input); + + pcmk__info("Replaced %d.%d.%d with %d.%d.%d from %s", admin_epoch, epoch, + updates, replace_admin_epoch, replace_epoch, replace_updates, + peer); + return pcmk_rc_ok; +} - cib_version_details(existing_cib, &admin_epoch, &epoch, &updates); - cib_version_details(input, &replace_admin_epoch, &replace_epoch, &replace_updates); +static int +process_replace_xpath(xmlNode *request, xmlNode **cib) +{ + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + const char *xpath = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + xmlNode *input = cib__get_calldata(request); + uint32_t options = cib_none; - if (replace_admin_epoch < admin_epoch) { - reason = PCMK_XA_ADMIN_EPOCH; + int num_results = 0; + int rc = pcmk_rc_ok; + xmlXPathObject *xpath_obj = pcmk__xpath_search((*cib)->doc, xpath); - } else if (replace_admin_epoch > admin_epoch) { - /* no more checks */ + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none); - } else if (replace_epoch < epoch) { - reason = PCMK_XA_EPOCH; + num_results = pcmk__xpath_num_results(xpath_obj); + if (num_results == 0) { + pcmk__debug("%s: %s does not exist", op, xpath); + rc = ENXIO; + goto done; + } - } else if (replace_epoch > epoch) { - /* no more checks */ + for (int i = 0; i < num_results; i++) { + xmlNode *match = NULL; + xmlNode *parent = NULL; + xmlChar *path = NULL; - } else if (replace_updates < updates) { - reason = PCMK_XA_NUM_UPDATES; + match = pcmk__xpath_result(xpath_obj, i); + if (match == NULL) { + continue; } - if (reason != NULL) { - pcmk__info("Replacement %d.%d.%d from %s not applied to %d.%d.%d: " - "current %s is greater than the replacement", - replace_admin_epoch, replace_epoch, - replace_updates, peer, admin_epoch, epoch, updates, - reason); - result = -pcmk_err_old_data; - } else { - pcmk__info("Replaced %d.%d.%d with %d.%d.%d from %s", - admin_epoch, epoch, updates, - replace_admin_epoch, replace_epoch, replace_updates, - peer); - } + path = xmlGetNodePath(match); + pcmk__debug("Processing %s op for %s with %s", op, xpath, path); + free(path); - if (*result_cib != existing_cib) { - pcmk__xml_free(*result_cib); + if (match == *cib) { + rc = replace_cib(request, cib); + break; } - *result_cib = pcmk__xml_copy(NULL, input); - } else { - xmlNode *obj_root = NULL; + parent = match->parent; + + pcmk__xml_free(match); + pcmk__xml_copy(parent, input); - obj_root = pcmk_find_cib_element(*result_cib, section); - result = pcmk__xe_replace_match(obj_root, input); - result = pcmk_rc2legacy(result); - if (result != pcmk_ok) { - pcmk__trace("No matching object to replace"); + if (!pcmk__is_set(options, cib_multiple)) { + break; } } - return result; +done: + xmlXPathFreeObject(xpath_obj); + return rc; +} + +static int +process_replace_section(xmlNode *request, xmlNode **cib) +{ + const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + xmlNode *input = cib__get_calldata(request); + + int rc = pcmk_rc_ok; + xmlNode *obj_root = NULL; + + if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) { + input = pcmk_find_cib_element(input, section); + } + + if (input == NULL) { + pcmk__err("Cannot find matching section to replace with no input data"); + return EINVAL; + } + + if (pcmk__xe_is(input, PCMK_XE_CIB)) { + return replace_cib(request, cib); + } + + if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei) + || pcmk__xe_is(input, section)) { + + section = NULL; + } + + obj_root = pcmk_find_cib_element(*cib, section); + + rc = pcmk__xe_replace_match(obj_root, input); + if (rc != pcmk_rc_ok) { + pcmk__trace("No matching object to replace"); + } + + return rc; } int -cib__process_upgrade(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +cib__process_replace(xmlNode *req, xmlNode **cib, xmlNode **answer) { - int rc = 0; + uint32_t options = cib_none; + + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none); + + if (pcmk__is_set(options, cib_xpath)) { + return process_replace_xpath(req, cib); + } + + return process_replace_section(req, cib); +} + +int +cib__process_upgrade(xmlNode *req, xmlNode **cib, xmlNode **answer) +{ + int rc = pcmk_rc_ok; + uint32_t options = cib_none; const char *max_schema = pcmk__xe_get(req, PCMK__XA_CIB_SCHEMA_MAX); - const char *original_schema = NULL; + const char *original_schema = pcmk__xe_get(*cib, PCMK_XA_VALIDATE_WITH); const char *new_schema = NULL; + xmlNode *updated = pcmk__xml_copy(NULL, *cib); - *answer = NULL; - pcmk__trace("Processing \"%s\" event with max=%s", op, max_schema); + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none); - original_schema = pcmk__xe_get(existing_cib, PCMK_XA_VALIDATE_WITH); - rc = pcmk__update_schema(result_cib, max_schema, true, + rc = pcmk__update_schema(&updated, max_schema, !pcmk__is_set(options, cib_verbose)); - rc = pcmk_rc2legacy(rc); - new_schema = pcmk__xe_get(*result_cib, PCMK_XA_VALIDATE_WITH); + *cib = pcmk__xml_replace_with_copy(*cib, updated); + pcmk__xml_free(updated); + + new_schema = pcmk__xe_get(*cib, PCMK_XA_VALIDATE_WITH); if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) { - update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false); - update_counter(*result_cib, PCMK_XA_EPOCH, true); - update_counter(*result_cib, PCMK_XA_NUM_UPDATES, true); - return pcmk_ok; + update_counter(*cib, PCMK_XA_ADMIN_EPOCH, false); + update_counter(*cib, PCMK_XA_EPOCH, true); + update_counter(*cib, PCMK_XA_NUM_UPDATES, true); + return pcmk_rc_ok; } return rc; diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c index d25e8008231..cc278cb0a28 100644 --- a/lib/cib/cib_remote.c +++ b/lib/cib/cib_remote.c @@ -1,5 +1,5 @@ /* - * Copyright 2008-2025 the Pacemaker project contributors + * Copyright 2008-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,32 +9,33 @@ #include -#include +#include // EAGAIN, EINVAL, ENOMSG, ENOTCONN, etc. #include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include +#include // NULL +#include // calloc, free +#include // strdup +#include // shutdown, SHUT_RDWR +#include // time, time_t +#include // close + +#include // gboolean, gpointer, g_*, G_*, etc. +#include // gnutls_*, GNUTLS_* +#include // xmlNode +#include // QB_XS + +#include // cib_* +#include // cib__* +#include // pcmk__err, pcmk__xml_*, etc. +#include // mainloop_* +#include // pcmk_rc_*, pcmk_ok, pcmk_strerror, etc. +#include // PCMK_XA_OP, PCMK_XA_USER +#include // CRM_OP_REGISTER, crm_system_name // GnuTLS handshake timeout in seconds #define TLS_HANDSHAKE_TIMEOUT 5 static pcmk__tls_t *tls = NULL; -#include - typedef struct cib_remote_opaque_s { int port; char *server; @@ -78,6 +79,7 @@ cib_remote_perform_op(cib_t *cib, const char *op, const char *host, rc = cib__create_op(cib, op, host, section, data, call_options, user_name, NULL, &op_msg); + rc = pcmk_rc2legacy(rc); if (rc != pcmk_ok) { return rc; } @@ -85,7 +87,7 @@ cib_remote_perform_op(cib_t *cib, const char *op, const char *host, if (pcmk__is_set(call_options, cib_transaction)) { rc = cib__extend_transaction(cib, op_msg); pcmk__xml_free(op_msg); - return rc; + return pcmk_rc2legacy(rc); } pcmk__trace("Sending %s message to the CIB manager", op); @@ -96,11 +98,12 @@ cib_remote_perform_op(cib_t *cib, const char *op, const char *host, } pcmk__xml_free(op_msg); - if ((call_options & cib_discard_reply)) { + if (pcmk__is_set(call_options, cib_discard_reply)) { pcmk__trace("Discarding reply"); return pcmk_ok; + } - } else if (!(call_options & cib_sync_call)) { + if (!pcmk__is_set(call_options, cib_sync_call)) { return cib->call_id; } @@ -162,11 +165,6 @@ cib_remote_perform_op(cib_t *cib, const char *op, const char *host, rc = -EPROTO; } - if (rc == -pcmk_err_diff_resync) { - /* This is an internal value that clients do not and should not care about */ - rc = pcmk_ok; - } - if (rc == pcmk_ok || rc == -EPERM) { pcmk__log_xml_debug(op_reply, "passed"); @@ -175,13 +173,8 @@ cib_remote_perform_op(cib_t *cib, const char *op, const char *host, pcmk__log_xml_warn(op_reply, "failed"); } - if (output_data == NULL) { - /* do nothing more */ - - } else if (!(call_options & cib_discard_reply)) { - xmlNode *wrapper = pcmk__xe_first_child(op_reply, PCMK__XE_CIB_CALLDATA, - NULL, NULL); - xmlNode *tmp = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); + if (output_data != NULL) { + xmlNode *tmp = cib__get_calldata(op_reply); if (tmp == NULL) { pcmk__trace("No output in reply to \"%s\" command %d", op, diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 5d0617085cb..714fb812f35 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -1,26 +1,39 @@ /* * Original copyright 2004 International Business Machines - * Later changes copyright 2008-2025 the Pacemaker project contributors + * Later changes copyright 2008-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include -#include -#include -#include +#include // errno, EACCES, EAGAIN, EALREADY, etc. +#include +#include // NULL +#include // uint32_t +#include // free, getenv +#include // LOG_CRIT, LOG_INFO + +#include // gboolean, GHashTable, g_*, etc. +#include // xmlNode +#include // QB_XS + +#include // cib_*, createEmptyCib, etc. +#include // cib__*, PCMK__CIB_* +#include // pcmk_acl_*, xml_acl_* +#include // pcmk_find_cib_element +#include // pcmk__err, pcmk__xml_*, etc. +#include // crm_time_* +#include // CRM_CHECK +#include // pcmk_unpack_nvpair_blocks +#include // PCMK_OPT_*, PCMK_VALUE_* +#include // pcmk_rc_*, pcmk_err_*, pcmk_ok, etc. +#include // pcmk_rule_input_t +#include // xml_*_patchset, PCMK_XA_*, PCMK_XE_* +#include // CRM_FEATURE_SET, crm_system_name gboolean cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates) @@ -100,9 +113,9 @@ createEmptyCib(int cib_epoch) pcmk__xe_set(cib_root, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); pcmk__xe_set(cib_root, PCMK_XA_VALIDATE_WITH, pcmk__highest_schema_name()); + pcmk__xe_set_int(cib_root, PCMK_XA_ADMIN_EPOCH, 0); pcmk__xe_set_int(cib_root, PCMK_XA_EPOCH, cib_epoch); pcmk__xe_set_int(cib_root, PCMK_XA_NUM_UPDATES, 0); - pcmk__xe_set_int(cib_root, PCMK_XA_ADMIN_EPOCH, 0); config = pcmk__xe_create(cib_root, PCMK_XE_CONFIGURATION); pcmk__xe_create(cib_root, PCMK_XE_STATUS); @@ -129,22 +142,148 @@ createEmptyCib(int cib_epoch) return cib_root; } +static void +read_config(GHashTable *options, xmlNode *current_cib) +{ + crm_time_t *now = NULL; + pcmk_rule_input_t rule_input = { 0, }; + xmlNode *config = pcmk_find_cib_element(current_cib, PCMK_XE_CRM_CONFIG); + + if (config == NULL) { + return; + } + + now = crm_time_new(NULL); + rule_input.now = now; + + pcmk_unpack_nvpair_blocks(config, PCMK_XE_CLUSTER_PROPERTY_SET, + PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, &rule_input, + options, NULL); + crm_time_free(now); +} + static bool cib_acl_enabled(xmlNode *xml, const char *user) { + const char *value = NULL; + GHashTable *options = NULL; bool rc = false; - if(pcmk_acl_required(user)) { - const char *value = NULL; - GHashTable *options = pcmk__strkey_table(free, free); + if ((xml == NULL) || !pcmk_acl_required(user)) { + return false; + } + + options = pcmk__strkey_table(free, free); + read_config(options, xml); + value = pcmk__cluster_option(options, PCMK_OPT_ENABLE_ACL); + + rc = pcmk__is_true(value); + g_hash_table_destroy(options); + return rc; +} + +/*! + * \internal + * \brief Get call data from a CIB request + * + * \param[in] request CIB request XML + * + * \return Call data added by \c cib__set_calldata(), or \c NULL if none is + * found + */ +xmlNode * +cib__get_calldata(const xmlNode *request) +{ + xmlNode *wrapper = pcmk__xe_first_child(request, PCMK__XE_CIB_CALLDATA, + NULL, NULL); + + return pcmk__xe_first_child(wrapper, NULL, NULL, NULL); +} + +/*! + * \internal + * \brief Add call data to a CIB request + * + * Add a copy of \p data to a new \c PCMK__XE_CIB_CALLDATA child of \p request. + * + * \param[in,out] request CIB request XML + * \param[in] data Call data to add a copy of (if \c NULL, do nothing) + */ +void +cib__set_calldata(xmlNode *request, xmlNode *data) +{ + xmlNode *wrapper = NULL; + + if (data == NULL) { + return; + } + + wrapper = pcmk__xe_create(request, PCMK__XE_CIB_CALLDATA); + pcmk__xml_copy(wrapper, data); +} + +int +cib__perform_op_ro(cib__op_fn_t fn, xmlNode *req, xmlNode **current_cib, + xmlNode **output) +{ + int rc = pcmk_rc_ok; + const char *op = NULL; + const char *section = NULL; + const char *user = NULL; + + xmlNode *cib = NULL; + xmlNode *cib_filtered = NULL; + + pcmk__assert((fn != NULL) && (req != NULL) + && (current_cib != NULL) && (*current_cib != NULL) + && (output != NULL) && (*output == NULL)); + + op = pcmk__xe_get(req, PCMK__XA_CIB_OP); + section = pcmk__xe_get(req, PCMK__XA_CIB_SECTION); + user = pcmk__xe_get(req, PCMK__XA_CIB_USER); + + cib = *current_cib; + + if (cib_acl_enabled(cib, user) + && xml_acl_filtered_copy(user, cib, cib, &cib_filtered)) { - cib_read_config(options, xml); - value = pcmk__cluster_option(options, PCMK_OPT_ENABLE_ACL); - rc = pcmk__is_true(value); - g_hash_table_destroy(options); + if (cib_filtered == NULL) { + pcmk__debug("Pre-filtered the entire cib"); + return EACCES; + } + cib = cib_filtered; + pcmk__log_xml_trace(cib, "filtered"); + } + + pcmk__trace("Processing %s for section '%s', user '%s'", op, + pcmk__s(section, "(null)"), pcmk__s(user, "(null)")); + pcmk__log_xml_trace(req, "request"); + + rc = fn(req, &cib, output); + + if (cib_filtered == *output) { + // Let the caller have this copy + return rc; } - pcmk__trace("CIB ACL is %s", (rc? "enabled" : "disabled")); + if (*output == NULL) { + goto done; + } + + if ((*output)->doc == (*current_cib)->doc) { + // Trust the caller to check this and not free *output + goto done; + } + + if ((cib_filtered == NULL) || ((*output)->doc != cib_filtered->doc)) { + goto done; + } + + // We're about to free the document of which *output is a part + *output = pcmk__xml_copy(NULL, *output); + +done: + pcmk__xml_free(cib_filtered); return rc; } @@ -196,349 +335,312 @@ should_copy_cib(const char *op, const char *section, int call_options) return true; } -int -cib_perform_op(cib_t *cib, const char *op, uint32_t call_options, - cib__op_fn_t fn, bool is_query, const char *section, - xmlNode *req, xmlNode *input, bool manage_counters, - bool *config_changed, xmlNode **current_cib, - xmlNode **result_cib, xmlNode **diff, xmlNode **output) +/*! + * \internal + * \brief Validate that a new CIB's feature set is not newer than ours + * + * Return an error if the new CIB's feature set is newer than ours. + * + * \param[in] new_cib Result CIB after performing operation + * + * \return Standard Pacemaker return code + */ +static int +check_new_feature_set(const xmlNode *new_cib) { - const bool dry_run = pcmk__is_set(call_options, cib_dryrun); - int rc = pcmk_rc_ok; - bool check_schema = true; - bool make_copy = true; - xmlNode *top = NULL; - xmlNode *scratch = NULL; - xmlNode *patchset_cib = NULL; - xmlNode *local_diff = NULL; + const char *new_version = pcmk__xe_get(new_cib, PCMK_XA_CRM_FEATURE_SET); + int rc = pcmk__check_feature_set(new_version); - const char *user = pcmk__xe_get(req, PCMK__XA_CIB_USER); - const bool enable_acl = cib_acl_enabled(*current_cib, user); - bool with_digest = false; + if (rc == pcmk_rc_ok) { + return pcmk_rc_ok; + } - pcmk__trace("Begin %s%s%s op", (dry_run? "dry run of " : ""), - (is_query? "read-only " : ""), op); + pcmk__err("Discarding update with feature set %s greater than our own (%s)", + new_version, CRM_FEATURE_SET); + return rc; +} - CRM_CHECK(output != NULL, return ENOMSG); - CRM_CHECK(current_cib != NULL, return ENOMSG); - CRM_CHECK(result_cib != NULL, return ENOMSG); - CRM_CHECK(config_changed != NULL, return ENOMSG); +/*! + * \internal + * \brief Validate that a new CIB has a newer version attribute than an old CIB + * + * Return an error if the value of the given attribute is higher in the old CIB + * than in the new CIB. + * + * \param[in] attr Name of version attribute to check + * \param[in] old_cib \c PCMK_XE_CIB element before performing operation + * \param[in] new_cib \c PCMK_XE_CIB element from result of operation + * \param[in] request CIB request + * + * \return Standard Pacemaker return code + * + * \note \p old_cib only has to contain the top-level \c PCMK_XE_CIB element. It + * might not be a full CIB. + */ +static int +check_cib_version_attr(const char *attr, const xmlNode *old_cib, + const xmlNode *new_cib, const xmlNode *request) +{ + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + int old_version = 0; + int new_version = 0; - if(output) { - *output = NULL; - } + pcmk__xe_get_int(old_cib, attr, &old_version); + pcmk__xe_get_int(new_cib, attr, &new_version); - *result_cib = NULL; - *config_changed = false; + if (old_version < new_version) { + return pcmk_rc_ok; + } - if (fn == NULL) { - return EINVAL; + if (old_version == new_version) { + return pcmk_rc_undetermined; } - if (is_query) { - xmlNode *cib_ro = *current_cib; - xmlNode *cib_filtered = NULL; + pcmk__err("%s went backwards in %s request: %d -> %d", attr, op, + old_version, new_version); + pcmk__log_xml_warn(request, "bad-request"); - if (enable_acl - && xml_acl_filtered_copy(user, *current_cib, *current_cib, - &cib_filtered)) { + return pcmk_rc_old_data; +} - if (cib_filtered == NULL) { - pcmk__debug("Pre-filtered the entire cib"); - return EACCES; - } - cib_ro = cib_filtered; - pcmk__log_xml_trace(cib_ro, "filtered"); - } +/*! + * \internal + * \brief Validate that a new CIB has newer versions than an old CIB + * + * Return an error if: + * - \c PCMK_XA_ADMIN_EPOCH is newer in the old CIB than in the new CIB; or + * - The \c PCMK_XA_ADMIN_EPOCH attributes are equal and \c PCMK_XA_EPOCH is + * newer in the old CIB than in the new CIB. + * + * \param[in] old_cib \c PCMK_XE_CIB element before performing operation + * \param[in] new_cib \c PCMK_XE_CIB element from result of operation + * \param[in] request CIB request + * + * \return Standard Pacemaker return code + * + * \note \p old_cib only has to contain the top-level \c PCMK_XE_CIB element. It + * might not be a full CIB. + */ +static int +check_cib_versions(const xmlNode *old_cib, const xmlNode *new_cib, + const xmlNode *request) +{ + int rc = check_cib_version_attr(PCMK_XA_ADMIN_EPOCH, old_cib, new_cib, + request); - rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output); - rc = pcmk_legacy2rc(rc); + if (rc != pcmk_rc_undetermined) { + return rc; + } - if(output == NULL || *output == NULL) { - /* nothing */ + // @TODO Why aren't we checking PCMK_XA_NUM_UPDATES if epochs are equal? + rc = check_cib_version_attr(PCMK_XA_EPOCH, old_cib, new_cib, request); + if (rc == pcmk_rc_undetermined) { + rc = pcmk_rc_ok; + } - } else if(cib_filtered == *output) { - cib_filtered = NULL; /* Let them have this copy */ + return rc; +} - } else if (*output == *current_cib) { - /* They already know not to free it */ +/*! + * \internal + * \brief Set values for update origin host, client, and user in new CIB + * + * \param[in,out] new_cib Result CIB after performing operation + * \param[in] request CIB request (source of origin info) + * + * \return Standard Pacemaker return code + */ +static int +set_update_origin(xmlNode *new_cib, const xmlNode *request) +{ + const char *origin = pcmk__xe_get(request, PCMK__XA_SRC); + const char *client = pcmk__xe_get(request, PCMK__XA_CIB_CLIENTNAME); + const char *user = pcmk__xe_get(request, PCMK__XA_CIB_USER); + const char *schema = pcmk__xe_get(new_cib, PCMK_XA_VALIDATE_WITH); - } else if(cib_filtered && (*output)->doc == cib_filtered->doc) { - /* We're about to free the document of which *output is a part */ - *output = pcmk__xml_copy(NULL, *output); + if (schema == NULL) { + return pcmk_rc_cib_corrupt; + } - } else if ((*output)->doc == (*current_cib)->doc) { - /* Give them a copy they can free */ - *output = pcmk__xml_copy(NULL, *output); - } + pcmk__xe_add_last_written(new_cib); + pcmk__warn_if_schema_deprecated(schema); - pcmk__xml_free(cib_filtered); - return rc; + // pacemaker-1.2 is the earliest schema version that allow these attributes + if (pcmk__cmp_schemas_by_name(schema, "pacemaker-1.2") < 0) { + return pcmk_rc_ok; } - make_copy = should_copy_cib(op, section, call_options); + if (origin != NULL) { + pcmk__xe_set(new_cib, PCMK_XA_UPDATE_ORIGIN, origin); + } else { + pcmk__xe_remove_attr(new_cib, PCMK_XA_UPDATE_ORIGIN); + } - if (!make_copy) { - /* Conditional on v2 patch style */ + if (client != NULL) { + pcmk__xe_set(new_cib, PCMK_XA_UPDATE_CLIENT, client); + } else { + pcmk__xe_remove_attr(new_cib, PCMK_XA_UPDATE_CLIENT); + } - scratch = *current_cib; + if (user != NULL) { + pcmk__xe_set(new_cib, PCMK_XA_UPDATE_USER, user); + } else { + pcmk__xe_remove_attr(new_cib, PCMK_XA_UPDATE_USER); + } - // Make a copy of the top-level element to store version details - top = pcmk__xe_create(NULL, (const char *) scratch->name); - pcmk__xe_copy_attrs(top, scratch, pcmk__xaf_none); - patchset_cib = top; - - pcmk__xml_commit_changes(scratch->doc); - pcmk__xml_doc_set_flags(scratch->doc, pcmk__xf_tracking); - if (enable_acl) { - pcmk__enable_acl(*current_cib, scratch, user); - } + return pcmk_rc_ok; +} - rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output); - rc = pcmk_legacy2rc(rc); +int +cib__perform_op_rw(enum cib_variant variant, cib__op_fn_t fn, xmlNode *req, + bool *config_changed, xmlNode **cib, xmlNode **diff, + xmlNode **output) +{ + int rc = pcmk_rc_ok; - /* If scratch points to a new object now (for example, after an erase - * operation), then *current_cib should point to the same object. - * - * @TODO Enable tracking and ACLs and calculate changes? Change tracking - * and unpacked ACLs didn't carry over to new object. - */ - *current_cib = scratch; + const char *op = NULL; + const char *section = NULL; + const char *user = NULL; + uint32_t call_options = cib_none; + bool enable_acl = false; + bool manage_version = true; - } else { - scratch = pcmk__xml_copy(NULL, *current_cib); - patchset_cib = *current_cib; + /* PCMK_XE_CIB element containing version numbers from before the operation. + * This may or may not point to a full CIB XML tree. Do not free, as this + * will be used as an alias for another pointer. + */ + xmlNode *old_versions = NULL; - pcmk__xml_doc_set_flags(scratch->doc, pcmk__xf_tracking); - if (enable_acl) { - pcmk__enable_acl(*current_cib, scratch, user); - } + xmlNode *top = NULL; - rc = (*fn) (op, call_options, section, req, input, *current_cib, - &scratch, output); - rc = pcmk_legacy2rc(rc); + pcmk__assert((fn != NULL) && (req != NULL) + && (config_changed != NULL) && (!*config_changed) + && (cib != NULL) && (*cib != NULL) + && (diff != NULL) && (*diff == NULL) + && (output != NULL) && (*output == NULL)); - /* @TODO This appears to be a hack to determine whether scratch points - * to a new object now, without saving the old pointer (which may be - * invalid now) for comparison. Confirm this, and check more clearly. - */ - if (!pcmk__xml_doc_all_flags_set(scratch->doc, pcmk__xf_tracking)) { - pcmk__trace("Inferring changes after %s op", op); - pcmk__xml_commit_changes(scratch->doc); - if (enable_acl) { - pcmk__enable_acl(*current_cib, scratch, user); - } - pcmk__xml_mark_changes(*current_cib, scratch); - } - CRM_CHECK(*current_cib != scratch, return EINVAL); + op = pcmk__xe_get(req, PCMK__XA_CIB_OP); + section = pcmk__xe_get(req, PCMK__XA_CIB_SECTION); + user = pcmk__xe_get(req, PCMK__XA_CIB_USER); + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &call_options, cib_none); + + enable_acl = cib_acl_enabled(*cib, user); + + pcmk__trace("Processing %s for section '%s', user '%s'", op, + pcmk__s(section, "(null)"), pcmk__s(user, "(null)")); + pcmk__log_xml_trace(req, "request"); + + if (!should_copy_cib(op, section, call_options)) { + // Make a copy of the top-level element to store version details + top = pcmk__xe_create(NULL, (const char *) (*cib)->name); + pcmk__xe_copy_attrs(top, *cib, pcmk__xaf_none); + old_versions = top; + + } else { + old_versions = *cib; + *cib = pcmk__xml_copy(NULL, *cib); + } + + pcmk__xml_commit_changes((*cib)->doc); + pcmk__xml_doc_set_flags((*cib)->doc, pcmk__xf_tracking); + if (enable_acl) { + pcmk__enable_acl(*cib, *cib, user); } - xml_acl_disable(scratch); /* Allow the system to make any additional changes */ + rc = fn(req, cib, output); + + // Allow ourselves to make any additional necessary changes + xml_acl_disable(*cib); + + if (rc != pcmk_rc_ok) { + goto done; + } - if ((rc == pcmk_rc_ok) && (scratch == NULL)) { + if (*cib == NULL) { rc = EINVAL; goto done; + } - } else if ((rc == pcmk_rc_ok) && xml_acl_denied(scratch)) { + if (xml_acl_denied(*cib)) { pcmk__trace("ACL rejected part or all of the proposed changes"); rc = EACCES; goto done; - - } else if (rc != pcmk_rc_ok) { - goto done; } /* If the CIB is from a file, we don't need to check that the feature set is * supported. All we care about in that case is the schema version, which * is checked elsewhere. */ - if (scratch && (cib == NULL || cib->variant != cib_file)) { - const char *new_version = pcmk__xe_get(scratch, - PCMK_XA_CRM_FEATURE_SET); - - rc = pcmk__check_feature_set(new_version); + if (variant != cib_file) { + rc = check_new_feature_set(*cib); if (rc != pcmk_rc_ok) { - pcmk__err("Discarding update with feature set '%s' greater than " - "our own '%s'", - new_version, CRM_FEATURE_SET); goto done; } } - if (patchset_cib != NULL) { - int old = 0; - int new = 0; - - pcmk__xe_get_int(scratch, PCMK_XA_ADMIN_EPOCH, &new); - pcmk__xe_get_int(patchset_cib, PCMK_XA_ADMIN_EPOCH, &old); - - if (old > new) { - pcmk__err("%s went backwards: %d -> %d (Opts: %#x)", - PCMK_XA_ADMIN_EPOCH, old, new, call_options); - pcmk__log_xml_warn(req, "Bad Op"); - pcmk__log_xml_warn(input, "Bad Data"); - rc = pcmk_rc_old_data; - - } else if (old == new) { - pcmk__xe_get_int(scratch, PCMK_XA_EPOCH, &new); - pcmk__xe_get_int(patchset_cib, PCMK_XA_EPOCH, &old); - if (old > new) { - pcmk__err("%s went backwards: %d -> %d (Opts: %#x)", - PCMK_XA_EPOCH, old, new, call_options); - pcmk__log_xml_warn(req, "Bad Op"); - pcmk__log_xml_warn(input, "Bad Data"); - rc = pcmk_rc_old_data; - } - } - } - - pcmk__trace("Massaging CIB contents"); - pcmk__strip_xml_text(scratch); + rc = check_cib_versions(old_versions, *cib, req); - if (make_copy) { - static time_t expires = 0; - time_t tm_now = time(NULL); + pcmk__strip_xml_text(*cib); - if (expires < tm_now) { - expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */ - with_digest = true; - } + if (pcmk__xe_attr_is_true(req, PCMK__XA_CIB_UPDATE)) { + /* This is a replace operation as a reply to a sync request. Keep + * whatever versions are in the received CIB. + */ + manage_version = false; } - local_diff = xml_create_patchset(0, patchset_cib, scratch, - config_changed, manage_counters); + /* If we didn't make a copy, the diff will only be accurate for the + * top-level PCMK_XE_CIB element + */ + *diff = xml_create_patchset(0, old_versions, *cib, config_changed, + manage_version); - pcmk__log_xml_changes(LOG_TRACE, scratch); - pcmk__xml_commit_changes(scratch->doc); + /* pcmk__xml_commit_changes() resets document private data, so call it even + * if there were no changes. + */ + pcmk__xml_commit_changes((*cib)->doc); - if(local_diff) { - if (with_digest) { - pcmk__xml_patchset_add_digest(local_diff, scratch); - } - pcmk__log_xml_patchset(LOG_INFO, local_diff); - pcmk__log_xml_trace(local_diff, "raw patch"); - } - - if (make_copy && (local_diff != NULL)) { - // Original to compare against doesn't exist - pcmk__if_tracing( - { - // Validate the calculated patch set - int test_rc = pcmk_ok; - int format = 1; - xmlNode *cib_copy = pcmk__xml_copy(NULL, patchset_cib); - - pcmk__xe_get_int(local_diff, PCMK_XA_FORMAT, &format); - test_rc = xml_apply_patchset(cib_copy, local_diff, - manage_counters); - - if (test_rc != pcmk_ok) { - pcmk__xml_write_temp_file(cib_copy, "PatchApply:calculated", - NULL); - pcmk__xml_write_temp_file(patchset_cib, "PatchApply:input", - NULL); - pcmk__xml_write_temp_file(scratch, "PatchApply:actual", - NULL); - pcmk__xml_write_temp_file(local_diff, "PatchApply:diff", - NULL); - pcmk__err("v%d patchset error, patch failed to apply: %s " - "(%d)", - format, pcmk_rc_str(pcmk_legacy2rc(test_rc)), - test_rc); - } - pcmk__xml_free(cib_copy); - }, - {} - ); - } - - if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei)) { - /* Throttle the amount of costly validation we perform due to status updates - * a) we don't really care whats in the status section - * b) we don't validate any of its contents at the moment anyway - */ - check_schema = false; + if (*diff == NULL) { + goto done; } - /* === scratch must not be modified after this point === - * Exceptions, anything in: + pcmk__log_xml_patchset(LOG_INFO, *diff); - static filter_t filter[] = { - { 0, PCMK_XA_CRM_DEBUG_ORIGIN }, - { 0, PCMK_XA_CIB_LAST_WRITTEN }, - { 0, PCMK_XA_UPDATE_ORIGIN }, - { 0, PCMK_XA_UPDATE_CLIENT }, - { 0, PCMK_XA_UPDATE_USER }, - }; + /* *cib must not be modified after this point, except for the attributes for + * which pcmk__xa_filterable() returns true */ if (*config_changed && !pcmk__is_set(call_options, cib_no_mtime)) { - const char *schema = pcmk__xe_get(scratch, PCMK_XA_VALIDATE_WITH); - - if (schema == NULL) { - rc = pcmk_rc_cib_corrupt; - } - - pcmk__xe_add_last_written(scratch); - pcmk__warn_if_schema_deprecated(schema); - - /* Make values of origin, client, and user in scratch match - * the ones in req (if the schema allows the attributes) - */ - if (pcmk__cmp_schemas_by_name(schema, "pacemaker-1.2") >= 0) { - const char *origin = pcmk__xe_get(req, PCMK__XA_SRC); - const char *client = pcmk__xe_get(req, PCMK__XA_CIB_CLIENTNAME); - - if (origin != NULL) { - pcmk__xe_set(scratch, PCMK_XA_UPDATE_ORIGIN, origin); - } else { - pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_ORIGIN); - } - - if (client != NULL) { - pcmk__xe_set(scratch, PCMK_XA_UPDATE_CLIENT, user); - } else { - pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_CLIENT); - } - - if (user != NULL) { - pcmk__xe_set(scratch, PCMK_XA_UPDATE_USER, user); - } else { - pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_USER); - } + rc = set_update_origin(*cib, req); + if (rc != pcmk_rc_ok) { + goto done; } } - pcmk__trace("Perform validation: %s", pcmk__btoa(check_schema)); - if ((rc == pcmk_rc_ok) && check_schema - && !pcmk__configured_schema_validates(scratch)) { + // Skip validation for status-only updates, since we allow anything there + if ((rc == pcmk_rc_ok) + && !pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei) + && !pcmk__configured_schema_validates(*cib)) { + rc = pcmk_rc_schema_validation; } - done: - - *result_cib = scratch; - - /* @TODO: This may not work correctly with !make_copy, since we don't +done: + /* @TODO This may not work correctly when !should_copy_cib(), since we don't * keep the original CIB. */ - if ((rc != pcmk_rc_ok) && cib_acl_enabled(patchset_cib, user) - && xml_acl_filtered_copy(user, patchset_cib, scratch, result_cib)) { + if ((rc != pcmk_rc_ok) && cib_acl_enabled(old_versions, user)) { + xmlNode *saved_cib = *cib; - if (*result_cib == NULL) { - pcmk__debug("Pre-filtered the entire cib result"); + if (xml_acl_filtered_copy(user, old_versions, *cib, cib)) { + if (*cib == NULL) { + pcmk__debug("Pre-filtered the entire cib result"); + } + pcmk__xml_free(saved_cib); } - pcmk__xml_free(scratch); - } - - if(diff) { - *diff = local_diff; - } else { - pcmk__xml_free(local_diff); } pcmk__xml_free(top); - pcmk__trace("Done"); return rc; } @@ -548,7 +650,7 @@ cib__create_op(cib_t *cib, const char *op, const char *host, const char *user_name, const char *client_name, xmlNode **op_msg) { - CRM_CHECK((cib != NULL) && (op_msg != NULL), return -EPROTO); + CRM_CHECK((cib != NULL) && (op_msg != NULL), return EPROTO); *op_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND); @@ -568,14 +670,9 @@ cib__create_op(cib_t *cib, const char *op, const char *host, pcmk__trace("Sending call options: %.8lx, %d", (long) call_options, call_options); pcmk__xe_set_int(*op_msg, PCMK__XA_CIB_CALLOPT, call_options); + cib__set_calldata(*op_msg, data); - if (data != NULL) { - xmlNode *wrapper = pcmk__xe_create(*op_msg, PCMK__XE_CIB_CALLDATA); - - pcmk__xml_copy(wrapper, data); - } - - return pcmk_ok; + return pcmk_rc_ok; } /*! @@ -592,24 +689,25 @@ validate_transaction_request(const xmlNode *request) const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); const char *host = pcmk__xe_get(request, PCMK__XA_CIB_HOST); const cib__operation_t *operation = NULL; - int rc = cib__get_operation(op, &operation); + int rc = pcmk_rc_ok; + rc = cib__get_operation(op, &operation); if (rc != pcmk_rc_ok) { // cib__get_operation() logs error return rc; } - if (!pcmk__is_set(operation->flags, cib__op_attr_transaction)) { + if (operation->type == cib__op_commit_transact) { pcmk__err("Operation %s is not supported in CIB transactions", op); return EOPNOTSUPP; } if (host != NULL) { pcmk__err("Operation targeting a specific node (%s) is not supported " - "in a CIB transaction", - host); + "in a CIB transaction", host); return EOPNOTSUPP; } + return pcmk_rc_ok; } @@ -620,11 +718,13 @@ validate_transaction_request(const xmlNode *request) * \param[in,out] cib CIB client whose transaction to extend * \param[in,out] request Request to add to transaction * - * \return Legacy Pacemaker return code + * \return Standard Pacemaker return code */ int cib__extend_transaction(cib_t *cib, xmlNode *request) { + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + const char *client_id = NULL; int rc = pcmk_rc_ok; pcmk__assert((cib != NULL) && (request != NULL)); @@ -637,18 +737,16 @@ cib__extend_transaction(cib_t *cib, xmlNode *request) if (rc == pcmk_rc_ok) { pcmk__xml_copy(cib->transaction, request); + return pcmk_rc_ok; + } - } else { - const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); - const char *client_id = NULL; + cib->cmds->client_id(cib, NULL, &client_id); - cib->cmds->client_id(cib, NULL, &client_id); - pcmk__err("Failed to add '%s' operation to transaction for client %s: " - "%s", - op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc)); - pcmk__log_xml_info(request, "failed"); - } - return pcmk_rc2legacy(rc); + pcmk__err("Failed to add '%s' operation to transaction for client %s: %s", + op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc)); + pcmk__log_xml_info(request, "failed"); + + return rc; } void @@ -658,12 +756,9 @@ cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc) cib_callback_client_t *blob = NULL; if (msg != NULL) { - xmlNode *wrapper = NULL; - pcmk__xe_get_int(msg, PCMK__XA_CIB_RC, &rc); pcmk__xe_get_int(msg, PCMK__XA_CIB_CALLID, &call_id); - wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_CALLDATA, NULL, NULL); - output = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); + output = cib__get_calldata(msg); } blob = cib__lookup_id(call_id); @@ -676,11 +771,6 @@ cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc) pcmk__debug("No cib object supplied"); } - if (rc == -pcmk_err_diff_resync) { - /* This is an internal value that clients do not and should not care about */ - rc = pcmk_ok; - } - if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) { pcmk__trace("Invoking callback %s for call %d", pcmk__s(blob->id, "without ID"), call_id); @@ -732,43 +822,15 @@ cib_native_notify(gpointer data, gpointer user_data) pcmk__trace("Callback invoked..."); } -void -cib_read_config(GHashTable * options, xmlNode * current_cib) -{ - xmlNode *config = NULL; - crm_time_t *now = NULL; - - if (options == NULL || current_cib == NULL) { - return; - } - - now = crm_time_new(NULL); - - g_hash_table_remove_all(options); - - config = pcmk_find_cib_element(current_cib, PCMK_XE_CRM_CONFIG); - if (config) { - pcmk_rule_input_t rule_input = { - .now = now, - }; - - pcmk_unpack_nvpair_blocks(config, PCMK_XE_CLUSTER_PROPERTY_SET, - PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, &rule_input, - options, NULL); - } - - pcmk__validate_cluster_options(options); - - crm_time_free(now); -} - int cib_internal_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options, const char *user_name) { - /* Note: *output_data gets set only for create and query requests. There are - * a lot of opportunities to clean up, clarify, check/enforce things, etc. + /* @COMPAT *output_data gets set only for create and query requests. Setting + * it for create requests is deprecated since 2.1.8. When that behavior is + * removed, we can restrict freeing it to read-only operations + * (cib__perform_op_ro()). */ int (*delegate)(cib_t *cib, const char *op, const char *host, const char *section, xmlNode *data, xmlNode **output_data, @@ -815,7 +877,7 @@ cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output, NULL); diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL); - if (rc < pcmk_ok || diff == NULL) { + if ((rc < pcmk_ok) || (diff == NULL)) { return rc; } @@ -823,24 +885,29 @@ cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output, pcmk__log_xml_patchset(level, diff); } - if (input != NULL) { - rc = cib__process_apply_patch(NULL, cib_none, NULL, event, diff, input, - output, NULL); + if (input == NULL) { + return rc; + } - if (rc != pcmk_ok) { - pcmk__debug("Update didn't apply: %s (%d) %p", pcmk_strerror(rc), - rc, *output); + if (*output != input) { + pcmk__xml_free(*output); + *output = pcmk__xml_copy(NULL, input); + } - if (rc == -pcmk_err_old_data) { - pcmk__trace("Masking error, we already have the supplied " - "update"); - return pcmk_ok; - } - pcmk__xml_free(*output); - *output = NULL; - return rc; - } + rc = xml_apply_patchset(*output, diff, true); + if (rc == pcmk_ok) { + return pcmk_ok; } + + pcmk__debug("Update didn't apply: %s (%d)", pcmk_strerror(rc), rc); + + if (rc == -pcmk_err_old_data) { + // Mask this error, since it means we already have the supplied update + return pcmk_ok; + } + + // Some other error + g_clear_pointer(output, pcmk__xml_free); return rc; } diff --git a/lib/cluster/cluster.c b/lib/cluster/cluster.c index 7003f7a6852..518322118de 100644 --- a/lib/cluster/cluster.c +++ b/lib/cluster/cluster.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -119,13 +119,21 @@ pcmk_cluster_disconnect(pcmk_cluster_t *cluster) const enum pcmk_cluster_layer cluster_layer = pcmk_get_cluster_layer(); const char *cluster_layer_s = pcmk_cluster_layer_text(cluster_layer); + /* @TODO Either decouple this from cluster disconnection, or move the caches + * to pcmk_cluster_t as suggested in comments in membership.c. + */ + pcmk__cluster_destroy_node_caches(); + + if (cluster == NULL) { + return EINVAL; + } + pcmk__info("Disconnecting from %s cluster layer", cluster_layer_s); switch (cluster_layer) { #if SUPPORT_COROSYNC case pcmk_cluster_layer_corosync: pcmk__corosync_disconnect(cluster); - pcmk__cluster_destroy_node_caches(); return pcmk_rc_ok; #endif // SUPPORT_COROSYNC diff --git a/lib/cluster/corosync.c b/lib/cluster/corosync.c index d354aaf28b6..0768f9cfb9a 100644 --- a/lib/cluster/corosync.c +++ b/lib/cluster/corosync.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,29 +9,33 @@ #include -#include -#include // PRIu64, etc. -#include -#include +#include // ENXIO, EINVAL +#include // PRIu32, PRIu64, PRIx32 #include -#include // uint32_t, etc. -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include +#include // NULL +#include // uint32_t, uint64_t +#include // sscanf +#include // free +#include // strerror, strchr +#include // gid_t, pid_t, uid_t +#include // sleep + +#include // cmap_* +#include // cs_*, CS_* +#include // quorum_* +#include // gboolean, gpointer, g_*, G_PRIORITY_HIGH +#include // xmlNode +#include // QB_XS + +#include // pcmk_cluster_*, etc. +#include // pcmk__cluster_private_t members +#include // pcmk__corosync2rc, pcmk__err, etc. +#include // crm_ipc_is_authentic_process +#include // CRM_LOG_ASSERT +#include // mainloop_* +#include // PCMK_VALUE_MEMBER +#include // CRM_EX_FATAL, crm_exit, pcmk_rc_*, etc. +#include // PCMK_XA_*, PCMK_XE_* #include "crmcluster_private.h" @@ -457,6 +461,9 @@ pcmk__corosync_quorum_connect(gboolean (*dispatch)(unsigned long long, * \param[in,out] cluster Initialized cluster object to connect * * \return Standard Pacemaker return code + * + * \note This initializes the node caches on success by calling + * \c pcmk__get_node(). */ int pcmk__corosync_connect(pcmk_cluster_t *cluster) @@ -466,8 +473,6 @@ pcmk__corosync_connect(pcmk_cluster_t *cluster) pcmk__node_status_t *local_node = NULL; int rc = pcmk_rc_ok; - pcmk__cluster_init_node_caches(); - if (cluster_layer != pcmk_cluster_layer_corosync) { pcmk__err("Invalid cluster layer: %s " QB_XS " cluster_layer=%d", cluster_layer_s, cluster_layer); diff --git a/lib/common/cib.c b/lib/common/cib.c index f9f187a88eb..94882caf831 100644 --- a/lib/common/cib.c +++ b/lib/common/cib.c @@ -161,8 +161,8 @@ pcmk_cib_parent_name_for(const char *element_name) /*! * \brief Find an element in the CIB * - * \param[in,out] cib Top-level CIB XML to search - * \param[in] element_name Name of CIB element to search for + * \param[in] cib Top-level CIB XML to search + * \param[in] element_name Name of CIB element to search for * * \return XML element in \p cib corresponding to \p element_name * (or \p cib itself if element is unknown or not found) diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index cb12b69c0a0..321ff8c7c4c 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -1,5 +1,5 @@ /* - * Copyright 2018-2025 the Pacemaker project contributors + * Copyright 2018-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -146,9 +146,6 @@ void pcmk__free_acls(GList *acls); G_GNUC_INTERNAL void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user); -G_GNUC_INTERNAL -bool pcmk__is_user_in_group(const char *user, const char *group); - G_GNUC_INTERNAL void pcmk__apply_acl(xmlNode *xml); diff --git a/lib/common/ipc_server.c b/lib/common/ipc_server.c index 5e7aa6e6ed3..3d1725f7d99 100644 --- a/lib/common/ipc_server.c +++ b/lib/common/ipc_server.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -1020,32 +1020,21 @@ pcmk__ipc_send_ack_as(const char *function, int line, pcmk__client_t *c, * \internal * \brief Add an IPC server to the main loop for the CIB manager API * - * \param[out] ipcs_ro New IPC server for read-only CIB manager API - * \param[out] ipcs_rw New IPC server for read/write CIB manager API - * \param[out] ipcs_shm New IPC server for shared-memory CIB manager API - * \param[in] ro_cb IPC callbacks for read-only API - * \param[in] rw_cb IPC callbacks for read/write and shared-memory APIs + * \param[out] ipcs Where to store newly created IPC server + * \param[in] cb IPC callbacks * * \note This function exits fatally on error. - * \note There is no actual difference between the three IPC endpoints other - * than their names. */ -void pcmk__serve_based_ipc(qb_ipcs_service_t **ipcs_ro, - qb_ipcs_service_t **ipcs_rw, - qb_ipcs_service_t **ipcs_shm, - struct qb_ipcs_service_handlers *ro_cb, - struct qb_ipcs_service_handlers *rw_cb) +void +pcmk__serve_based_ipc(qb_ipcs_service_t **ipcs, + struct qb_ipcs_service_handlers *cb) { - *ipcs_ro = mainloop_add_ipc_server(PCMK__SERVER_BASED_RO, - QB_IPC_NATIVE, ro_cb); - - *ipcs_rw = mainloop_add_ipc_server(PCMK__SERVER_BASED_RW, - QB_IPC_NATIVE, rw_cb); + pcmk__assert((ipcs != NULL) && (*ipcs == NULL) && (cb != NULL)); - *ipcs_shm = mainloop_add_ipc_server(PCMK__SERVER_BASED_SHM, - QB_IPC_SHM, rw_cb); + *ipcs = mainloop_add_ipc_server(pcmk__server_ipc_name(pcmk_ipc_based), + QB_IPC_SHM, cb); - if (*ipcs_ro == NULL || *ipcs_rw == NULL || *ipcs_shm == NULL) { + if (*ipcs == NULL) { pcmk__crit("Failed to create %s IPC server; shutting down", pcmk__server_log_name(pcmk_ipc_based)); pcmk__crit("Verify pacemaker and pacemaker_remote are not both " @@ -1054,27 +1043,6 @@ void pcmk__serve_based_ipc(qb_ipcs_service_t **ipcs_ro, } } -/*! - * \internal - * \brief Destroy IPC servers for the CIB manager API - * - * \param[out] ipcs_ro IPC server for read-only the CIB manager API - * \param[out] ipcs_rw IPC server for read/write the CIB manager API - * \param[out] ipcs_shm IPC server for shared-memory the CIB manager API - * - * \note This is a convenience function for calling qb_ipcs_destroy() for each - * argument. - */ -void -pcmk__stop_based_ipc(qb_ipcs_service_t *ipcs_ro, - qb_ipcs_service_t *ipcs_rw, - qb_ipcs_service_t *ipcs_shm) -{ - qb_ipcs_destroy(ipcs_ro); - qb_ipcs_destroy(ipcs_rw); - qb_ipcs_destroy(ipcs_shm); -} - /*! * \internal * \brief Add an IPC server to the main loop for the controller API @@ -1086,7 +1054,9 @@ pcmk__stop_based_ipc(qb_ipcs_service_t *ipcs_ro, qb_ipcs_service_t * pcmk__serve_controld_ipc(struct qb_ipcs_service_handlers *cb) { - return mainloop_add_ipc_server(CRM_SYSTEM_CRMD, QB_IPC_NATIVE, cb); + pcmk__assert(cb != NULL); + + return mainloop_add_ipc_server(CRM_SYSTEM_CRMD, QB_IPC_SHM, cb); } /*! @@ -1102,8 +1072,10 @@ void pcmk__serve_attrd_ipc(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { + pcmk__assert((ipcs != NULL) && (*ipcs == NULL) && (cb != NULL)); + *ipcs = mainloop_add_ipc_server(pcmk__server_ipc_name(pcmk_ipc_attrd), - QB_IPC_NATIVE, cb); + QB_IPC_SHM, cb); if (*ipcs == NULL) { pcmk__crit("Failed to create %s IPC server; shutting down", @@ -1150,8 +1122,10 @@ void pcmk__serve_fenced_ipc(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { + pcmk__assert((ipcs != NULL) && (*ipcs == NULL) && (cb != NULL)); + *ipcs = mainloop_add_ipc_server_with_prio(pcmk__server_ipc_name(pcmk_ipc_fenced), - QB_IPC_NATIVE, cb, QB_LOOP_HIGH); + QB_IPC_SHM, cb, QB_LOOP_HIGH); if (*ipcs == NULL) { pcmk__crit("Failed to create %s IPC server; shutting down", @@ -1175,8 +1149,10 @@ void pcmk__serve_pacemakerd_ipc(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { + pcmk__assert((ipcs != NULL) && (*ipcs == NULL) && (cb != NULL)); + *ipcs = mainloop_add_ipc_server(pcmk__server_ipc_name(pcmk_ipc_pacemakerd), - QB_IPC_NATIVE, cb); + QB_IPC_SHM, cb); if (*ipcs == NULL) { pcmk__crit("Failed to create %s IPC server; shutting down", @@ -1206,8 +1182,10 @@ void pcmk__serve_schedulerd_ipc(qb_ipcs_service_t **ipcs, struct qb_ipcs_service_handlers *cb) { + pcmk__assert((ipcs != NULL) && (*ipcs == NULL) && (cb != NULL)); + *ipcs = mainloop_add_ipc_server(pcmk__server_ipc_name(pcmk_ipc_schedulerd), - QB_IPC_NATIVE, cb); + QB_IPC_SHM, cb); if (*ipcs == NULL) { pcmk__crit("Failed to create %s IPC server; shutting down", diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index a63854c8d9d..1f9587a98b3 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -29,7 +29,6 @@ struct trigger_s { gboolean trigger; void *user_data; guint id; - }; struct mainloop_timer_s { @@ -41,6 +40,21 @@ struct mainloop_timer_s { void *userdata; }; +static GHashTable *mainloop_children = NULL; +static qb_array_t *gio_map = NULL; + +static void +child_free(mainloop_child_t *child) +{ + if (child->timerid != 0) { + pcmk__trace("Removing timer %d", child->timerid); + g_source_remove(child->timerid); + child->timerid = 0; + } + free(child->desc); + free(child); +} + static gboolean crm_trigger_prepare(GSource * source, gint * timeout) { @@ -335,6 +349,8 @@ mainloop_destroy_signal_entry(int sig) * \note The true signal handler merely sets a mainloop trigger to call this * dispatch function via the mainloop. Therefore, the dispatch function * does not need to be async-safe. + * \note The added signal handler gets freed by \c mainloop_cleanup() if it is + * not freed manually using \c mainloop_destroy_signal(). */ gboolean mainloop_add_signal(int sig, void (*dispatch) (int sig)) @@ -398,15 +414,17 @@ mainloop_destroy_signal(int sig) return TRUE; } -static qb_array_t *gio_map = NULL; - +/*! + * \internal + * \brief Free data structures used for the mainloop + * + * \todo This is incomplete. Free other data structures created in this file. + */ void -mainloop_cleanup(void) +mainloop_cleanup(void) { - if (gio_map != NULL) { - qb_array_free(gio_map); - gio_map = NULL; - } + g_clear_pointer(&mainloop_children, g_hash_table_destroy); + g_clear_pointer(&gio_map, qb_array_free); for (int sig = 0; sig < NSIG; ++sig) { mainloop_destroy_signal_entry(sig); @@ -595,31 +613,6 @@ struct qb_ipcs_poll_handlers gio_poll_funcs = { .dispatch_del = gio_poll_dispatch_del, }; -static enum qb_ipc_type -pick_ipc_type(enum qb_ipc_type requested) -{ - const char *env = pcmk__env_option(PCMK__ENV_IPC_TYPE); - - if (env && strcmp("shared-mem", env) == 0) { - return QB_IPC_SHM; - } else if (env && strcmp("socket", env) == 0) { - return QB_IPC_SOCKET; - } else if (env && strcmp("posix", env) == 0) { - return QB_IPC_POSIX_MQ; - } else if (env && strcmp("sysv", env) == 0) { - return QB_IPC_SYSV_MQ; - } else if (requested == QB_IPC_NATIVE) { - /* We prefer shared memory because the server never blocks on - * send. If part of a message fits into the socket, libqb - * needs to block until the remainder can be sent also. - * Otherwise the client will wait forever for the remaining - * bytes. - */ - return QB_IPC_SHM; - } - return requested; -} - qb_ipcs_service_t * mainloop_add_ipc_server(const char *name, enum qb_ipc_type type, struct qb_ipcs_service_handlers *callbacks) @@ -639,7 +632,7 @@ mainloop_add_ipc_server_with_prio(const char *name, enum qb_ipc_type type, gio_map = qb_array_create_2(64, sizeof(struct gio_to_qb_poll), 1); } - server = qb_ipcs_create(name, 0, pick_ipc_type(type), callbacks); + server = qb_ipcs_create(name, 0, QB_IPC_NATIVE, callbacks); if (server == NULL) { pcmk__err("Could not create %s IPC server: %s (%d)", name, @@ -673,21 +666,6 @@ mainloop_del_ipc_server(qb_ipcs_service_t * server) } } -struct mainloop_io_s { - char *name; - void *userdata; - - int fd; - guint source; - crm_ipc_t *ipc; - GIOChannel *channel; - - int (*dispatch_fn_ipc) (const char *buffer, ssize_t length, gpointer userdata); - int (*dispatch_fn_io) (gpointer userdata); - void (*destroy_fn) (gpointer userdata); - -}; - /*! * \internal * \brief I/O watch callback function (GIOFunc) @@ -954,43 +932,41 @@ mainloop_add_fd(const char *name, int priority, int fd, void *userdata, struct mainloop_fd_callbacks * callbacks) { mainloop_io_t *client = NULL; + const GIOCondition condition = G_IO_IN|G_IO_HUP|G_IO_NVAL|G_IO_ERR; - if (fd >= 0) { - client = calloc(1, sizeof(mainloop_io_t)); - if (client == NULL) { - return NULL; - } - client->name = strdup(name); - client->userdata = userdata; - - if (callbacks) { - client->destroy_fn = callbacks->destroy; - client->dispatch_fn_io = callbacks->dispatch; - } + if (fd < 0) { + errno = EINVAL; + return NULL; + } - client->fd = fd; - client->channel = g_io_channel_unix_new(fd); - client->source = - g_io_add_watch_full(client->channel, priority, - (G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR), mainloop_gio_callback, - client, mainloop_gio_destroy); + client = pcmk__assert_alloc(1, sizeof(mainloop_io_t)); + client->name = pcmk__str_copy(name); + client->userdata = userdata; - /* Now that mainloop now holds a reference to channel, - * thanks to g_io_add_watch_full(), drop ours from g_io_channel_unix_new(). - * - * This means that channel will be free'd by: - * g_main_context_dispatch() or g_source_remove() - * -> g_source_destroy_internal() - * -> g_source_callback_unref() - * shortly after mainloop_gio_destroy() completes - */ - g_io_channel_unref(client->channel); - pcmk__trace("Added connection %d for %s[%p].%d", client->source, - client->name, client, fd); - } else { - errno = EINVAL; + if (callbacks != NULL) { + client->destroy_fn = callbacks->destroy; + client->dispatch_fn_io = callbacks->dispatch; } + client->fd = fd; + client->channel = g_io_channel_unix_new(fd); + client->source = g_io_add_watch_full(client->channel, priority, condition, + mainloop_gio_callback, client, + mainloop_gio_destroy); + + /* Now that mainloop now holds a reference to channel, thanks to + * g_io_add_watch_full(), drop ours from g_io_channel_unix_new(). + * + * This means that channel will be free'd by: + * g_main_context_dispatch() or g_source_remove() + * -> g_source_destroy_internal() + * -> g_source_callback_unref() + * shortly after mainloop_gio_destroy() completes + */ + g_io_channel_unref(client->channel); + + pcmk__trace("Added connection %d for %s[%p].%d", client->source, + client->name, client, fd); return client; } @@ -1003,12 +979,15 @@ mainloop_del_fd(mainloop_io_t *client) pcmk__trace("Removing client %s[%p]", client->name, client); - // mainloop_gio_destroy() gets called during source removal + /* g_source_remove() marks the source as destroyed, unsets the source + * callback (mainloop_gio_callback()), and destroys the callback data (the + * client) via the notify function (mainloop_gio_destroy()). We can rely on + * mainloop_gio_callback() not getting called again for this source, and on + * the client being destroyed. + */ g_source_remove(client->source); } -static GList *child_list = NULL; - pid_t mainloop_child_pid(mainloop_child_t * child) { @@ -1039,48 +1018,41 @@ mainloop_clear_child_userdata(mainloop_child_t * child) child->privatedata = NULL; } -/* good function name */ -static void -child_free(mainloop_child_t *child) -{ - if (child->timerid != 0) { - pcmk__trace("Removing timer %d", child->timerid); - g_source_remove(child->timerid); - child->timerid = 0; - } - free(child->desc); - free(child); -} - -/* terrible function name */ static int -child_kill_helper(mainloop_child_t *child) +child_kill_helper(const mainloop_child_t *child) { - int rc; - if (child->flags & mainloop_leave_pid_group) { + int rc = 0; + + if (pcmk__is_set(child->flags, mainloop_leave_pid_group)) { pcmk__debug("Killing PID %lld only. Leaving its process group intact.", (long long) child->pid); rc = kill(child->pid, SIGKILL); + } else { pcmk__debug("Killing PID %lld's entire process group", (long long) child->pid); rc = kill(-child->pid, SIGKILL); } - if (rc < 0) { - if (errno != ESRCH) { - pcmk__err("kill(%d, KILL) failed: %s", child->pid, strerror(errno)); - } - return -errno; + if (rc == 0) { + return pcmk_rc_ok; } - return 0; + + rc = errno; + if (rc == ESRCH) { + return rc; + } + + pcmk__err("kill(%lld, KILL) failed: %s", (long long) child->pid, + strerror(rc)); + return rc; } static gboolean child_timeout_callback(gpointer p) { mainloop_child_t *child = p; - int rc = 0; + int rc = pcmk_rc_ok; child->timerid = 0; if (child->timeout) { @@ -1090,7 +1062,7 @@ child_timeout_callback(gpointer p) } rc = child_kill_helper(child); - if (rc == -ESRCH) { + if (rc == ESRCH) { /* Nothing left to do. pid doesn't exist */ return FALSE; } @@ -1111,15 +1083,16 @@ child_waitpid(mainloop_child_t *child, int flags) int signo = 0; int status = 0; int exitcode = 0; - bool callback_needed = true; rc = waitpid(child->pid, &status, flags); + if (rc == 0) { // WNOHANG in flags, and child status is not available pcmk__trace("Child process %lld (%s) still active", (long long) child->pid, child->desc); - callback_needed = false; + return false; + } - } else if (rc != child->pid) { + if (rc != child->pid) { /* According to POSIX, possible conditions: * - child->pid was non-positive (process group or any child), * and rc is specific child @@ -1131,8 +1104,8 @@ child_waitpid(mainloop_child_t *child, int flags) */ signo = SIGCHLD; exitcode = 1; - pcmk__notice("Wait for child process %d (%s) interrupted: %s", - child->pid, child->desc, pcmk_rc_str(errno)); + pcmk__notice("Wait for child process %lld (%s) interrupted: %s", + (long long) child->pid, child->desc, strerror(errno)); } else if (WIFEXITED(status)) { exitcode = WEXITSTATUS(status); @@ -1148,37 +1121,45 @@ child_waitpid(mainloop_child_t *child, int flags) #ifdef WCOREDUMP // AIX, SunOS, maybe others } else if (WCOREDUMP(status)) { core = 1; - pcmk__err("Child process %d (%s) dumped core", child->pid, child->desc); + pcmk__err("Child process %lld (%s) dumped core", (long long) child->pid, + child->desc); #endif } else { // flags must contain WUNTRACED and/or WCONTINUED to reach this pcmk__trace("Child process %lld (%s) stopped or continued", (long long) child->pid, child->desc); - callback_needed = false; + return false; } - if (callback_needed && child->exit_fn) { + if (child->exit_fn != NULL) { child->exit_fn(child, core, signo, exitcode); } - return callback_needed; + + return true; +} + +static gboolean +child_waitpid_no_hang(gpointer key, gpointer value, gpointer user_data) +{ + mainloop_child_t *child = value; + + if (!child_waitpid(child, WNOHANG)) { + return FALSE; + } + + pcmk__trace("Removing completed process %lld from child table", + (long long) child->pid); + return TRUE; } static void child_death_dispatch(int signal) { - for (GList *iter = child_list; iter; ) { - GList *saved = iter; - mainloop_child_t *child = iter->data; - - iter = iter->next; - if (child_waitpid(child, WNOHANG)) { - pcmk__trace("Removing completed process %lld from child list", - (long long) child->pid); - child_list = g_list_remove_link(child_list, saved); - g_list_free(saved); - child_free(child); - } + if (mainloop_children == NULL) { + return; } + + g_hash_table_foreach_remove(mainloop_children, child_waitpid_no_hang, NULL); } static gboolean @@ -1196,55 +1177,79 @@ child_signal_init(gpointer p) gboolean mainloop_child_kill(pid_t pid) { - GList *iter; + /* It is impossible to block SIGKILL. This allows us to call waitpid() + * without the WNOHANG flag. + */ + int waitflags = 0; + int rc = pcmk_rc_ok; + char *pid_s = NULL; mainloop_child_t *child = NULL; - mainloop_child_t *match = NULL; - /* It is impossible to block SIGKILL, this allows us to - * call waitpid without WNOHANG flag.*/ - int waitflags = 0, rc = 0; - - for (iter = child_list; iter != NULL && match == NULL; iter = iter->next) { - child = iter->data; - if (pid == child->pid) { - match = child; - } + gboolean killed = FALSE; + + if (mainloop_children == NULL) { + // We're not tracking any children, so don't kill this PID + goto done; } - if (match == NULL) { - return FALSE; + pid_s = pcmk__assert_asprintf("%lld", (long long) pid); + child = g_hash_table_lookup(mainloop_children, pid_s); + + if (child == NULL) { + // We're not tracking a child with this PID, so don't kill it + goto done; } - rc = child_kill_helper(match); - if(rc == -ESRCH) { - /* It's gone, but hasn't shown up in waitpid() yet. Wait until we get - * SIGCHLD and let handler clean it up as normal (so we get the correct - * return code/status). The blocking alternative would be to call - * child_waitpid(match, 0). + rc = child_kill_helper(child); + if (rc == ESRCH) { + /* kill() didn't find the PID. Assume this means the process is in some + * intermediate stage of exiting. Wait until we get SIGCHLD and let + * the handler clean it up as normal, so that we get the correct return + * code/status. The blocking alternative would be to call + * child_waitpid(child, 0). + * + * Treat this as success in the meantime. */ pcmk__trace("Waiting for signal that child process %lld completed", - (long long) match->pid); - return TRUE; + (long long) child->pid); + killed = TRUE; + goto done; + } - } else if(rc != 0) { - /* If KILL for some other reason set the WNOHANG flag since we - * can't be certain what happened. + if (rc != pcmk_rc_ok) { + /* If kill() failed for some other reason, set the WNOHANG flag, since + * we can't be certain what happened. */ waitflags = WNOHANG; } - if (!child_waitpid(match, waitflags)) { - /* not much we can do if this occurs */ - return FALSE; + if (!child_waitpid(child, waitflags)) { + /* waitpid() didn't find the child. This shouldn't happen if kill() + * succeeded. In any case, there's no obviously good way to proceed. + * Treat this as a failure. + * + * @TODO Should we call the child's exit_fn with a failure here, and/or + * free the mainloop_child_t? We can hope that we successfully wait on + * it in response to a SIGCHLD that comes later. But we might just never + * do anything with this child again. + * + * @TODO Consider using GSubprocess for all subprocess tracking. + */ + goto done; } - child_list = g_list_remove(child_list, match); - child_free(match); - return TRUE; + g_hash_table_remove(mainloop_children, pid_s); + killed = TRUE; + +done: + free(pid_s); + return killed; } /* Create/Log a new tracked process * To track a process group, use -pid * + * Newly created child will be freed by mainloop_cleanup(), if not sooner. + * * @TODO Using a non-positive pid (i.e. any child, or process group) would * likely not be useful since we will free the child after the first * completed process. @@ -1256,6 +1261,7 @@ mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, pcmk__mainloop_child_exit_fn_t exit_fn) { static bool need_init = TRUE; + bool is_new = false; mainloop_child_t *child = pcmk__assert_alloc(1, sizeof(mainloop_child_t)); child->pid = pid; @@ -1270,7 +1276,17 @@ mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, child->timerid = pcmk__create_timer(timeout, child_timeout_callback, child); } - child_list = g_list_append(child_list, child); + if (mainloop_children == NULL) { + mainloop_children = pcmk__strkey_table(free, + (GDestroyNotify) child_free); + } + + is_new = g_hash_table_insert(mainloop_children, + pcmk__assert_asprintf("%lld", (long long) pid), + child); + + // It should be impossible to have this PID in mainloop_children already + CRM_LOG_ASSERT(is_new); if(need_init) { need_init = FALSE; @@ -1282,6 +1298,7 @@ mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, } } +// Newly created child will be freed by mainloop_cleanup(), if not sooner void mainloop_child_add(pid_t pid, int timeout, const char *desc, void *privatedata, pcmk__mainloop_child_exit_fn_t exit_fn) diff --git a/lib/common/mock.c b/lib/common/mock.c index 69d3dc4e4a2..00769da5f59 100644 --- a/lib/common/mock.c +++ b/lib/common/mock.c @@ -1,5 +1,5 @@ /* - * Copyright 2021-2025 the Pacemaker project contributors + * Copyright 2021-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -223,9 +223,9 @@ __wrap_getpid(void) } -/* setgrent(), getgrent() and endgrent() +/* getgrnam() * - * If pcmk__mock_grent is set to true, getgrent() will behave as if the only + * If pcmk__mock_getgrnam is set to true, getgrnam() will behave as if the only * groups on the system are: * * - grp0 (user0, user1) @@ -233,10 +233,7 @@ __wrap_getpid(void) * - grp2 (user2, user1) */ -bool pcmk__mock_grent = false; - -// Index of group that will be returned next from getgrent() -static int group_idx = 0; +bool pcmk__mock_getgrnam = false; // Data used for testing static const char* grp0_members[] = { @@ -260,43 +257,28 @@ static const char* grp2_members[] = { * string literal = const char* (cannot be changed b/c ? ) * vs. char* (it's getting casted to this) */ -static const int NUM_GROUPS = 3; static struct group groups[] = { {(char*)"grp0", (char*)"", 0, (char**)grp0_members}, {(char*)"grp1", (char*)"", 1, (char**)grp1_members}, {(char*)"grp2", (char*)"", 2, (char**)grp2_members}, }; -// This function resets the group_idx to 0. -void -__wrap_setgrent(void) { - if (pcmk__mock_grent) { - group_idx = 0; - } else { - __real_setgrent(); - } -} - -/* This function returns the next group entry in the list of groups, or - * NULL if there aren't any left. - * group_idx is a global variable which keeps track of where you are in the list +/* This function returns the group entry whose name matches the argument, or + * NULL if no match is found. */ struct group * -__wrap_getgrent(void) { - if (pcmk__mock_grent) { - if (group_idx >= NUM_GROUPS) { - return NULL; +__wrap_getgrnam(const char *name) { + if (pcmk__mock_getgrnam) { + for (int i = 0; i < PCMK__NELEM(groups); i++) { + if (pcmk__str_eq(groups[i].gr_name, name, pcmk__str_none)) { + return &groups[i]; + } } - return &groups[group_idx++]; - } else { - return __real_getgrent(); - } -} -void -__wrap_endgrent(void) { - if (!pcmk__mock_grent) { - __real_endgrent(); + return NULL; + + } else { + return __real_getgrnam(name); } } diff --git a/lib/common/mock_private.h b/lib/common/mock_private.h index 5fa2d918fa6..896f526ae11 100644 --- a/lib/common/mock_private.h +++ b/lib/common/mock_private.h @@ -1,5 +1,5 @@ /* - * Copyright 2021-2025 the Pacemaker project contributors + * Copyright 2021-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -66,18 +66,14 @@ extern bool pcmk__mock_getpid; pid_t __real_getpid(void); pid_t __wrap_getpid(void); -extern bool pcmk__mock_grent; -void __real_setgrent(void); -void __wrap_setgrent(void); -struct group * __wrap_getgrent(void); -struct group * __real_getgrent(void); -void __wrap_endgrent(void); -void __real_endgrent(void); - extern bool pcmk__mock_getpwnam; struct passwd *__real_getpwnam(const char *name); struct passwd *__wrap_getpwnam(const char *name); +extern bool pcmk__mock_getgrnam; +struct group *__real_getgrnam(const char *name); +struct group *__wrap_getgrnam(const char *name); + extern bool pcmk__mock_readlink; ssize_t __real_readlink(const char *restrict path, char *restrict buf, size_t bufsize); diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 3650cf5f286..4149f3e757e 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -180,8 +180,9 @@ is_config_change(xmlNode *xml) return FALSE; } +// Guaranteed to return non-NULL static xmlNode * -xml_create_patchset_v2(xmlNode *source, xmlNode *target) +xml_create_patchset_v2(const xmlNode *source, xmlNode *target) { int lpc = 0; GList *gIter = NULL; @@ -192,11 +193,6 @@ xml_create_patchset_v2(xmlNode *source, xmlNode *target) xmlNode *patchset = NULL; pcmk__assert(target != NULL); - - if (!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) { - return NULL; - } - pcmk__assert(target->doc != NULL); docpriv = target->doc->_private; @@ -240,8 +236,9 @@ xml_create_patchset_v2(xmlNode *source, xmlNode *target) return patchset; } +// *config_changed is unchanged if the return value is NULL xmlNode * -xml_create_patchset(int format, xmlNode *source, xmlNode *target, +xml_create_patchset(int format, const xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version) { bool local_config_changed = false; @@ -467,7 +464,7 @@ check_patchset_versions(const xmlNode *cib_root, const xmlNode *patchset) vfields[i], current[0], current[1], current[2], source[0], source[1], source[2], target[0], target[1], target[2]); - return pcmk_rc_diff_resync; + return pcmk_rc_diff_failed; } if (current[i] > source[i]) { pcmk__info("Current %s is too high " diff --git a/lib/common/patchset_display.c b/lib/common/patchset_display.c index 9cc1663eaf0..110e56e3a98 100644 --- a/lib/common/patchset_display.c +++ b/lib/common/patchset_display.c @@ -50,9 +50,10 @@ xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset) const char *fmt = pcmk__xe_get(patchset, PCMK_XA_FORMAT); const char *digest = pcmk__xe_get(patchset, PCMK_XA_DIGEST); - out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt); + out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], + pcmk__s(fmt, "(no format)")); rc = out->info(out, "Diff: +++ %d.%d.%d %s", - add[0], add[1], add[2], digest); + add[0], add[1], add[2], pcmk__s(digest, "(no digest)")); } else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) { rc = out->info(out, "Local-only Change: %d.%d.%d", diff --git a/lib/common/schemas.c b/lib/common/schemas.c index 87d87f95910..f571ec3bc52 100644 --- a/lib/common/schemas.c +++ b/lib/common/schemas.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -749,7 +749,7 @@ pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name) } static bool -validate_with(xmlNode *xml, pcmk__schema_t *schema, +validate_with(xmlDoc *doc, pcmk__schema_t *schema, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context) { @@ -773,7 +773,8 @@ validate_with(xmlNode *xml, pcmk__schema_t *schema, switch (schema->validator) { case pcmk__schema_validator_rng: cache = (relaxng_ctx_cache_t **) &(schema->cache); - valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache); + valid = validate_with_relaxng(doc, error_handler, + error_handler_context, file, cache); break; default: pcmk__err("Unknown validator type: %d", schema->validator); @@ -785,28 +786,29 @@ validate_with(xmlNode *xml, pcmk__schema_t *schema, } static bool -validate_with_silent(xmlNode *xml, pcmk__schema_t *schema) +validate_with_silent(xmlDoc *doc, pcmk__schema_t *schema) { - bool rc, sl_backup = silent_logging; + bool rc = false; + bool sl_backup = silent_logging; + silent_logging = TRUE; - rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR)); + rc = validate_with(doc, schema, (xmlRelaxNGValidityErrorFunc) xml_log, + GUINT_TO_POINTER(LOG_ERR)); silent_logging = sl_backup; return rc; } bool -pcmk__validate_xml(xmlNode *xml_blob, const char *validation, - xmlRelaxNGValidityErrorFunc error_handler, +pcmk__validate_xml(xmlNode *xml, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context) { + const char *validation = NULL; GList *entry = NULL; pcmk__schema_t *schema = NULL; - CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return false); + CRM_CHECK((xml != NULL) && (xml->doc != NULL), return false); - if (validation == NULL) { - validation = pcmk__xe_get(xml_blob, PCMK_XA_VALIDATE_WITH); - } + validation = pcmk__xe_get(xml, PCMK_XA_VALIDATE_WITH); pcmk__warn_if_schema_deprecated(validation); entry = pcmk__get_schema(validation); @@ -818,7 +820,7 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, } schema = entry->data; - return validate_with(xml_blob, schema, error_handler, + return validate_with(xml->doc, schema, error_handler, error_handler_context); } @@ -833,8 +835,7 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, bool pcmk__configured_schema_validates(xmlNode *xml) { - return pcmk__validate_xml(xml, NULL, - (xmlRelaxNGValidityErrorFunc) xml_log, + return pcmk__validate_xml(xml, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR)); } @@ -968,23 +969,21 @@ cib_upgrade_err(void *ctx, const char *fmt, ...) /*! * \internal - * \brief Apply a single XSL transformation to given XML + * \brief Apply a single XSL transformation to the given XML document * - * \param[in] xml XML to transform + * \param[in] doc XML document * \param[in] transform XSL name * \param[in] to_logs If false, certain validation errors will be sent to * stderr rather than logged * * \return Transformed XML on success, otherwise NULL */ -static xmlNode * -apply_transformation(const xmlNode *xml, const char *transform, - gboolean to_logs) +static xmlDoc * +apply_transformation(xmlDoc *doc, const char *transform, bool to_logs) { char *xform = NULL; - xmlNode *out = NULL; - xmlDocPtr res = NULL; xsltStylesheet *xslt = NULL; + xmlDoc *result_doc = NULL; xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, transform); @@ -1002,13 +1001,11 @@ apply_transformation(const xmlNode *xml, const char *transform, /* Caller allocates private data for final result document. Intermediate * result documents are temporary and don't need private data. */ - res = xsltApplyStylesheet(xslt, xml->doc, NULL); - CRM_CHECK(res != NULL, goto cleanup); + result_doc = xsltApplyStylesheet(xslt, doc, NULL); + CRM_CHECK(result_doc != NULL, goto cleanup); xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */ - out = xmlDocGetRootElement(res); - cleanup: if (xslt) { xsltFreeStylesheet(xslt); @@ -1016,14 +1013,14 @@ apply_transformation(const xmlNode *xml, const char *transform, free(xform); - return out; + return result_doc; } /*! * \internal * \brief Perform all transformations needed to upgrade XML to next schema * - * \param[in] input_xml XML to transform + * \param[in] input_doc XML document to transform * \param[in] schema_index Index of schema that successfully validates * \p original_xml * \param[in] to_logs If false, certain validation errors will be sent to @@ -1031,15 +1028,15 @@ apply_transformation(const xmlNode *xml, const char *transform, * * \return XML result of schema transforms if successful, otherwise NULL */ -static xmlNode * -apply_upgrade(const xmlNode *input_xml, int schema_index, gboolean to_logs) +static xmlDoc * +apply_upgrade(xmlDoc *input_doc, int schema_index, bool to_logs) { pcmk__schema_t *schema = g_list_nth_data(known_schemas, schema_index); pcmk__schema_t *upgraded_schema = g_list_nth_data(known_schemas, schema_index + 1); - xmlNode *old_xml = NULL; - xmlNode *new_xml = NULL; + xmlDoc *old_doc = NULL; + xmlDoc *new_doc = NULL; xmlRelaxNGValidityErrorFunc error_handler = NULL; pcmk__assert((schema != NULL) && (upgraded_schema != NULL)); @@ -1055,34 +1052,36 @@ apply_upgrade(const xmlNode *input_xml, int schema_index, gboolean to_logs) pcmk__debug("Upgrading schema from %s to %s: applying XSL transform %s", schema->name, upgraded_schema->name, transform); - new_xml = apply_transformation(input_xml, transform, to_logs); - pcmk__xml_free(old_xml); + new_doc = apply_transformation(input_doc, transform, to_logs); + pcmk__xml_free_doc(old_doc); - if (new_xml == NULL) { + if (new_doc == NULL) { pcmk__err("XSL transform %s failed, aborting upgrade", transform); return NULL; } - input_xml = new_xml; - old_xml = new_xml; + + input_doc = new_doc; + old_doc = new_doc; } // Final result document from upgrade pipeline needs private data - pcmk__xml_new_private_data((xmlNode *) new_xml->doc); + pcmk__xml_new_private_data((xmlNode *) new_doc); // Ensure result validates with its new schema - if (!validate_with(new_xml, upgraded_schema, error_handler, + if (!validate_with(new_doc, upgraded_schema, error_handler, GUINT_TO_POINTER(LOG_ERR))) { pcmk__err("Schema upgrade from %s to %s failed: XSL transform pipeline " "produced an invalid configuration", schema->name, upgraded_schema->name); - pcmk__log_xml_debug(new_xml, "bad-transform-result"); - pcmk__xml_free(new_xml); + pcmk__log_xml_debug(xmlDocGetRootElement(new_doc), + "bad-transform-result"); + pcmk__xml_free_doc(new_doc); return NULL; } pcmk__info("Schema upgrade from %s to %s succeeded", schema->name, upgraded_schema->name); - return new_xml; + return new_doc; } /*! @@ -1109,16 +1108,13 @@ get_configured_schema(const xmlNode *xml) * after being transformed) * \param[in] max_schema_name If not NULL, do not update \p xml to any * schema later than this one - * \param[in] transform If false, do not update \p xml to any schema - * that requires an XSL transform * \param[in] to_logs If false, certain validation errors will be * sent to stderr rather than logged * * \return Standard Pacemaker return code */ int -pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform, - bool to_logs) +pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool to_logs) { int max_stable_schemas = xml_latest_schema_index(); int max_schema_index = 0; @@ -1126,12 +1122,15 @@ pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform, GList *entry = NULL; pcmk__schema_t *best_schema = NULL; pcmk__schema_t *original_schema = NULL; - xmlRelaxNGValidityErrorFunc error_handler = - to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL; + xmlRelaxNGValidityErrorFunc error_handler = NULL; CRM_CHECK((xml != NULL) && (*xml != NULL) && ((*xml)->doc != NULL), return EINVAL); + if (to_logs) { + error_handler = (xmlRelaxNGValidityErrorFunc) xml_log; + } + if (max_schema_name != NULL) { GList *max_entry = pcmk__get_schema(max_schema_name); @@ -1156,13 +1155,13 @@ pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform, for (; entry != NULL; entry = entry->next) { pcmk__schema_t *current_schema = entry->data; - xmlNode *upgrade = NULL; + xmlDoc *upgrade = NULL; if (current_schema->schema_index > max_schema_index) { break; } - if (!validate_with(*xml, current_schema, error_handler, + if (!validate_with((*xml)->doc, current_schema, error_handler, GUINT_TO_POINTER(LOG_ERR))) { pcmk__debug("Schema %s does not validate", current_schema->name); if (best_schema != NULL) { @@ -1181,8 +1180,8 @@ pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform, } // coverity[null_field] The index check ensures entry->next is not NULL - if (!transform || (current_schema->transforms == NULL) - || validate_with_silent(*xml, entry->next->data)) { + if ((current_schema->transforms == NULL) + || validate_with_silent((*xml)->doc, entry->next->data)) { /* The next schema either doesn't require a transform or validates * successfully even without the transform. Skip the transform and * try the next schema with the same XML. @@ -1190,24 +1189,27 @@ pcmk__update_schema(xmlNode **xml, const char *max_schema_name, bool transform, continue; } - upgrade = apply_upgrade(*xml, current_schema->schema_index, to_logs); + upgrade = apply_upgrade((*xml)->doc, current_schema->schema_index, + to_logs); if (upgrade == NULL) { /* The transform failed, so this schema can't be used. Later * schemas are unlikely to validate, but try anyway until we * run out of options. */ rc = pcmk_rc_transform_failed; + } else { best_schema = current_schema; pcmk__xml_free(*xml); - *xml = upgrade; + *xml = xmlDocGetRootElement(upgrade); } } if ((best_schema != NULL) && (best_schema->schema_index > original_schema->schema_index)) { - pcmk__info("%s the configuration schema to %s", - (transform? "Transformed" : "Upgraded"), best_schema->name); + + pcmk__info("Transformed the configuration schema to %s", + best_schema->name); pcmk__xe_set(*xml, PCMK_XA_VALIDATE_WITH, best_schema->name); } return rc; @@ -1253,7 +1255,7 @@ pcmk__update_configured_schema(xmlNode **xml, bool to_logs) entry = NULL; converted = pcmk__xml_copy(NULL, *xml); - if (pcmk__update_schema(&converted, NULL, true, to_logs) == pcmk_rc_ok) { + if (pcmk__update_schema(&converted, NULL, to_logs) == pcmk_rc_ok) { new_schema_name = pcmk__xe_get(converted, PCMK_XA_VALIDATE_WITH); entry = pcmk__get_schema(new_schema_name); } diff --git a/lib/common/servers.c b/lib/common/servers.c index f2e6d79ef50..e5a6715db2f 100644 --- a/lib/common/servers.c +++ b/lib/common/servers.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -29,67 +29,72 @@ * members, and libqb IPC server endpoints for both the old and new names, and * could drop the old names only after we no longer supported connections with * older nodes. + * + * @TODO It would be easy to use system_names[0] as a server's IPC name. + * Everything would automatically use the new names except for proxied + * connections from *older* Pacemaker Remote nodes. We would just have to map + * the old names to the new names in remote_proxy_new(), the same as we're + * currently mapping PCMK__SERVER_BASED_RO to PCMK__SERVER_BASED_RW there. */ static struct { const char *log_name; // Readable server name for use in logs const char *system_names[2]; // crm_system_name values (subdaemon names) - const char *ipc_names[3]; // libqb IPC names used to contact server + const char *ipc_name; // libqb IPC name used to contact server const char *message_types[3]; // IPC/cluster message types sent to server } server_info[] = { [pcmk_ipc_unknown] = { NULL, { NULL, NULL, }, - { NULL, NULL, NULL, }, + NULL, { NULL, NULL, NULL, }, }, [pcmk_ipc_attrd] = { "attribute manager", { PCMK__SERVER_ATTRD, NULL, }, - { PCMK__VALUE_ATTRD, NULL, NULL, }, + PCMK__VALUE_ATTRD, { PCMK__VALUE_ATTRD, NULL, NULL, }, }, [pcmk_ipc_based] = { "CIB manager", { PCMK__SERVER_BASED, NULL, }, - { PCMK__SERVER_BASED_RW, PCMK__SERVER_BASED_RO, - PCMK__SERVER_BASED_SHM, }, + PCMK__SERVER_BASED_RW, { CRM_SYSTEM_CIB, NULL, NULL, }, }, [pcmk_ipc_controld] = { "controller", { PCMK__SERVER_CONTROLD, NULL, }, - { PCMK__VALUE_CRMD, NULL, NULL, }, + PCMK__VALUE_CRMD, { PCMK__VALUE_CRMD, CRM_SYSTEM_DC, CRM_SYSTEM_TENGINE, }, }, [pcmk_ipc_execd] = { "executor", { PCMK__SERVER_EXECD, PCMK__SERVER_REMOTED, }, - { PCMK__VALUE_LRMD, NULL, NULL, }, + PCMK__VALUE_LRMD, { PCMK__VALUE_LRMD, NULL, NULL, }, }, [pcmk_ipc_fenced] = { "fencer", { PCMK__SERVER_FENCED, NULL, }, - { PCMK__VALUE_STONITH_NG, NULL, NULL, }, + PCMK__VALUE_STONITH_NG, { PCMK__VALUE_STONITH_NG, NULL, NULL, }, }, [pcmk_ipc_pacemakerd] = { "launcher", { PCMK__SERVER_PACEMAKERD, NULL, }, - { CRM_SYSTEM_MCP, NULL, NULL, }, + CRM_SYSTEM_MCP, { CRM_SYSTEM_MCP, NULL, NULL, }, }, [pcmk_ipc_schedulerd] = { "scheduler", { PCMK__SERVER_SCHEDULERD, NULL, }, - { CRM_SYSTEM_PENGINE, NULL, NULL, }, + CRM_SYSTEM_PENGINE, { CRM_SYSTEM_PENGINE, NULL, NULL, }, }, }; @@ -132,7 +137,7 @@ pcmk__server_log_name(enum pcmk_ipc_server server) /*! * \internal - * \brief Return the (primary) IPC endpoint name for a server + * \brief Return the IPC endpoint name for a server * * \param[in] server Server to get IPC endpoint for * @@ -145,7 +150,7 @@ pcmk__server_ipc_name(enum pcmk_ipc_server server) { CRM_CHECK((server > 0) && (server < PCMK__NELEM(server_info)), return NULL); - return server_info[server].ipc_names[0]; + return server_info[server].ipc_name; } /*! @@ -192,13 +197,11 @@ pcmk__parse_server(const char *text) return server; } } - for (name = 0; - (name < 3) && (server_info[server].ipc_names[name] != NULL); - ++name) { - if (strcmp(text, server_info[server].ipc_names[name]) == 0) { - return server; - } + + if (pcmk__str_eq(text, server_info[server].ipc_name, pcmk__str_none)) { + return server; } + for (name = 0; (name < 3) && (server_info[server].message_types[name] != NULL); ++name) { diff --git a/lib/common/tests/acl/Makefile.am b/lib/common/tests/acl/Makefile.am index e8887ff7851..36c59d89d81 100644 --- a/lib/common/tests/acl/Makefile.am +++ b/lib/common/tests/acl/Makefile.am @@ -13,8 +13,7 @@ include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pcmk__is_user_in_group_test -check_PROGRAMS += pcmk_acl_required_test +check_PROGRAMS = pcmk_acl_required_test check_PROGRAMS += xml_acl_denied_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/acl/pcmk__is_user_in_group_test.c b/lib/common/tests/acl/pcmk__is_user_in_group_test.c deleted file mode 100644 index b698b4cc244..00000000000 --- a/lib/common/tests/acl/pcmk__is_user_in_group_test.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2020-2025 the Pacemaker project contributors - * - * The version control history for this file may have further details. - * - * This source code is licensed under the GNU General Public License version 2 - * or later (GPLv2+) WITHOUT ANY WARRANTY. - */ - -#include - -#include - -#include -#include - -#include "../../crmcommon_private.h" -#include "mock_private.h" - -static void -is_pcmk__is_user_in_group(void **state) -{ - pcmk__mock_grent = true; - - // null user - assert_false(pcmk__is_user_in_group(NULL, "grp0")); - // null group - assert_false(pcmk__is_user_in_group("user0", NULL)); - // nonexistent group - assert_false(pcmk__is_user_in_group("user0", "nonexistent_group")); - // user is in group - assert_true(pcmk__is_user_in_group("user0", "grp0")); - // user is not in group - assert_false(pcmk__is_user_in_group("user2", "grp0")); - - pcmk__mock_grent = false; -} - -PCMK__UNIT_TEST(NULL, NULL, - cmocka_unit_test(is_pcmk__is_user_in_group)) diff --git a/lib/common/tests/utils/Makefile.am b/lib/common/tests/utils/Makefile.am index fc99b307958..266593bfd9a 100644 --- a/lib/common/tests/utils/Makefile.am +++ b/lib/common/tests/utils/Makefile.am @@ -17,6 +17,7 @@ check_PROGRAMS += pcmk__daemon_user_test check_PROGRAMS += pcmk__fail_attr_name_test check_PROGRAMS += pcmk__failcount_name_test check_PROGRAMS += pcmk__getpid_s_test +check_PROGRAMS += pcmk__is_user_in_group_test check_PROGRAMS += pcmk__lastfailure_name_test check_PROGRAMS += pcmk__lookup_user_test check_PROGRAMS += pcmk__realloc_test diff --git a/lib/common/tests/utils/pcmk__is_user_in_group_test.c b/lib/common/tests/utils/pcmk__is_user_in_group_test.c new file mode 100644 index 00000000000..c986e1b8eb0 --- /dev/null +++ b/lib/common/tests/utils/pcmk__is_user_in_group_test.c @@ -0,0 +1,54 @@ +/* + * Copyright 2020-2026 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include + +#include + +#include "mock_private.h" + +static int +setup(void **state) +{ + pcmk__mock_getgrnam = true; + return 0; +} + +static int +teardown(void **state) +{ + pcmk__mock_getgrnam = false; + return 0; +} + +static void +null_args(void **state) +{ + pcmk__assert_asserts(pcmk__is_user_in_group(NULL, NULL)); + pcmk__assert_asserts(pcmk__is_user_in_group(NULL, "grp0")); + pcmk__assert_asserts(pcmk__is_user_in_group("user0", NULL)); +} + +static void +user_in_group(void **state) +{ + assert_true(pcmk__is_user_in_group("user0", "grp0")); +} + +static void +user_not_in_group(void **state) +{ + assert_false(pcmk__is_user_in_group("user0", "nonexistent_group")); + assert_false(pcmk__is_user_in_group("user2", "grp0")); +} + +PCMK__UNIT_TEST(setup, teardown, + cmocka_unit_test(null_args), + cmocka_unit_test(user_in_group), + cmocka_unit_test(user_not_in_group)) diff --git a/lib/common/utils.c b/lib/common/utils.c index 9c0c115e5e8..62fe177c457 100644 --- a/lib/common/utils.c +++ b/lib/common/utils.c @@ -71,32 +71,23 @@ pcmk_common_cleanup(void) bool pcmk__is_user_in_group(const char *user, const char *group) { - struct group *grent; - char **gr_mem; + const struct group *group_entry = NULL; - if (user == NULL || group == NULL) { + pcmk__assert((user != NULL) && (group != NULL)); + + group_entry = getgrnam(group); + if (group_entry == NULL) { return false; } - - setgrent(); - while ((grent = getgrent()) != NULL) { - if (grent->gr_mem == NULL) { - continue; - } - if(strcmp(group, grent->gr_name) != 0) { - continue; - } + for (const char *const *member = (const char *const *) group_entry->gr_mem; + *member != NULL; member++) { - gr_mem = grent->gr_mem; - while (*gr_mem != NULL) { - if (!strcmp(user, *gr_mem++)) { - endgrent(); - return true; - } + if (pcmk__str_eq(user, *member, pcmk__str_none)) { + return true; } } - endgrent(); + return false; } diff --git a/lib/common/xml.c b/lib/common/xml.c index 772ef3d4f2e..14e7ab88e18 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -860,6 +860,51 @@ pcmk__xml_copy(xmlNode *parent, xmlNode *src) return copy; } +/*! + * \internal + * \brief Replace one XML node with a copy of another XML node + * + * This function handles change tracking and applies ACLs. + * + * \param[in,out] old XML node to replace + * \param[in] new XML node to copy as replacement for \p old + * + * \return Copy of \p new that replaced \p old + * + * \note This frees \p old. + * \note The caller is responsible for freeing the return value using + * \c pcmk__xml_free() (but note that it may be part of a larger XML + * tree). + */ +xmlNode * +pcmk__xml_replace_with_copy(xmlNode *old, xmlNode *new) +{ + xmlNode *new_copy = NULL; + + pcmk__assert((old != NULL) && (new != NULL)); + + /* Pass old to pcmk__xml_copy() so that new_copy gets created within the + * same doc. But old won't remain its parent. + */ + new_copy = pcmk__xml_copy(old, new); + old = xmlReplaceNode(old, new_copy); + + // old == NULL means memory allocation error + pcmk__assert(old != NULL); + + // May be unnecessary but avoids slight changes to some test outputs + pcmk__xml_tree_foreach(new_copy, pcmk__xml_reset_node_flags, NULL); + + if (pcmk__xml_doc_all_flags_set(new_copy->doc, pcmk__xf_tracking)) { + // Replaced sections may have included relevant ACLs + pcmk__apply_acl(new_copy); + } + pcmk__xml_mark_changes(old, new_copy); + pcmk__xml_free_node(old); + + return new_copy; +} + /*! * \internal * \brief Remove XML text nodes from specified XML and all its children diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index f39fc5ab4a2..4353a3a7d74 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -732,38 +732,6 @@ pcmk__xe_delete_match(xmlNode *xml, xmlNode *search) return ENXIO; } -/*! - * \internal - * \brief Replace one XML node with a copy of another XML node - * - * This function handles change tracking and applies ACLs. - * - * \param[in,out] old XML node to replace - * \param[in] new XML node to copy as replacement for \p old - * - * \note This frees \p old. - */ -static void -replace_node(xmlNode *old, xmlNode *new) -{ - // Pass old for its doc; it won't remain the parent of new - new = pcmk__xml_copy(old, new); - old = xmlReplaceNode(old, new); - - // old == NULL means memory allocation error - pcmk__assert(old != NULL); - - // May be unnecessary but avoids slight changes to some test outputs - pcmk__xml_tree_foreach(new, pcmk__xml_reset_node_flags, NULL); - - if (pcmk__xml_doc_all_flags_set(new->doc, pcmk__xf_tracking)) { - // Replaced sections may have included relevant ACLs - pcmk__apply_acl(new); - } - pcmk__xml_mark_changes(old, new); - pcmk__xml_free_node(old); -} - /*! * \internal * \brief Replace one XML subtree with a copy of another if the two match @@ -805,7 +773,7 @@ replace_xe_if_matching(xmlNode *xml, void *user_data) pcmk__log_xml_trace(xml, "replace-match"); pcmk__log_xml_trace(replace, "replace-with"); - replace_node(xml, replace); + pcmk__xml_replace_with_copy(xml, replace); // Found a match and replaced it; stop traversing tree return false; diff --git a/lib/lrmd/proxy_common.c b/lib/lrmd/proxy_common.c index 7c81dbd3757..10f8ca890f5 100644 --- a/lib/lrmd/proxy_common.c +++ b/lib/lrmd/proxy_common.c @@ -1,5 +1,5 @@ /* - * Copyright 2015-2025 the Pacemaker project contributors + * Copyright 2015-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -186,6 +186,16 @@ remote_proxy_new(lrmd_t *lrmd, struct ipc_client_callbacks *proxy_callbacks, return NULL; } + /* @COMPAT Proxied clients from Pacemaker Remote nodes older than version + * 3.0.2 can connect using PCMK__SERVER_BASED_RO. Since we use + * PCMK__SERVER_BASED_RW for everything now, and since no local or same- + * versioned proxied clients can connect to PCMK__SERVER_BASED_RO, just map + * it to PCMK__SERVER_BASED_RW here. + */ + if (pcmk__str_eq(channel, PCMK__SERVER_BASED_RO, pcmk__str_none)) { + channel = PCMK__SERVER_BASED_RW; + } + proxy = pcmk__assert_alloc(1, sizeof(remote_proxy_t)); proxy->node_name = strdup(node_name); diff --git a/lib/pacemaker/pcmk_fence.c b/lib/pacemaker/pcmk_fence.c index 186fe043d3e..8b06bf9b4a1 100644 --- a/lib/pacemaker/pcmk_fence.c +++ b/lib/pacemaker/pcmk_fence.c @@ -1,5 +1,5 @@ /* - * Copyright 2009-2025 the Pacemaker project contributors + * Copyright 2009-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -222,6 +222,7 @@ pcmk__request_fencing(stonith_t *st, const char *target, const char *action, mainloop = g_main_loop_new(NULL, FALSE); g_main_loop_run(mainloop); + g_main_loop_unref(mainloop); free(async_fence_data.name); diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c index 54393a968e2..647c2342257 100644 --- a/lib/pacemaker/pcmk_simulate.c +++ b/lib/pacemaker/pcmk_simulate.c @@ -1,5 +1,5 @@ /* - * Copyright 2021-2025 the Pacemaker project contributors + * Copyright 2021-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -404,7 +404,7 @@ profile_file(const char *xml_file, unsigned int repeat, goto done; } - if (!pcmk__validate_xml(cib_object, NULL, NULL, NULL)) { + if (!pcmk__validate_xml(cib_object, NULL, NULL)) { goto done; } diff --git a/lib/pacemaker/pcmk_verify.c b/lib/pacemaker/pcmk_verify.c index 0ae1d1fb0c6..b8653094d56 100644 --- a/lib/pacemaker/pcmk_verify.c +++ b/lib/pacemaker/pcmk_verify.c @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 the Pacemaker project contributors + * Copyright 2023-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -83,8 +83,8 @@ pcmk__verify(pcmk_scheduler_t *scheduler, pcmk__output_t *out, pcmk__xe_create(*cib_object, PCMK_XE_STATUS); } - if (!pcmk__validate_xml(*cib_object, NULL, - (xmlRelaxNGValidityErrorFunc) out->err, out)) { + if (!pcmk__validate_xml(*cib_object, (xmlRelaxNGValidityErrorFunc) out->err, + out)) { pcmk__config_has_error = true; rc = pcmk_rc_schema_validation; goto verify_done; diff --git a/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c b/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c index 2492a5a411d..29b22d2e6cf 100644 --- a/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c +++ b/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c @@ -95,8 +95,8 @@ incorrect_type(void **state) xmlNode *xml = NULL; xmlNode *result = NULL; - /* cib__process_delete() returns pcmk_ok even if given the wrong type, so we - * have to do an xpath query of the CIB to make sure it's still there. + /* cib__process_delete() returns pcmk_rc_ok even if given the wrong type, so + * we have to do an XPath query of the CIB to make sure it's still there */ assert_int_equal(pcmk_resource_delete(&xml, "Fencing", "clone"), pcmk_rc_ok); pcmk__assert_validates(xml); @@ -129,8 +129,8 @@ unknown_resource(void **state) { xmlNode *xml = NULL; - /* cib__process_delete() returns pcmk_ok even if asked to delete something - * that doesn't exist. + /* cib__process_delete() returns pcmk_rc_ok even if asked to delete + * something that doesn't exist */ assert_int_equal(pcmk_resource_delete(&xml, "no_such_resource", "primitive"), pcmk_rc_ok); pcmk__assert_validates(xml); diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index c2398958f32..f6849e41e3d 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -455,7 +455,7 @@ cluster_summary(pcmk__output_t *out, va_list args) { const char *client = pcmk__xe_get(scheduler->input, PCMK_XA_UPDATE_CLIENT); const char *origin = pcmk__xe_get(scheduler->input, - PCMK_XA_UPDATE_ORIGIN); + PCMK_XA_UPDATE_ORIGIN); PCMK__OUTPUT_LIST_HEADER(out, false, rc, "Cluster Summary"); out->message(out, "cluster-times", scheduler->priv->local_node_name, diff --git a/mk/tap.mk b/mk/tap.mk index bac6d0e7e09..194fc61d34b 100644 --- a/mk/tap.mk +++ b/mk/tap.mk @@ -1,5 +1,5 @@ # -# Copyright 2021-2025 the Pacemaker project contributors +# Copyright 2021-2026 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -19,16 +19,14 @@ CLEANFILES = *.log *.trs WRAPPED = abort \ calloc \ - endgrent \ fopen \ getenv \ getpid \ - getgrent \ + getgrnam \ getpwnam \ readlink \ realloc \ setenv \ - setgrent \ strdup \ unsetenv diff --git a/python/pacemaker/_cts/patterns.py b/python/pacemaker/_cts/patterns.py index 819eae654e3..1ab34ed72d7 100644 --- a/python/pacemaker/_cts/patterns.py +++ b/python/pacemaker/_cts/patterns.py @@ -1,7 +1,7 @@ """Pattern-holding classes for Pacemaker's Cluster Test Suite (CTS).""" __all__ = ["PatternSelector"] -__copyright__ = "Copyright 2008-2025 the Pacemaker project contributors" +__copyright__ = "Copyright 2008-2026 the Pacemaker project contributors" __license__ = "GNU General Public License version 2 or later (GPLv2+)" from pacemaker.buildoptions import BuildOptions @@ -177,7 +177,7 @@ def __init__(self): r"error.*: Operation 'reboot' .* using FencingFail returned ", r"getinfo response error: 1$", r"sbd.* error: inquisitor_child: DEBUG MODE IS ACTIVE", - r"sbd.* pcmk:\s*error:.*Connection to cib_ro.* (failed|closed)", + r"sbd.* pcmk:\s*error:.*Connection to cib_rw.* (failed|closed)", ] self._bad_news = [ diff --git a/tools/cibadmin.c b/tools/cibadmin.c index fc20bdea950..701b27f8bd7 100644 --- a/tools/cibadmin.c +++ b/tools/cibadmin.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -330,7 +330,7 @@ cibadmin_post_upgrade(pcmk__output_t *out, cib_t *cib_conn, int call_options, if (cib_conn->cmds->query(cib_conn, NULL, &obj, call_options) == pcmk_ok) { - pcmk__update_schema(&obj, NULL, true, false); + pcmk__update_schema(&obj, NULL, false); } pcmk__xml_free(obj); } @@ -349,7 +349,7 @@ cibadmin_post_default(pcmk__output_t *out, cib_t *cib_conn, int call_options, && pcmk__xe_is(output, PCMK_XE_CIB)) { // Show validation errors to stderr - pcmk__validate_xml(output, NULL, NULL, NULL); + pcmk__validate_xml(output, NULL, NULL); } return pcmk_rc2exitc(cib_rc); } diff --git a/tools/crm_attribute.c b/tools/crm_attribute.c index 4a2ce9cf72b..b79c2f9cd7c 100644 --- a/tools/crm_attribute.c +++ b/tools/crm_attribute.c @@ -66,7 +66,6 @@ struct { char *attr_value; char *dest_node; gchar *dest_uname; - gboolean inhibit; gchar *set_name; char *set_type; gchar *type; @@ -312,31 +311,15 @@ static GOptionEntry addl_entries[] = { NULL }, - { "inhibit-policy-engine", '!', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.inhibit, - NULL, NULL - }, - { NULL } }; static GOptionEntry deprecated_entries[] = { - { "attr-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.attr_id, - NULL, NULL - }, - // NOTE: resource-agents <4.2.0 (2018-10-24) uses this option { "attr-name", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, attr_name_cb, NULL, NULL }, - { "attr-value", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, update_cb, - NULL, NULL - }, - - { "delete-attr", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, delete_cb, - NULL, NULL - }, - // NOTE: resource-agents <4.2.0 (2018-10-24) uses this option { "get-value", 0, G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, value_cb, NULL, NULL @@ -812,11 +795,6 @@ main(int argc, char **argv) goto done; } - if (options.inhibit) { - pcmk__warn("Inhibiting notifications for this update"); - cib__set_call_options(cib_opts, crm_system_name, cib_inhibit_notify); - } - rc = cib__create_signon(&the_cib); if (rc != pcmk_rc_ok) { exit_code = pcmk_rc2exitc(rc); diff --git a/tools/crm_mon.c b/tools/crm_mon.c index ab42c3bac96..8853e6ba52e 100644 --- a/tools/crm_mon.c +++ b/tools/crm_mon.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -1948,7 +1948,6 @@ crm_diff_update(const char *event, xmlNode * msg) rc = xml_apply_patchset(current_cib, diff, TRUE); switch (rc) { - case -pcmk_err_diff_resync: case -pcmk_err_diff_failed: pcmk__notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc); @@ -1961,6 +1960,7 @@ crm_diff_update(const char *event, xmlNode * msg) pcmk__notice("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc); pcmk__xml_free(current_cib); current_cib = NULL; + break; } } diff --git a/tools/crm_resource.c b/tools/crm_resource.c index bbcd5f77738..538eaf4edbf 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -2390,9 +2390,7 @@ main(int argc, char **argv) cib__clean_up_connection(&cib_conn); pcmk_free_ipc_api(controld_api); pcmk_free_scheduler(scheduler); - if (mainloop != NULL) { - g_main_loop_unref(mainloop); - } + g_clear_pointer(&mainloop, g_main_loop_unref); pcmk__output_and_clear_error(&error, out); diff --git a/tools/crm_simulate.c b/tools/crm_simulate.c index 6f08a917f44..dc186c38608 100644 --- a/tools/crm_simulate.c +++ b/tools/crm_simulate.c @@ -1,5 +1,5 @@ /* - * Copyright 2009-2025 the Pacemaker project contributors + * Copyright 2009-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -381,7 +381,7 @@ setup_input(pcmk__output_t *out, const char *input, const char *output, return rc; } - if (!pcmk__validate_xml(cib_object, NULL, NULL, NULL)) { + if (!pcmk__validate_xml(cib_object, NULL, NULL)) { pcmk__xml_free(cib_object); return pcmk_rc_schema_validation; }