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 11fca1a9bff..da2b46ccfb8 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/based/Makefile.am b/daemons/based/Makefile.am index 60d19eeb315..f8ec87f25f7 100644 --- a/daemons/based/Makefile.am +++ b/daemons/based/Makefile.am @@ -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. # @@ -10,13 +10,19 @@ include $(top_srcdir)/mk/common.mk include $(top_srcdir)/mk/man.mk -EXTRA_DIST = cib.pam - halibdir = $(CRM_DAEMON_DIR) halib_PROGRAMS = pacemaker-based -noinst_HEADERS = based_transaction.h \ +noinst_HEADERS = based_callbacks.h \ + based_corosync.h \ + based_io.h \ + based_ipc.h \ + based_messages.h \ + based_notify.h \ + based_operation.h \ + based_remote.h \ + based_transaction.h \ pacemaker-based.h pacemaker_based_CFLAGS = $(CFLAGS_HARDENED_EXE) @@ -29,7 +35,9 @@ pacemaker_based_LDADD += $(CLUSTERLIBS) $(PAM_LIBS) pacemaker_based_SOURCES = pacemaker-based.c \ based_callbacks.c \ + based_corosync.c \ based_io.c \ + based_ipc.c \ based_messages.c \ based_notify.c \ based_operation.c \ diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index f02bfe74042..d23fb2864cf 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.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,121 +9,44 @@ #include -#include -#include -#include -#include - +#include // EACCES, ECONNREFUSED #include -#include -#include // uint32_t, uint64_t, UINT64_C() -#include -#include -#include // PRIu64 - -#include -#include -#include // xmlXPathObject, etc. - -#include -#include -#include - -#include +#include // NULL, size_t +#include // free +#include // u?int*_t, UINT64_C +#include // LOG_INFO, LOG_DEBUG +#include // time_t + +#include // gboolean, gpointer, g_*, etc. +#include // xmlNode +#include // LOG_TRACE + +#include // cib_call_options values +#include // cib__* +#include // pcmk__cluster_send_message +#include // pcmk__s, pcmk__str_eq +#include // crm_ipc_*, pcmk_ipc_* +#include // CRM_LOG_ASSERT, CRM_CHECK +#include // mainloop_* +#include // pcmk_rc_* +#include // PCMK_XA_*, PCMK_XE_* +#include // CRM_OP_* #include -#define EXIT_ESCALATION_MS 10000 - -qb_ipcs_service_t *ipcs_ro = NULL; -qb_ipcs_service_t *ipcs_rw = NULL; -qb_ipcs_service_t *ipcs_shm = NULL; - -static int cib_process_command(xmlNode *request, - const cib__operation_t *operation, - cib__op_fn_t op_function, xmlNode **reply, - bool privileged); - -static int32_t cib_common_callback(qb_ipcs_connection_t *c, void *data, - size_t size, bool privileged); - -static int32_t -cib_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) -{ - if (cib_shutdown_flag) { - pcmk__info("Ignoring new IPC client [%d] during shutdown", - pcmk__client_pid(c)); - return -ECONNREFUSED; - } - - if (pcmk__new_client(c, uid, gid) == NULL) { - return -ENOMEM; - } - return 0; -} - -static int32_t -cib_ipc_dispatch_rw(qb_ipcs_connection_t * c, void *data, size_t size) -{ - return cib_common_callback(c, data, size, true); -} - -static int32_t -cib_ipc_dispatch_ro(qb_ipcs_connection_t * c, void *data, size_t size) -{ - return cib_common_callback(c, data, size, false); -} - -/* Error code means? */ -static int32_t -cib_ipc_closed(qb_ipcs_connection_t * c) -{ - pcmk__client_t *client = pcmk__find_client(c); - - if (client == NULL) { - return 0; - } - pcmk__trace("Connection %p", c); - pcmk__free_client(client); - return 0; -} - -static void -cib_ipc_destroy(qb_ipcs_connection_t * c) -{ - pcmk__trace("Connection %p", c); - cib_ipc_closed(c); - if (cib_shutdown_flag) { - cib_shutdown(0); - } -} - -struct qb_ipcs_service_handlers ipc_ro_callbacks = { - .connection_accept = cib_ipc_accept, - .connection_created = NULL, - .msg_process = cib_ipc_dispatch_ro, - .connection_closed = cib_ipc_closed, - .connection_destroyed = cib_ipc_destroy -}; - -struct qb_ipcs_service_handlers ipc_rw_callbacks = { - .connection_accept = cib_ipc_accept, - .connection_created = NULL, - .msg_process = cib_ipc_dispatch_rw, - .connection_closed = cib_ipc_closed, - .connection_destroyed = cib_ipc_destroy -}; +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 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) * @@ -131,439 +54,268 @@ struct qb_ipcs_service_handlers ipc_rw_callbacks = { * \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; } static void -do_local_notify(const xmlNode *notify_src, const char *client_id, - bool sync_reply, bool from_peer) +do_local_notify(const xmlNode *xml, const char *client_id, bool sync_reply, + bool from_peer) { - int msg_id = 0; + int call_id = 0; int rc = pcmk_rc_ok; - pcmk__client_t *client_obj = NULL; + pcmk__client_t *client = NULL; uint32_t flags = crm_ipc_server_event; + const char *client_type = NULL; + const char *client_name = NULL; + const char *client_desc = ""; + const char *sync_s = (sync_reply? "synchronous" : "asynchronous"); - CRM_CHECK((notify_src != NULL) && (client_id != NULL), return); + CRM_CHECK((xml != NULL) && (client_id != NULL), return); - pcmk__trace("Performing local %ssync notification for %s", - sync_reply ? "" : "a", client_id); + if (from_peer) { + client_desc = " (originator of delegated_request)"; + } + + pcmk__trace("Performing local %s notification for %s", sync_s, client_id); - pcmk__xe_get_int(notify_src, PCMK__XA_CIB_CALLID, &msg_id); + pcmk__xe_get_int(xml, PCMK__XA_CIB_CALLID, &call_id); - client_obj = pcmk__find_client_by_id(client_id); - if (client_obj == NULL) { - pcmk__debug("Could not notify client %s%s %s of call %d result: " - "client no longer exists", - client_id, - (from_peer? " (originator of delegated request)" : ""), - (sync_reply? "synchronously" : "asynchronously"), msg_id); + client = pcmk__find_client_by_id(client_id); + if (client == NULL) { + pcmk__debug("Could not notify client %s%s %sly of call %d result: " + "client no longer exists", client_id, client_desc, sync_s, + call_id); return; } + client_type = pcmk__client_type_str(PCMK__CLIENT_TYPE(client)); + client_name = pcmk__client_name(client); + if (sync_reply) { flags = crm_ipc_flags_none; - if (client_obj->ipcs != NULL) { - msg_id = client_obj->request_id; - client_obj->request_id = 0; + if (client->ipcs != NULL) { + call_id = client->request_id; + client->request_id = 0; } } - switch (PCMK__CLIENT_TYPE(client_obj)) { + switch (PCMK__CLIENT_TYPE(client)) { case pcmk__client_ipc: - rc = pcmk__ipc_send_xml(client_obj, msg_id, notify_src, flags); + rc = pcmk__ipc_send_xml(client, call_id, xml, flags); break; case pcmk__client_tls: case pcmk__client_tcp: - rc = pcmk__remote_send_xml(client_obj->remote, notify_src); + rc = pcmk__remote_send_xml(client->remote, xml); break; default: rc = EPROTONOSUPPORT; break; } + if (rc == pcmk_rc_ok) { - pcmk__trace("Notified %s client %s%s %s of call %d result", - pcmk__client_type_str(PCMK__CLIENT_TYPE(client_obj)), - pcmk__client_name(client_obj), - (from_peer? " (originator of delegated request)" : ""), - (sync_reply? "synchronously" : "asynchronously"), msg_id); + pcmk__trace("Notified %s client %s%s %sly of call %d result", + client_type, client_name, client_desc, sync_s, call_id); } else { - pcmk__warn("Could not notify %s client %s%s %s of call %d result: %s", - pcmk__client_type_str(PCMK__CLIENT_TYPE(client_obj)), - pcmk__client_name(client_obj), - (from_peer? " (originator of delegated request)" : ""), - (sync_reply? "synchronously" : "asynchronously"), msg_id, - pcmk_rc_str(rc)); + pcmk__warn("Could not notify %s client %s%s %ssynchronously of call %d " + "result: %s", client_type, client_name, client_desc, sync_s, + call_id, pcmk_rc_str(rc)); } } -void -cib_common_callback_worker(uint32_t id, uint32_t flags, xmlNode * op_request, - pcmk__client_t *cib_client, bool privileged) +/*! + * \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) { - const char *op = pcmk__xe_get(op_request, PCMK__XA_CIB_OP); - uint32_t call_options = cib_none; - int rc = pcmk_rc_ok; + xmlNode *ping = NULL; - rc = pcmk__xe_get_flags(op_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 (!based_is_primary) { + // Only the DC sends a ping + return G_SOURCE_REMOVE; } - /* Requests with cib_transaction set should not be sent to based directly - * (outside of a commit-transaction request) - */ - if (pcmk__is_set(call_options, cib_transaction)) { - return; + if (++ping_seq < 0) { + ping_seq = 0; } - if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) { - if (flags & crm_ipc_client_response) { - xmlNode *ack = pcmk__xe_create(NULL, __func__); + g_clear_pointer(&ping_digest, free); + ping_modified_since = false; - pcmk__xe_set(ack, PCMK__XA_CIB_OP, CRM_OP_REGISTER); - pcmk__xe_set(ack, PCMK__XA_CIB_CLIENTID, cib_client->id); - pcmk__ipc_send_xml(cib_client, id, ack, flags); - cib_client->request_id = 0; - pcmk__xml_free(ack); - } - return; - - } else if (pcmk__str_eq(op, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) { - /* Update the notify filters for this client */ - int on_off = 0; - crm_exit_t status = CRM_EX_OK; - uint64_t bit = UINT64_C(0); - const char *type = pcmk__xe_get(op_request, PCMK__XA_CIB_NOTIFY_TYPE); - - pcmk__xe_get_int(op_request, PCMK__XA_CIB_NOTIFY_ACTIVATE, &on_off); - - pcmk__debug("Setting %s callbacks %s for client %s", type, - (on_off? "on" : "off"), pcmk__client_name(cib_client)); - - if (pcmk__str_eq(type, PCMK__VALUE_CIB_POST_NOTIFY, pcmk__str_none)) { - bit = cib_notify_post; - - } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_PRE_NOTIFY, - pcmk__str_none)) { - bit = cib_notify_pre; - - } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_UPDATE_CONFIRMATION, - pcmk__str_none)) { - bit = cib_notify_confirm; + 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); - } else if (pcmk__str_eq(type, PCMK__VALUE_CIB_DIFF_NOTIFY, - pcmk__str_none)) { - bit = cib_notify_diff; - - } else { - status = CRM_EX_INVALID_PARAM; - } - - if (bit != 0) { - if (on_off) { - pcmk__set_client_flags(cib_client, bit); - } else { - pcmk__clear_client_flags(cib_client, bit); - } - } - - pcmk__ipc_send_ack(cib_client, id, flags, PCMK__XE_ACK, NULL, status); - return; - } + pcmk__trace("Requesting peer digests (%lld)", ping_seq); + pcmk__cluster_send_message(NULL, pcmk_ipc_based, ping); - cib_process_request(op_request, privileged, cib_client); + pcmk__xml_free(ping); + return G_SOURCE_REMOVE; } -static int32_t -cib_common_callback(qb_ipcs_connection_t *c, void *data, size_t size, bool privileged) +/*! + * \internal + * \brief Process a reply to a \c CRM_OP_PING request + * + * 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. + * + * 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. + * + * 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 void +process_ping_reply(const xmlNode *reply) { - int rc = pcmk_rc_ok; - uint32_t id = 0; - uint32_t flags = 0; - uint32_t call_options = cib_none; - pcmk__client_t *cib_client = pcmk__find_client(c); - xmlNode *op_request = NULL; - - // Sanity-check, and parse XML from IPC data - CRM_CHECK(cib_client != NULL, return 0); - if (data == NULL) { - pcmk__debug("No IPC data from PID %d", pcmk__client_pid(c)); - return 0; - } - - pcmk__trace("Dispatching %sprivileged request from client %s", - (privileged? "" : "un"), cib_client->id); - - rc = pcmk__ipc_msg_append(&cib_client->buffer, data); - - if (rc == pcmk_rc_ipc_more) { - /* We haven't read the complete message yet, so just return. */ - return 0; + const char *host = pcmk__xe_get(reply, PCMK__XA_SRC); - } else if (rc == pcmk_rc_ok) { - /* We've read the complete message and there's already a header on - * the front. Pass it off for processing. - */ - op_request = pcmk__client_data2xml(cib_client, &id, &flags); - g_byte_array_free(cib_client->buffer, TRUE); - cib_client->buffer = NULL; + xmlNode *pong = cib__get_calldata(reply); + long long seq = 0; + const char *digest = pcmk__xe_get(pong, PCMK_XA_DIGEST); - } else { - /* Some sort of error occurred reassembling the message. All we can - * do is clean up, log an error and return. - */ - pcmk__err("Error when reading IPC message: %s", pcmk_rc_str(rc)); + xmlNode *remote_versions = cib__get_calldata(pong); - if (cib_client->buffer != NULL) { - g_byte_array_free(cib_client->buffer, TRUE); - cib_client->buffer = NULL; - } + int rc = pcmk__xe_get_ll(pong, PCMK__XA_CIB_PING_ID, &seq); - return 0; + 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; } - if (op_request) { - int rc = pcmk_rc_ok; - - rc = pcmk__xe_get_flags(op_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 (!based_is_primary) { + pcmk__trace("Ignoring ping reply %lld from %s because we are no longer " + "DC", seq, host); + return; } - if (op_request == NULL) { - pcmk__trace("Invalid message from %p", c); - pcmk__ipc_send_ack(cib_client, id, flags, PCMK__XE_NACK, NULL, - CRM_EX_PROTOCOL); - return 0; + if (digest == NULL) { + pcmk__trace("Ignoring ping reply %lld from %s with no digest", seq, + host); + return; } - if (pcmk__is_set(call_options, cib_sync_call)) { - CRM_LOG_ASSERT(flags & crm_ipc_client_response); - CRM_LOG_ASSERT(cib_client->request_id == 0); /* This means the client has two synchronous events in-flight */ - cib_client->request_id = id; /* Reply only to the last one */ + if (seq != ping_seq) { + pcmk__trace("Ignoring out-of-sequence ping reply %lld from %s", seq, + host); + return; } - if (cib_client->name == NULL) { - const char *value = pcmk__xe_get(op_request, PCMK__XA_CIB_CLIENTNAME); - - if (value == NULL) { - cib_client->name = pcmk__itoa(cib_client->pid); - } else { - cib_client->name = pcmk__str_copy(value); - } + if (ping_modified_since) { + pcmk__trace("Ignoring ping reply %lld from %s: CIB updated since", seq, + host); + return; } - pcmk__xe_set(op_request, PCMK__XA_CIB_CLIENTID, cib_client->id); - pcmk__xe_set(op_request, PCMK__XA_CIB_CLIENTNAME, cib_client->name); - - CRM_LOG_ASSERT(cib_client->user != NULL); - pcmk__update_acl_user(op_request, PCMK__XA_CIB_USER, cib_client->user); - - cib_common_callback_worker(id, flags, op_request, cib_client, privileged); - pcmk__xml_free(op_request); - - return 0; -} - -static uint64_t ping_seq = 0; -static char *ping_digest = NULL; -static bool ping_modified_since = false; - -static gboolean -cib_digester_cb(gpointer data) -{ - if (based_is_primary) { - char buffer[32]; - xmlNode *ping = pcmk__xe_create(NULL, PCMK__XE_PING); - - ping_seq++; - free(ping_digest); - ping_digest = NULL; - ping_modified_since = false; - pcmk__assert(snprintf(buffer, 32, "%" PRIu64, ping_seq) >= 0); - - pcmk__trace("Requesting peer digests (%s)", buffer); - - 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, buffer); - - pcmk__xe_set(ping, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET); - pcmk__cluster_send_message(NULL, pcmk_ipc_based, ping); - - pcmk__xml_free(ping); + if (ping_digest == NULL) { + ping_digest = pcmk__digest_xml(the_cib, true); } - return FALSE; -} -static void -process_ping_reply(xmlNode *reply) -{ - uint64_t seq = 0; - const char *host = pcmk__xe_get(reply, PCMK__XA_SRC); + pcmk__trace("Processing ping reply %lld from %s (%s)", seq, host, digest); - 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); - 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); + if (pcmk__str_eq(ping_digest, digest, pcmk__str_casei)) { 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(digest == NULL) { - pcmk__trace("Ignoring ping reply %s from %s with no digest", seq_s, - host); - - } else if(seq != ping_seq) { - pcmk__trace("Ignoring out of sequence ping reply %s from %s", seq_s, - host); - - } else if(ping_modified_since) { - pcmk__trace("Ignoring ping reply %s from %s: cib updated since", seq_s, - host); + pcmk__notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s", + 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__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); - } 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); - } - } + sync_our_cib(reply, false); } static void -parse_local_options(const pcmk__client_t *cib_client, - const cib__operation_t *operation, - const char *host, const char *op, bool *local_notify, - bool *needs_reply, bool *process, bool *needs_forward) +log_local_options(const pcmk__client_t *client, + const cib__operation_t *operation, const char *host, + const char *op) { - // 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 cib_process_request() or earlier) if host is not NULL or - * OUR_NODENAME. + /* @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(cib_client)); - - if (!pcmk__str_eq(host, OUR_NODENAME, - pcmk__str_casei|pcmk__str_null_matches)) { + pcmk__client_name(client)); - pcmk__warn("Operation '%s' is always local but its target host is " - "set to '%s'", - op, host); - } - return; - } - - if (pcmk__is_set(operation->flags, cib__op_attr_modifies) - || !pcmk__str_eq(host, OUR_NODENAME, + if (pcmk__str_eq(host, OUR_NODENAME, pcmk__str_casei|pcmk__str_null_matches)) { + return; + } - // 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(cib_client), pcmk__s(host, "all nodes")); + pcmk__warn("Operation '%s' is always local but its target host is set " + "to '%s'", op, host); return; } if (stand_alone) { pcmk__trace("Processing %s op from client %s (stand-alone)", op, - pcmk__client_name(cib_client)); + pcmk__client_name(client)); } else { pcmk__trace("Processing %saddressed %s op from client %s", ((host != NULL)? "locally " : "un"), op, - pcmk__client_name(cib_client)); + pcmk__client_name(client)); } } @@ -578,7 +330,7 @@ parse_peer_options(const cib__operation_t *operation, xmlNode *request, * (This may no longer be relevant since legacy mode was dropped; need to * trace code more closely to check.) */ - const char *host = NULL; + const char *host = pcmk__xe_get(request, PCMK__XA_CIB_HOST); 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); @@ -590,20 +342,36 @@ 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; + if (is_reply && pcmk__str_eq(op, CRM_OP_PING, pcmk__str_none)) { + process_ping_reply(request); + return false; + } + + if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SHUTDOWN, pcmk__str_none)) { + if (reply_to == NULL) { + return true; } - goto skip_is_reply; - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_SYNC_TO_ALL, - pcmk__str_none)) { - // Nothing to do + // @TODO Is this possible? + pcmk__debug("Ignoring shutdown request from %s because reply_to=%s", + originator, reply_to); + 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(op, PCMK__CIB_REQUEST_SYNC, pcmk__str_none)) { + pcmk__trace("Will notify local clients for %s reply from %s", op, + originator); + *process = false; + *needs_reply = false; + *local_notify = true; + return true; + } + + if ((reply_to != NULL) + && pcmk__str_eq(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)) { /* Only the DC (node with the oldest software) should process @@ -626,74 +394,41 @@ parse_peer_options(const cib__operation_t *operation, xmlNode *request, if (upgrade_rc != NULL) { // Our upgrade request was rejected by DC, notify clients of result + pcmk__assert(is_reply); 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__trace("Will notify local clients for %s reply from %s", op, + originator); + *process = false; + *needs_reply = 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(NULL); - 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_is_primary) { + // 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; return true; + } - } else if (host != NULL) { + if (host != NULL) { pcmk__trace("Ignoring %s request intended for CIB manager on %s", 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(op, CRM_OP_PING, pcmk__str_none)) { + return true; } + *needs_reply = false; 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), @@ -747,6 +482,160 @@ forward_request(xmlNode *request) pcmk__xe_remove_attr(request, PCMK__XA_CIB_DELEGATED_FROM); } +static int +based_perform_op_rw(xmlNode *request, const cib__operation_t *operation, + cib__op_fn_t op_function, xmlNode **output) +{ + const char *feature_set = pcmk__xe_get(the_cib, + PCMK_XA_CRM_FEATURE_SET); + xmlNode *result_cib = the_cib; + xmlNode *cib_diff = NULL; + + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + const char *originator = pcmk__xe_get(request, PCMK__XA_SRC); + uint32_t call_options = cib_none; + + bool config_changed = false; + int rc = pcmk_rc_ok; + + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &call_options, cib_none); + + /* @TODO The cib__op_attr_modifies flag means the request *may* modify + * *something*. A successful request with this flag set may not have + * modified anything (for example, a delete request when there is no match + * to delete), or it may have modified something other than the CIB (for + * example, the CIB manager's primary/secondary status). Thus we may be + * setting the ping_modified_since flag when the CIB has not been modified. + */ + ping_modified_since = true; + + /* result_cib must not be modified after cib__perform_op_rw() returns. + * + * It's not important whether the client variant is cib_native or + * cib_remote. + */ + rc = cib__perform_op_rw(cib_undefined, op_function, request, + &config_changed, &result_cib, &cib_diff, output); + + /* On validation error, include the schema-violating result CIB in any reply + * or notification that we will send. + */ + if (rc == pcmk_rc_schema_validation) { + pcmk__assert((result_cib != the_cib) && (*output == NULL)); + *output = result_cib; + goto done; + } + + // Discard result for failure or dry run + if ((rc != pcmk_rc_ok) || pcmk__any_flags_set(call_options, cib_dryrun)) { + if (result_cib != the_cib) { + pcmk__xml_free(result_cib); + } + + goto done; + } + + if (result_cib != the_cib) { + /* Always write to disk for successful ops with the writes-through flag + * set. This also avoids the need to detect ordering changes. + * + * An exception is a request within a transaction. Since a transaction + * is atomic, intermediate results must not be written to disk. + */ + const bool to_disk = !pcmk__is_set(call_options, cib_transaction) + && (config_changed + || pcmk__is_set(operation->flags, + cib__op_attr_writes_through)); + + rc = based_activate_cib(result_cib, to_disk, op); + } + + /* @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)) { + + sync_our_cib(request, true); + } + + if (digest_timer == NULL) { + digest_timer = mainloop_timer_add("based_digest_timer", 5000, false, + digest_timer_cb, NULL); + } + + mainloop_timer_start(digest_timer); + +done: + if (!pcmk__any_flags_set(call_options, + cib_dryrun|cib_inhibit_notify|cib_transaction)) { + + based_diff_notify(request, rc, cib_diff); + } + + pcmk__xml_free(cib_diff); + return rc; +} + +/*! + * \internal + * \brief Log the result of processing a CIB request locally + * + * \param[in] request Request XML + * \param[in] operation Operation info + * \param[in] rc Return code from processing the request + * \param[in] elapsed How long processing took in seconds + */ +static void +log_op_result(const xmlNode *request, const cib__operation_t *operation, int rc, + long long elapsed) +{ + int level = LOG_INFO; + + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + 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); + + int admin_epoch = 0; + int epoch = 0; + int num_updates = 0; + + if (!pcmk__is_set(operation->flags, cib__op_attr_modifies)) { + level = LOG_TRACE; + + } else if (rc != pcmk_rc_ok) { + level = LOG_WARNING; + } + + section = pcmk__s(section, "'all'"); + originator = pcmk__s(originator, "local"); + client_name = pcmk__s(client_name, "client"); + + 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); + + do_crm_log(level, + "Completed %s operation for section %s: %s (rc=%d, " + "origin=%s/%s/%s, version=%d.%d.%d)", + op, section, pcmk_rc_str(rc), rc, + originator, client_name, call_id, + admin_epoch, epoch, num_updates); + + if (elapsed > 3) { + pcmk__trace("%s operation took %llds to complete", op, elapsed); + crm_write_blackbox(0, NULL); + } +} + static void send_peer_reply(xmlNode *msg, const char *originator) { @@ -769,16 +658,17 @@ send_peer_reply(xmlNode *msg, const char *originator) * \internal * \brief Handle an IPC or CPG message containing a request * - * \param[in,out] request Request XML - * \param[in] privileged Whether privileged commands may be run - * (see cib_server_ops[] definition) - * \param[in] cib_client IPC client that sent request (or NULL if CPG) + * \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) * * \return Standard Pacemaker return code */ int -cib_process_request(xmlNode *request, bool privileged, - const pcmk__client_t *cib_client) +based_process_request(xmlNode *request, bool privileged, + const pcmk__client_t *client) { // @TODO: Break into multiple smaller functions uint32_t call_options = cib_none; @@ -786,9 +676,8 @@ cib_process_request(xmlNode *request, bool privileged, bool process = true; // Whether to process request locally now bool needs_reply = true; // 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 *op_reply = NULL; + xmlNode *reply = NULL; int rc = pcmk_rc_ok; const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); @@ -803,17 +692,20 @@ cib_process_request(xmlNode *request, bool privileged, const cib__operation_t *operation = NULL; cib__op_fn_t op_function = NULL; + xmlNode *output = NULL; + time_t start_time = 0; + 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 ((host != NULL) && (*host == '\0')) { + if (pcmk__str_empty(host)) { host = NULL; } - if (cib_client == NULL) { + if (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); @@ -836,514 +728,99 @@ cib_process_request(xmlNode *request, bool privileged, return EOPNOTSUPP; } - if (cib_client != NULL) { - parse_local_options(cib_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)) { /* 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 client %s locally because it's part " + "of a transaction", op, pcmk__client_name(client)); + + } else if (client != NULL) { + // Forward modifying and non-local requests via cluster + if (!pcmk__is_set(operation->flags, cib__op_attr_local) + && (pcmk__is_set(operation->flags, cib__op_attr_modifies) + || !pcmk__str_eq(host, OUR_NODENAME, + pcmk__str_casei|pcmk__str_null_matches))) { + + forward_request(request); + return pcmk_rc_ok; + } + + // Process locally and notify local client; no peer to reply to + needs_reply = false; + local_notify = true; + + log_local_options(client, operation, host, op); + + } else if (!parse_peer_options(operation, request, &local_notify, + &needs_reply, &process)) { + return pcmk_rc_ok; } 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"); } - if (needs_forward) { - forward_request(request); - return pcmk_rc_ok; - } - if (cib_status != pcmk_rc_ok) { rc = cib_status; pcmk__err("Ignoring request because cluster configuration is invalid " "(please repair and restart): %s", pcmk_rc_str(rc)); - op_reply = create_cib_reply(op, call_id, client_id, call_options, - pcmk_rc2legacy(rc), the_cib); - - } else if (process) { - time_t finished = 0; - time_t now = time(NULL); - int level = LOG_INFO; - const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); - const char *admin_epoch_s = NULL; - const char *epoch_s = NULL; - const char *num_updates_s = NULL; - - rc = cib_process_command(request, operation, op_function, &op_reply, - privileged); - - if (!pcmk__is_set(operation->flags, cib__op_attr_modifies)) { - 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; - } - - if (the_cib != NULL) { - admin_epoch_s = pcmk__xe_get(the_cib, PCMK_XA_ADMIN_EPOCH); - epoch_s = pcmk__xe_get(the_cib, PCMK_XA_EPOCH); - num_updates_s = pcmk__xe_get(the_cib, PCMK_XA_NUM_UPDATES); - } - do_crm_log(level, - "Completed %s operation for section %s: %s (rc=%d, origin=%s/%s/%s, version=%s.%s.%s)", - op, pcmk__s(section, "'all'"), pcmk_rc_str(rc), rc, - pcmk__s(originator, "local"), client_name, call_id, - pcmk__s(admin_epoch_s, "0"), pcmk__s(epoch_s, "0"), - pcmk__s(num_updates_s, "0")); - - finished = time(NULL); - if ((finished - now) > 3) { - pcmk__trace("%s operation took %llds to complete", op, - (long long) (finished - now)); - crm_write_blackbox(0, NULL); + if (!pcmk__is_set(call_options, cib_discard_reply)) { + reply = create_cib_reply(request, rc, the_cib); } - if (op_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 && (cib_client == NULL) - && !pcmk__is_set(call_options, cib_discard_reply)) { - send_peer_reply(op_reply, originator); - } - - if (local_notify && client_id) { - do_local_notify(process ? op_reply : request, client_id, - pcmk__is_set(call_options, cib_sync_call), - (cib_client == NULL)); - } - -done: - pcmk__xml_free(op_reply); - return rc; -} - -/*! - * \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; -} - -#define XPATH_CONFIG_CHANGE \ - "//" PCMK_XE_CHANGE \ - "[contains(@" PCMK_XA_PATH ",'/" PCMK_XE_CRM_CONFIG "/')]" - -static bool -contains_config_change(xmlNode *diff) -{ - bool changed = false; - - if (diff) { - xmlXPathObject *xpathObj = pcmk__xpath_search(diff->doc, - XPATH_CONFIG_CHANGE); - - if (pcmk__xpath_num_results(xpathObj) > 0) { - changed = true; - } - xmlXPathFreeObject(xpathObj); - } - return changed; -} - -static int -cib_process_command(xmlNode *request, const cib__operation_t *operation, - cib__op_fn_t op_function, xmlNode **reply, bool privileged) -{ - 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; - - 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("digester", 5000, FALSE, cib_digester_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); - rc = pcmk_legacy2rc(rc); - - CRM_CHECK(result_cib == NULL, pcmk__xml_free(result_cib)); + if (!process) { goto done; } - /* @COMPAT: Handle a valid write action (legacy) - * - * @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. - */ - 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)); - } - - 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); - rc = pcmk_legacy2rc(rc); - - /* Always write to disk for successful ops with the flag set. This also - * negates the need to detect ordering changes. - */ - if ((rc == pcmk_rc_ok) - && pcmk__is_set(operation->flags, cib__op_attr_writes_through)) { - - config_changed = true; - } - - 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" : "")); - - rc = activateCibXml(result_cib, config_changed, op); - rc = pcmk_legacy2rc(rc); - if (rc != pcmk_rc_ok) { - pcmk__err("Failed to activate new CIB: %s", pcmk_rc_str(rc)); - } - } - - if ((rc == pcmk_rc_ok) && contains_config_change(cib_diff)) { - cib_read_config(config_hash, result_cib); - } - - /* @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(pcmk__xe_get(the_cib, - PCMK_XA_CRM_FEATURE_SET), - "3.19.0") < 0)) { - - sync_our_cib(request, true); - } - - mainloop_timer_stop(digest_timer); - mainloop_timer_start(digest_timer); - - } else if (rc == pcmk_rc_schema_validation) { - pcmk__assert(result_cib != the_cib); + start_time = time(NULL); - if (output != NULL) { - pcmk__log_xml_info(output, "cib:output"); - pcmk__xml_free(output); - } + if (!privileged + && pcmk__is_set(operation->flags, cib__op_attr_privileged)) { - output = result_cib; + rc = EACCES; + } else if (!pcmk__is_set(operation->flags, cib__op_attr_modifies)) { + rc = cib__perform_op_ro(op_function, request, &the_cib, &output); } 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)); - - if (result_cib != the_cib) { - pcmk__xml_free(result_cib); - } + rc = based_perform_op_rw(request, operation, op_function, &output); } - 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)); - cib_diff_notify(op, pcmk_rc2legacy(rc), call_id, client_id, client_name, - originator, input, cib_diff); - } - - pcmk__log_xml_patchset(LOG_TRACE, cib_diff); + log_op_result(request, operation, rc, (time(NULL) - start_time)); - 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); + reply = create_cib_reply(request, rc, output); } - if (output != the_cib) { + if ((output != NULL) && (output->doc != the_cib->doc)) { pcmk__xml_free(output); } - pcmk__trace("done"); - pcmk__xml_free(cib_diff); - return rc; -} - -void -cib_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); - } - - cib_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); - } -} - -static gboolean -cib_force_exit(gpointer data) -{ - pcmk__notice("Exiting immediately after %s without shutdown acknowledgment", - pcmk__readable_interval(EXIT_ESCALATION_MS)); - terminate_cib(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)); -} +done: + if (!pcmk__is_set(operation->flags, cib__op_attr_modifies) + && needs_reply && !stand_alone && (client == NULL)) { -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)"); - terminate_cib(CRM_EX_OK); - return; + send_peer_reply(reply, originator); } - 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 -cib_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++; - } - - disconnects += pcmk__ipc_client_count(); - - pcmk__debug("Disconnecting %d remote clients", - pcmk__ipc_client_count()); - pcmk__foreach_ipc_client(disconnect_remote_client, NULL); - pcmk__info("Disconnected %d clients", disconnects); + if (local_notify && (client_id != NULL)) { + do_local_notify((process? reply : request), client_id, + pcmk__is_set(call_options, cib_sync_call), + (client == NULL)); } - 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(); - - } else { - pcmk__info("Waiting on %d clients to disconnect (%d)", - pcmk__ipc_client_count(), srv_stats.active_connections); - } + pcmk__xml_free(reply); + return rc; } -extern int remote_fd; -extern int remote_tls_fd; - /*! * \internal * \brief Close remote sockets, free the global CIB and quit @@ -1352,22 +829,17 @@ extern int remote_tls_fd; * skip disconnecting from the cluster layer) */ void -terminate_cib(int exit_status) +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; - } + based_ipc_cleanup(); + based_remote_cleanup(); - uninitializeCib(); + g_clear_pointer(&digest_timer, mainloop_timer_del); + g_clear_pointer(&ping_digest, free); + 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; } @@ -1379,7 +851,7 @@ terminate_cib(int exit_status) * messing with the peer caches). */ if (exit_status == CRM_EX_OK) { - pcmk_cluster_disconnect(crm_cluster); + based_cluster_disconnect(); } g_main_loop_quit(mainloop); return; @@ -1388,7 +860,6 @@ terminate_cib(int exit_status) /* 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); + based_cluster_disconnect(); crm_exit(CRM_EX_OK); } diff --git a/daemons/based/based_callbacks.h b/daemons/based/based_callbacks.h new file mode 100644 index 00000000000..57eb5db37eb --- /dev/null +++ b/daemons/based/based_callbacks.h @@ -0,0 +1,23 @@ +/* + * 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_CALLBACKS__H +#define BASED_CALLBACKS__H + +#include + +#include // xmlNode + +#include // pcmk__client_t + +int based_process_request(xmlNode *request, bool privileged, + const pcmk__client_t *client); +void based_terminate(int exit_status); + +#endif // BASED_CALLBACKS__H diff --git a/daemons/based/based_corosync.c b/daemons/based/based_corosync.c new file mode 100644 index 00000000000..a600df9063b --- /dev/null +++ b/daemons/based/based_corosync.c @@ -0,0 +1,161 @@ +/* + * 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 +#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" + +pcmk_cluster_t *based_cluster = NULL; + +static 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); + } +} + +#if SUPPORT_COROSYNC +static void +based_cpg_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 +based_cpg_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 +based_peer_change_cb(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); + } + return; + + default: + return; + } +} + +/*! + * \internal + * \brief Initialize \c based_cluster and connect to the cluster layer + * + * \return Standard Pacemaker return code + */ +int +based_cluster_connect(void) +{ + int rc = pcmk_rc_ok; + + based_cluster = pcmk_cluster_new(); + +#if SUPPORT_COROSYNC + if (pcmk_get_cluster_layer() == pcmk_cluster_layer_corosync) { + pcmk_cluster_set_destroy_fn(based_cluster, based_cpg_destroy); + pcmk_cpg_set_deliver_fn(based_cluster, based_cpg_dispatch); + pcmk_cpg_set_confchg_fn(based_cluster, pcmk__cpg_confchg_cb); + } +#endif // SUPPORT_COROSYNC + + pcmk__cluster_set_status_callback(based_peer_change_cb); + + rc = pcmk_cluster_connect(based_cluster); + if (rc != pcmk_rc_ok) { + pcmk__err("Cluster connection failed"); + } + + return rc; +} + +/*! + * \internal + * \brief Disconnect from the cluster layer and free \c based_cluster + */ +void +based_cluster_disconnect(void) +{ + if (based_cluster == NULL) { + return; + } + + pcmk_cluster_disconnect(based_cluster); + g_clear_pointer(&based_cluster, pcmk_cluster_free); +} diff --git a/daemons/based/based_corosync.h b/daemons/based/based_corosync.h new file mode 100644 index 00000000000..4df1ea151b3 --- /dev/null +++ b/daemons/based/based_corosync.h @@ -0,0 +1,20 @@ +/* + * 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 + +#include // pcmk_cluster_t + +extern pcmk_cluster_t *based_cluster; + +int based_cluster_connect(void); +void based_cluster_disconnect(void); + +#endif // BASED_COROSYNC__H diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c index 981db4ffd7b..4df713eaaea 100644 --- a/daemons/based/based_io.c +++ b/daemons/based/based_io.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,420 +9,617 @@ #include +#include // dirent, scandir +#include // errno, EACCES, ENODATA +#include // SIGPIPE #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include +#include // NULL +#include // rename +#include // free, mkdtemp +#include // strerror, strrchr, etc. +#include // stat, umask, etc. +#include // pid_t +#include // time_t +#include // _exit, fork + +#include // g_*, G_* +#include // xmlNode +#include // QB_FALSE, QB_TRUE +#include // qb_log_* + +#include // cib_file_* +#include // createEmptyCib +#include // pcmk__assert_asprintf, PCMK__XE_*, etc. +#include // CRM_CHECK +#include // mainloop_add_signal +#include // pcmk_legacy2rc, pcmk_rc_* +#include // pcmk_common_cleanup +#include // PCMK_XA_*, PCMK_XE_* #include -crm_trigger_t *cib_writer = NULL; - -int write_cib_contents(gpointer p); +static bool writes_enabled = true; +static crm_trigger_t *write_trigger = NULL; +/*! + * \internal + * \brief Process the exit status of a child forked from \c write_cib_async() + * + * \param[in] child Mainloop child data + * \param[in] core If set to 1, the child process dumped core + * \param[in] signo Signal that the child process exited with + * \param[in] exit_code Child process's exit code + */ static void -cib_rename(const char *old) +write_cib_cb(mainloop_child_t *child, int core, int signo, int exit_code) { - int new_fd; - char *new = pcmk__assert_asprintf("%s/cib.auto.XXXXXX", cib_root); + const char *error = "Could not write CIB to disk"; - umask(S_IWGRP | S_IWOTH | S_IROTH); - new_fd = mkstemp(new); + if ((exit_code != 0) && writes_enabled) { + writes_enabled = false; + error = "Disabling CIB disk writes after failure"; + } + + if ((signo == 0) && (exit_code == 0)) { + pcmk__trace("Disk write [%lld] succeeded", (long long) child->pid); + + } else if (signo == 0) { + pcmk__err("%s: process %lld exited with code %d", error, + (long long) child->pid, exit_code); - if ((new_fd < 0) || (rename(old, new) < 0)) { - pcmk__err("Couldn't archive unusable file %s (disabling disk writes " - "and continuing)", - old); - cib_writes_enabled = FALSE; } else { - pcmk__err("Archived unusable file %s as %s", old, new); + pcmk__err("%s: process %lld terminated with signal %d (%s)%s", + error, (long long) child->pid, signo, strsignal(signo), + ((core != 0)? " and dumped core" : "")); } - if (new_fd > 0) { - close(new_fd); - } - free(new); + mainloop_trigger_complete(write_trigger); } -/* - * It is the callers responsibility to free the output of this function +/*! + * \internal + * \brief Write the CIB to disk in a forked child + * + * This avoids blocking in the parent. The child writes synchronously. The + * parent tracks the child via the mainloop and runs a callback when the child + * exits. + * + * \param[in] user_data Ignored */ - -static xmlNode * -retrieveCib(const char *filename, const char *sigfile) +static int +write_cib_async(gpointer user_data) { - xmlNode *root = NULL; - int rc = cib_file_read_and_verify(filename, sigfile, &root); + int rc = pcmk_rc_ok; + pid_t pid = 0; + int blackbox_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0); + + /* 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. + * + * @TODO How? What is meant by this last sentence? + */ + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); + + pid = fork(); + if (pid < 0) { + pcmk__err("Disabling disk writes after fork failure: %s", + strerror(errno)); + writes_enabled = false; + return G_SOURCE_REMOVE; + } - if (rc == pcmk_ok) { - pcmk__info("Loaded CIB from %s (with digest %s)", filename, sigfile); - } else { - pcmk__warn("Continuing but NOT using CIB from %s (with digest %s): %s", - filename, sigfile, pcmk_strerror(rc)); - if (rc == -pcmk_err_cib_modified) { - // Archive the original files so the contents are not lost - cib_rename(filename); - cib_rename(sigfile); + if (pid > 0) { + // Parent + mainloop_child_add(pid, 0, "disk-writer", NULL, write_cib_cb); + + if (blackbox_state == QB_LOG_STATE_ENABLED) { + qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); } + + return G_SOURCE_CONTINUE; + } + + /* 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. + */ + rc = cib_file_write_with_digest(the_cib, cib_root, "cib.xml"); + rc = pcmk_legacy2rc(rc); + + pcmk_common_cleanup(); + + /* A nonzero exit code will cause further writes to be disabled. Use _exit() + * because exit() could affect the parent adversely. + * + * @TODO Investigate whether _exit() instead of exit() is really necessary. + * This goes back to commit 58cb43dc, which states that exit() may close + * things it shoudn't close. There is no explanation of what these things + * might be. The exit(2) man page states that exit() calls atexit/on_exit + * handlers and flushes open stdio streams. The exit(3) man page states that + * file created with tmpfile() are removed. But neither Pacemaker nor libqb + * uses atexit or on_exit, and it's not clear why we'd be worried about + * stdio streams. + */ + switch (rc) { + case pcmk_rc_ok: + _exit(CRM_EX_OK); + + case pcmk_rc_cib_modified: + _exit(CRM_EX_DIGEST); + + case pcmk_rc_cib_backup: + case pcmk_rc_cib_save: + _exit(CRM_EX_CANTCREAT); + + default: + _exit(CRM_EX_ERROR); } - return root; } -static int cib_archive_filter(const struct dirent * a) +/*! + * \internal + * \brief Enable CIB writes to disk (signal handler) + * + * \param[in] nsig Ignored + */ +void +based_enable_writes(int nsig) +{ + pcmk__info("(Re)enabling disk writes"); + writes_enabled = true; +} + +/*! + * \internal + * \brief Initialize data structures for \c pacemaker-based I/O + */ +void +based_io_init(void) { - int rc = 0; - // Looking for regular files starting with "cib-" and not ending in .sig - struct stat s; - char *a_path = pcmk__assert_asprintf("%s/%s", cib_root, a->d_name); + writes_enabled = !stand_alone; + if (writes_enabled + && pcmk__env_option_enabled(PCMK__SERVER_BASED, + PCMK__ENV_VALGRIND_ENABLED)) { - if(stat(a_path, &s) != 0) { - rc = errno; - pcmk__trace("%s - stat failed: %s (%d)", a->d_name, pcmk_rc_str(rc), - rc); - rc = 0; + writes_enabled = false; + pcmk__err("*** Disabling disk writes to avoid confusing Valgrind ***"); + } - } else if (!S_ISREG(s.st_mode)) { - pcmk__trace("%s - wrong type (%#o)", a->d_name, - (unsigned int) (s.st_mode & S_IFMT)); + /* @TODO Should we be setting this up if we've explicitly disabled writes + * already? + */ + mainloop_add_signal(SIGPIPE, based_enable_writes); - } else if (!g_str_has_prefix(a->d_name, "cib-")) { - pcmk__trace("%s - wrong prefix", a->d_name); + write_trigger = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_async, NULL); +} - } else if (g_str_has_suffix(a->d_name, ".sig")) { - pcmk__trace("%s - wrong suffix", a->d_name); +/*! + * \internal + * \brief Rename a CIB or digest file after digest mismatch + * + * This is just a wrapper for logging an error. The caller should disable writes + * on error. + * + * \param[in] old_path Original file path + * \param[in] new_path New file path + * + * \return Standard Pacemaker return code + */ +static int +rename_one(const char *old_path, const char *new_path) +{ + int rc = rename(old_path, new_path); - } else { - pcmk__debug("%s - candidate", a->d_name); - rc = 1; + if (rc == 0) { + return pcmk_rc_ok; } - free(a_path); + rc = errno; + pcmk__err("Failed to rename %s to %s after digest mismatch: %s. Disabling " + "disk writes.", old_path, new_path, strerror(rc)); return rc; } -static int cib_archive_sort(const struct dirent ** a, const struct dirent **b) -{ - /* Order by creation date - most recently created file first */ - int rc = 0; - struct stat buf; +#define CIBFILE "cib.xml" - time_t a_age = 0; - time_t b_age = 0; +/*! + * \internal + * \brief Archive the current CIB file in \c cib_root with its saved digest file + * + * When a CIB file's calculated digest doesn't match its saved one, we archive + * both the CIB file and its digest (".sig") file. This way the contents can be + * inspected for troubleshooting purposes. + * + * A subdirectory with a unique name is created in \c cib_root, using the + * \c mkdtemp() template \c "cib.auto.XXXXXX". Then \c CIB_FILE and + * CIB_FILE ".sig" are moved to that directory. + * + * \param[in] old_cibfile_path Original path of CIB file + * \param[in] old_sigfile_path Original path of digest file + */ +static void +archive_on_digest_mismatch(const char *old_cibfile_path, + const char *old_sigfile_path) +{ + char *new_dir = pcmk__assert_asprintf("%s/cib.auto.XXXXXX", cib_root); + char *new_cibfile_path = NULL; + char *new_sigfile_path = NULL; - char *a_path = pcmk__assert_asprintf("%s/%s", cib_root, a[0]->d_name); - char *b_path = pcmk__assert_asprintf("%s/%s", cib_root, b[0]->d_name); + umask(S_IWGRP | S_IWOTH | S_IROTH); - if(stat(a_path, &buf) == 0) { - a_age = buf.st_ctime; - } - if(stat(b_path, &buf) == 0) { - b_age = buf.st_ctime; + if (mkdtemp(new_dir) == NULL) { + pcmk__err("Failed to create directory to archive %s and %s after " + "digest mismatch: %s. Disabling disk writes.", + old_cibfile_path, old_sigfile_path, strerror(errno)); + writes_enabled = false; + goto done; } - free(a_path); - free(b_path); + new_cibfile_path = pcmk__assert_asprintf("%s/%s", new_dir, CIBFILE); + new_sigfile_path = pcmk__assert_asprintf("%s.sig", new_cibfile_path); + + if ((rename_one(old_cibfile_path, new_cibfile_path) != pcmk_rc_ok) + || (rename_one(old_sigfile_path, new_sigfile_path) != pcmk_rc_ok)) { - if(a_age > b_age) { - rc = 1; - } else if(a_age < b_age) { - rc = -1; + writes_enabled = false; + goto done; } - pcmk__trace("%s (%lu) vs. %s (%lu) : %d", - a[0]->d_name, (unsigned long)a_age, - b[0]->d_name, (unsigned long)b_age, rc); - return rc; + pcmk__err("Archived %s and %s in %s after digest mismatch", + old_cibfile_path, old_sigfile_path, new_dir); + +done: + free(new_dir); + free(new_cibfile_path); + free(new_sigfile_path); } -xmlNode * -readCibXmlFile(const char *dir, const char *file, bool discard_status) +/*! + * \internal + * \brief Read CIB XML from \c CIBFILE in the \c cib_root directory + * + * \return CIB XML parsed from \c CIBFILE in \c cib_root , or \c NULL if the + * file was not found or if parsing failed + * + * \note The caller is responsible for freeing the return value using + * \c pcmk__xml_free(). + */ +static xmlNode * +read_current_cib(void) { - struct dirent **namelist = NULL; + char *cibfile_path = pcmk__assert_asprintf("%s/%s", cib_root, CIBFILE); + char *sigfile_path = pcmk__assert_asprintf("%s.sig", cibfile_path); + const char *sigfile = strrchr(sigfile_path, '/') + 1; - int lpc = 0; - char *sigfile = NULL; - char *sigfilepath = NULL; - char *filename = NULL; - const char *name = NULL; - const char *value = NULL; + xmlNode *cib_xml = NULL; + int rc = pcmk_rc_ok; - xmlNode *root = NULL; - xmlNode *status = NULL; + if (!pcmk__daemon_can_write(cib_root, CIBFILE) + || !pcmk__daemon_can_write(cib_root, sigfile)) { - sigfile = pcmk__assert_asprintf("%s.sig", file); - if (pcmk__daemon_can_write(dir, file) == FALSE - || pcmk__daemon_can_write(dir, sigfile) == FALSE) { cib_status = EACCES; - return NULL; + goto done; } - filename = pcmk__assert_asprintf("%s/%s", dir, file); - sigfilepath = pcmk__assert_asprintf("%s/%s", dir, sigfile); - free(sigfile); - cib_status = pcmk_rc_ok; - root = retrieveCib(filename, sigfilepath); - free(filename); - free(sigfilepath); - - if (root == NULL) { - lpc = scandir(cib_root, &namelist, cib_archive_filter, cib_archive_sort); - if (lpc < 0) { - pcmk__err("Could not check for CIB backups in %s: %s", cib_root, - pcmk_rc_str(errno)); - } - } - - while (root == NULL && lpc > 1) { - int rc = pcmk_ok; - lpc--; + rc = cib_file_read_and_verify(cibfile_path, sigfile_path, &cib_xml); + rc = pcmk_legacy2rc(rc); - filename = pcmk__assert_asprintf("%s/%s", cib_root, - namelist[lpc]->d_name); - sigfile = pcmk__assert_asprintf("%s.sig", filename); + if (rc == pcmk_rc_ok) { + pcmk__info("Loaded CIB from %s (with digest %s)", cibfile_path, + sigfile_path); + goto done; + } - rc = cib_file_read_and_verify(filename, sigfile, &root); - if (rc == pcmk_ok) { - pcmk__notice("Loaded CIB from last valid backup %s (with digest " - "%s)", - filename, sigfile); - } else { - pcmk__warn("Not using next most recent CIB backup from %s (with " - "digest %s): %s", - filename, sigfile, pcmk_strerror(rc)); - } + pcmk__warn("Continuing but NOT using CIB from %s (with digest %s): %s", + cibfile_path, sigfile_path, pcmk_rc_str(rc)); - free(namelist[lpc]); - free(filename); - free(sigfile); + if (rc == pcmk_rc_cib_modified) { + // Archive the original files so the contents are not lost + archive_on_digest_mismatch(cibfile_path, sigfile_path); } - free(namelist); - if (root == NULL) { - root = createEmptyCib(0); - pcmk__warn("Continuing with an empty configuration"); - } +done: + free(cibfile_path); + free(sigfile_path); + return cib_xml; +} - if (cib_writes_enabled - && pcmk__env_option_enabled(PCMK__SERVER_BASED, - PCMK__ENV_VALGRIND_ENABLED)) { +/*! + * \internal + * \brief \c scandir() filter for backup CIB files in \c cib_root + * + * \param[in] entry Directory entry + * + * \retval 1 if the entry is a regular file whose name begins with \c "cib-" and + * does not end with ".sig" + * \retval 0 otherwise + */ +static int +backup_cib_filter(const struct dirent *entry) +{ + char *path = pcmk__assert_asprintf("%s/%s", cib_root, entry->d_name); + struct stat sb; + int rc = stat(path, &sb); - cib_writes_enabled = FALSE; - pcmk__err("*** Disabling disk writes to avoid confusing Valgrind ***"); - } + free(path); - status = pcmk__xe_first_child(root, PCMK_XE_STATUS, NULL, NULL); - if (discard_status && status != NULL) { - // Strip out the PCMK_XE_STATUS section if there is one - pcmk__xml_free(status); - status = NULL; - } - if (status == NULL) { - pcmk__xe_create(root, PCMK_XE_STATUS); + if (rc != 0) { + pcmk__warn("Filtering %s/%s during scan for backup CIB: stat() failed: " + "%s", cib_root, entry->d_name, strerror(errno)); + return 0; } - /* Do this before schema validation happens */ + return S_ISREG(sb.st_mode) + && g_str_has_prefix(entry->d_name, "cib-") + && !g_str_has_suffix(entry->d_name, ".sig"); +} - /* fill in some defaults */ - value = pcmk__xe_get(root, PCMK_XA_ADMIN_EPOCH); - if (value == NULL) { // Not possible with schema validation enabled - pcmk__warn("Defaulting missing " PCMK_XA_ADMIN_EPOCH " to 0, but " - "cluster may get confused about which node's configuration " - "is most recent"); - pcmk__xe_set_int(root, PCMK_XA_ADMIN_EPOCH, 0); - } +/*! + * \internal + * \brief Get a file's last change time (\c ctime) + * + * The file is assumed to be a backup CIB file in the \c cib_root directory. + * + * \param[in] file Base name of file + * + * \return Last change time of \p file, or 0 on \c stat() failure + */ +static time_t +get_backup_cib_ctime(const char *file) +{ + char *path = pcmk__assert_asprintf("%s/%s", cib_root, file); + struct stat sb; + int rc = stat(path, &sb); - name = PCMK_XA_EPOCH; - value = pcmk__xe_get(root, name); - if (value == NULL) { - pcmk__xe_set_int(root, name, 0); - } + free(path); - name = PCMK_XA_NUM_UPDATES; - value = pcmk__xe_get(root, name); - if (value == NULL) { - pcmk__xe_set_int(root, name, 0); + if (rc != 0) { + pcmk__warn("Failed to stat() %s/%s while sorting backup CIBs: %s", + cib_root, file, strerror(errno)); + return 0; } - // Unset (DC should set appropriate value) - pcmk__xe_remove_attr(root, PCMK_XA_DC_UUID); + return sb.st_ctime; +} - if (discard_status) { - pcmk__log_xml_trace(root, "[on-disk]"); +/*! + * \internal + * \brief Compare directory entries based on their last change times + * + * The entries are assumed to be CIB files in the \c cib_root directory. + * + * \param[in] entry1 First directory entry to compare + * \param[in] entry2 Second directory entry to compare + * + * \retval -1 if \p entry1 was changed more recently than \p entry2 + * \retval 0 if \p entry1 was last changed at the same timestamp as \p entry2 + * \retval 1 if \p entry1 was changed less recently than \p entry2 + */ +static int +compare_backup_cibs(const struct dirent **entry1, const struct dirent **entry2) +{ + time_t ctime1 = get_backup_cib_ctime((*entry1)->d_name); + time_t ctime2 = get_backup_cib_ctime((*entry2)->d_name); + + if (ctime1 > ctime2) { + pcmk__trace("%s/%s (%lld) newer than %s/%s (%lld)", + cib_root, (*entry1)->d_name, (long long) ctime1, + cib_root, (*entry2)->d_name, (long long) ctime2); + return -1; } - if (!pcmk__configured_schema_validates(root)) { - cib_status = pcmk_rc_schema_validation; + if (ctime1 < ctime2) { + pcmk__trace("%s/%s (%lld) older than %s/%s (%lld)", + cib_root, (*entry1)->d_name, (long long) ctime1, + cib_root, (*entry2)->d_name, (long long) ctime2); + return 1; } - return root; + + pcmk__trace("%s/%s (%lld) same age as %s/%s (%lld)", + cib_root, (*entry1)->d_name, (long long) ctime1, + cib_root, (*entry2)->d_name, (long long) ctime2); + return 0; } -void -uninitializeCib(void) +/*! + * \internal + * \brief Read CIB XML from the last valid backup file in \c cib_root + * + * \return CIB XML parsed from the last valid backup file, or \c NULL if none + * was found + */ +static xmlNode * +read_backup_cib(void) { - xmlNode *tmp_cib = the_cib; + xmlNode *cib_xml = NULL; + struct dirent **namelist = NULL; + int num_files = scandir(cib_root, &namelist, backup_cib_filter, + compare_backup_cibs); - if (tmp_cib == NULL) { - return; + if (num_files < 0) { + pcmk__err("Could not check for CIB backups in %s: %s", cib_root, + pcmk_rc_str(errno)); + goto done; } - the_cib = NULL; - pcmk__xml_free(tmp_cib); -} + for (int i = 0; i < num_files; i++) { + const char *cibfile = namelist[i]->d_name; + char *cibfile_path = pcmk__assert_asprintf("%s/%s", cib_root, cibfile); + char *sigfile_path = pcmk__assert_asprintf("%s.sig", cibfile_path); -/* - * This method will free the old CIB pointer on success and the new one - * on failure. - */ -int -activateCibXml(xmlNode *new_cib, bool to_disk, const char *op) -{ - if (new_cib) { - xmlNode *saved_cib = the_cib; - - pcmk__assert(new_cib != saved_cib); - the_cib = new_cib; - pcmk__xml_free(saved_cib); - if (cib_writes_enabled && cib_status == pcmk_rc_ok && to_disk) { - pcmk__debug("Triggering CIB write for %s op", op); - mainloop_set_trigger(cib_writer); + int rc = cib_file_read_and_verify(cibfile_path, sigfile_path, &cib_xml); + + rc = pcmk_legacy2rc(rc); + + if (rc == pcmk_rc_ok) { + pcmk__notice("Loaded CIB from last valid backup %s (with digest " + "%s)", cibfile_path, sigfile_path); + } else { + pcmk__warn("Not using next most recent CIB backup from %s (with " + "digest %s): %s", cibfile_path, sigfile_path, + pcmk_rc_str(rc)); + } + + free(cibfile_path); + free(sigfile_path); + + if (rc == pcmk_rc_ok) { + break; } - return pcmk_ok; } - pcmk__err("Ignoring invalid CIB"); - if (the_cib) { - pcmk__warn("Reverting to last known CIB"); - } else { - pcmk__crit("Could not write out new CIB and no saved version to revert " - "to"); +done: + for (int i = 0; i < num_files; i++) { + free(namelist[i]); } - return -ENODATA; + free(namelist); + + return cib_xml; } +/*! + * \internal + * \brief Set the CIB XML's \c PCMK_XE_STATUS element to empty if appropriate + * + * Delete the current \c PCMK_XE_STATUS element if not running in stand-alone + * mode. Then create an empty \c PCMK_XE_STATUS child if either of the following + * is true: + * * not running in stand-alone mode + * * running in stand-alone mode with no \c PCMK_XE_STATUS element + * + * \param[in,out] cib_xml CIB XML + */ static void -cib_diskwrite_complete(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) +set_empty_status(xmlNode *cib_xml) { - const char *errmsg = "Could not write CIB to disk"; + xmlNode *status = pcmk__xe_first_child(cib_xml, PCMK_XE_STATUS, NULL, NULL); - if ((exitcode != 0) && cib_writes_enabled) { - cib_writes_enabled = FALSE; - errmsg = "Disabling CIB disk writes after failure"; + if (!stand_alone) { + g_clear_pointer(&status, pcmk__xml_free); } - if ((signo == 0) && (exitcode == 0)) { - pcmk__trace("Disk write [%d] succeeded", (int) pid); - - } else if (signo == 0) { - pcmk__err("%s: process %d exited %d", errmsg, (int) pid, exitcode); + if (status == NULL) { + pcmk__xe_create(cib_xml, PCMK_XE_STATUS); + } +} - } else { - pcmk__err("%s: process %d terminated with signal %d (%s)%s", - errmsg, (int) pid, signo, strsignal(signo), - ((core != 0)? " and dumped core" : "")); +/*! + * \internal + * \brief Set the given CIB version attribute to 0 if it's not already set + * + * \param[in,out] cib_xml CIB XML + * \param[in] version_attr Version attribute + */ +static void +set_default_if_unset(xmlNode *cib_xml, const char *version_attr) +{ + if (pcmk__xe_get(cib_xml, version_attr) != NULL) { + return; } - mainloop_trigger_complete(cib_writer); + pcmk__warn("Defaulting missing %s to 0, but cluster may get confused about " + "which node's configuration is most recent", version_attr); + pcmk__xe_set_int(cib_xml, version_attr, 0); } -int -write_cib_contents(gpointer p) +/*! + * \internal + * \brief Read the most recent CIB from a file in \c cib_root + * + * This function first tries to read the CIB from a file called \c "cib.xml" in + * the \c cib_root directory. + * + * If that fails or there is a digest mismatch, it tries all the backup CIB + * files in \c cib_root, in order from most recently changed to least, moving to + * the next backup file on failure or digest mismatch. + * + * If no valid CIB file is found, this function generates an empty CIB. + * + * \return The most current CIB XML available, or an empty CIB if none is + * available (guaranteed not to be \c NULL) + * + * \note The caller is responsible for freeing the return value using + * \c pcmk__xml_free(). + */ +xmlNode * +based_read_cib(void) { - int exit_rc = pcmk_ok; - xmlNode *cib_local = NULL; + static const char *version_attrs[] = { + PCMK_XA_ADMIN_EPOCH, + PCMK_XA_EPOCH, + PCMK_XA_NUM_UPDATES, + }; - /* Make a copy of the CIB to write (possibly in a forked child) */ - if (p) { - /* Synchronous write out */ - cib_local = pcmk__xml_copy(NULL, p); + xmlNode *cib_xml = read_current_cib(); - } else { - int pid = 0; - int bb_state = qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0); - - /* Turn it off before the fork() to avoid: - * - 2 processes writing to the same shared mem - * - the child needing to disable it - * (which would close it from underneath the parent) - * This way, the shared mem files are already closed - */ - qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); - - pid = fork(); - if (pid < 0) { - pcmk__err("Disabling disk writes after fork failure: %s", - pcmk_rc_str(errno)); - cib_writes_enabled = FALSE; - return FALSE; - } + if (cib_xml == NULL) { + cib_xml = read_backup_cib(); + } - if (pid) { - /* Parent */ - mainloop_child_add(pid, 0, "disk-writer", NULL, cib_diskwrite_complete); - if (bb_state == QB_LOG_STATE_ENABLED) { - /* Re-enable now that it it safe */ - qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); - } + if (cib_xml == NULL) { + cib_xml = createEmptyCib(0); + pcmk__warn("Continuing with an empty configuration"); + } - return -1; /* -1 means 'still work to do' */ - } + set_empty_status(cib_xml); + + /* Default the three version attributes to 0 if unset. The schema requires + * them to be set, so: + * * It's not possible for them to be unset if schema validation was enabled + * when the CIB file was generated, or if it was generated by Pacemaker + * and then unmodified. + * * We need to set these defaults before schema validation happens. + */ + for (int i = 0; i < PCMK__NELEM(version_attrs); i++) { + set_default_if_unset(cib_xml, version_attrs[i]); + } - /* Asynchronous write-out after a fork() */ + // The DC should set appropriate value for PCMK_XA_DC_UUID + pcmk__xe_remove_attr(cib_xml, PCMK_XA_DC_UUID); - /* In theory, we can scribble on the_cib here and not affect the parent, - * but let's be safe anyway. - */ - cib_local = pcmk__xml_copy(NULL, the_cib); + if (!stand_alone) { + pcmk__log_xml_trace(cib_xml, "on-disk"); } - /* Write the CIB */ - exit_rc = cib_file_write_with_digest(cib_local, cib_root, "cib.xml"); - - /* A nonzero exit code will cause further writes to be disabled */ - pcmk__xml_free(cib_local); - if (p == NULL) { - crm_exit_t exit_code = CRM_EX_OK; - - switch (exit_rc) { - case pcmk_ok: - exit_code = CRM_EX_OK; - break; - case pcmk_err_cib_modified: - exit_code = CRM_EX_DIGEST; // Existing CIB doesn't match digest - break; - case pcmk_err_cib_backup: // Existing CIB couldn't be backed up - case pcmk_err_cib_save: // New CIB couldn't be saved - exit_code = CRM_EX_CANTCREAT; - break; - default: - exit_code = CRM_EX_ERROR; - break; - } + if (!pcmk__configured_schema_validates(cib_xml)) { + cib_status = pcmk_rc_schema_validation; + } + + return cib_xml; +} - /* Use _exit() because exit() could affect the parent adversely */ - pcmk_common_cleanup(); - _exit(exit_code); +/*! + * \internal + * \brief Activate new CIB XML + * + * This function frees the existing \c the_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) + * \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 + * only) + * + * \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. + */ +int +based_activate_cib(xmlNode *new_cib, bool to_disk, const char *op) +{ + CRM_CHECK((new_cib != NULL) && (new_cib != the_cib), return ENODATA); + + pcmk__xml_free(the_cib); + the_cib = new_cib; + + if (to_disk && writes_enabled && (cib_status == pcmk_rc_ok)) { + pcmk__debug("Triggering CIB write for %s op", op); + mainloop_set_trigger(write_trigger); } - return exit_rc; + + return pcmk_rc_ok; } diff --git a/daemons/based/based_io.h b/daemons/based/based_io.h new file mode 100644 index 00000000000..f9bb5011a1a --- /dev/null +++ b/daemons/based/based_io.h @@ -0,0 +1,22 @@ +/* + * 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_IO__H +#define BASED_IO__H + +#include + +#include // xmlNode + +void based_io_init(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); + +#endif // BASED_IO__H diff --git a/daemons/based/based_ipc.c b/daemons/based/based_ipc.c new file mode 100644 index 00000000000..ea76c42ea99 --- /dev/null +++ b/daemons/based/based_ipc.c @@ -0,0 +1,344 @@ +/* + * 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 // ECONNREFUSED, ENOMEM +#include +#include // size_t +#include // int32_t, uint32_t +#include // uid_t, gid_t + +#include // g_byte_array_free(), TRUE +#include // xmlNode +#include // qb_ipcs_* + +#include // cib_none, cib_sync_call +#include // pcmk__client_*, pcmk__trace, etc. +#include // crm_ipc_client_response +#include // CRM_CHECK(), CRM_LOG_ASSERT() +#include // CRM_EX_PROTOCOL, pcmk_rc_* +#include // CRM_OP_REGISTER + +#include "pacemaker-based.h" + +static qb_ipcs_service_t *ipcs_ro = NULL; +static qb_ipcs_service_t *ipcs_rw = NULL; +static qb_ipcs_service_t *ipcs_shm = NULL; + +/*! + * \internal + * \brief Accept a new client IPC connection + * + * \param[in,out] c New connection + * \param[in] uid Client user id + * \param[in] gid Client group id + * + * \return 0 on success, \c -errno otherwise + */ +static int32_t +based_ipc_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) +{ + if (cib_shutdown_flag) { + pcmk__info("Ignoring new IPC client [%d] during shutdown", + pcmk__client_pid(c)); + return -ECONNREFUSED; + } + + pcmk__trace("New client connection %p", c); + if (pcmk__new_client(c, uid, gid) == NULL) { + return -ENOMEM; + } + return 0; +} + +/*! + * \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 + * + * \return 0 in all cases + */ +static int32_t +dispatch_common(qb_ipcs_connection_t *c, void *data, bool privileged) +{ + 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; + + // Sanity-check, and parse XML from IPC data + CRM_CHECK(client != NULL, return 0); + if (data == NULL) { + pcmk__debug("No IPC data from PID %d", pcmk__client_pid(c)); + 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) { + /* We haven't read the complete message yet, so just return. */ + return 0; + + } else if (rc == pcmk_rc_ok) { + /* We've read the complete message and there's already a header on + * the front. Pass it off for processing. + */ + msg = pcmk__client_data2xml(client, &id, &flags); + g_byte_array_free(client->buffer, TRUE); + client->buffer = NULL; + + } else { + /* Some sort of error occurred reassembling the message. All we can + * do is clean up, log an error and return. + */ + pcmk__err("Error when reading IPC message: %s", pcmk_rc_str(rc)); + + if (client->buffer != NULL) { + g_byte_array_free(client->buffer, TRUE); + client->buffer = NULL; + } + + return 0; + } + + if (msg == NULL) { + pcmk__debug("Unrecognizable IPC data from PID %d", pcmk__client_pid(c)); + pcmk__ipc_send_ack(client, id, flags, PCMK__XE_NACK, NULL, + CRM_EX_PROTOCOL); + return 0; + } + + 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); + } + } + + 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"); + } + + /* Requests with cib_transaction set should not be sent to based directly + * (that is, outside of a commit-transaction request) + */ + if (pcmk__is_set(call_options, cib_transaction)) { + 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); + + // If false, the client has two synchronous events in flight + CRM_LOG_ASSERT(client->request_id == 0); + + // Reply only to the last one + client->request_id = id; + } + + pcmk__xe_set(msg, PCMK__XA_CIB_CLIENTID, client->id); + pcmk__xe_set(msg, PCMK__XA_CIB_CLIENTNAME, client->name); + + 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)) { + xmlNode *ack = NULL; + + if (!pcmk__is_set(flags, crm_ipc_client_response)) { + return 0; + } + + ack = pcmk__xe_create(NULL, __func__); + pcmk__xe_set(ack, PCMK__XA_CIB_OP, CRM_OP_REGISTER); + pcmk__xe_set(ack, PCMK__XA_CIB_CLIENTID, client->id); + pcmk__ipc_send_xml(client, id, ack, flags); + + client->request_id = 0; + pcmk__xml_free(ack); + return 0; + } + + if (pcmk__str_eq(op, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) { + crm_exit_t status = CRM_EX_OK; + int rc = based_update_notify_flags(msg, client); + + if (rc != pcmk_rc_ok) { + status = CRM_EX_INVALID_PARAM; + } + + pcmk__ipc_send_ack(client, id, flags, PCMK__XE_ACK, NULL, status); + return 0; + } + + based_process_request(msg, privileged, client); + pcmk__xml_free(msg); + return 0; +} + +/*! + * \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 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 Destroy a client IPC connection + * + * \param[in] c Connection to destroy + * + * \return 0 (do not re-run this callback) + */ +static int32_t +based_ipc_closed(qb_ipcs_connection_t *c) +{ + pcmk__client_t *client = pcmk__find_client(c); + + if (client == NULL) { + pcmk__trace("Ignoring request to clean up unknown connection %p", c); + } else { + pcmk__trace("Cleaning up closed client connection %p", c); + pcmk__free_client(client); + } + + return 0; +} + +/*! + * \internal + * \brief Destroy a client IPC connection + * + * \param[in] c Connection to destroy + */ +static void +based_ipc_destroy(qb_ipcs_connection_t *c) +{ + pcmk__trace("Destroying client connection %p", c); + based_ipc_closed(c); +} + +static struct qb_ipcs_service_handlers ipc_ro_callbacks = { + .connection_accept = based_ipc_accept, + .connection_created = NULL, + .msg_process = based_ipc_dispatch_ro, + .connection_closed = based_ipc_closed, + .connection_destroyed = based_ipc_destroy, +}; + +static 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_ro, &ipcs_rw, &ipcs_shm, &ipc_ro_callbacks, + &ipc_rw_callbacks); +} + +/*! + * \internal + * \brief Clean up \c based IPC communication + */ +void +based_ipc_cleanup(void) +{ + if (ipcs_ro != NULL) { + pcmk__drop_all_clients(ipcs_ro); + g_clear_pointer(&ipcs_ro, qb_ipcs_destroy); + } + + if (ipcs_rw != NULL) { + pcmk__drop_all_clients(ipcs_rw); + g_clear_pointer(&ipcs_rw, qb_ipcs_destroy); + } + + if (ipcs_shm != NULL) { + pcmk__drop_all_clients(ipcs_shm); + g_clear_pointer(&ipcs_shm, 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 0aaedec5001..246a08af7b8 100644 --- a/daemons/based/based_messages.c +++ b/daemons/based/based_messages.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,370 +9,306 @@ #include +#include // EINVAL, ENOTCONN, EPROTO #include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include - -#include -#include +#include // NULL +#include // free + +#include // g_list_free_full, GList +#include // xmlNode +#include // QB_XS + +#include // PCMK__CIB_REQUEST_UPGRADE +#include // pcmk__cluster_send_message +#include // pcmk__info, pcmk__xml_free, etc. +#include // pcmk_ipc_server +#include // CRM_CHECK +#include // pcmk_err, pcmk_ok, pcmk_rc* +#include // PCMK_XA_*, PCMK_XE_* +#include // CRM_FEATURE_SET #include -/* 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; +/*! + * \internal + * \brief Process a \c PCMK__CIB_REQUEST_ABS_DELETE + * + * \param[in] req Ignored + * \param[in] cib Ignored + * \param[in] answer Ignored + * + * \return \c EINVAL + * + * \note This is unimplemented and simply returns an error. + */ int -cib_process_shutdown_req(const char *op, int options, const char *section, xmlNode * req, - xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, - xmlNode ** answer) +based_process_abs_delete(xmlNode *req, xmlNode **cib, xmlNode **answer) { - const char *host = pcmk__xe_get(req, PCMK__XA_SRC); + /* @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; +} - *answer = NULL; +int +based_process_commit_transact(xmlNode *req, xmlNode **cib, xmlNode **answer) +{ + /* 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); - if (pcmk__xe_get(req, PCMK__XA_CIB_ISREPLYTO) == NULL) { - pcmk__info("Peer %s is requesting to shut down", host); - return pcmk_ok; - } + rc = based_commit_transaction(input, client, origin, cib); + if (rc != pcmk_rc_ok) { + char *source = based_transaction_source_str(client, origin); - if (!cib_shutdown_flag) { - pcmk__err("Peer %s mistakenly thinks we wanted to shut down", host); - return -EINVAL; + pcmk__err("Could not commit transaction for %s: %s", source, + pcmk_rc_str(rc)); + free(source); } - pcmk__info("Exiting after %s acknowledged our shutdown request", host); - terminate_cib(CRM_EX_OK); - return pcmk_ok; + return rc; } -// @COMPAT: Remove when PCMK__CIB_REQUEST_NOOP is removed int -cib_process_noop(const char *op, int options, const char *section, xmlNode *req, - xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, - xmlNode **answer) +based_process_is_primary(xmlNode *req, xmlNode **cib, xmlNode **answer) { - pcmk__trace("Processing \"%s\" event", op); - *answer = NULL; - return pcmk_ok; + // @COMPAT Pacemaker Remote clients <3.0.0 may send this + return (based_is_primary? pcmk_rc_ok : EPERM); } +// @COMPAT: Remove when PCMK__CIB_REQUEST_NOOP is removed int -cib_process_readwrite(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) { - int result = pcmk_ok; - - pcmk__trace("Processing \"%s\" event", op); - - // @COMPAT Pacemaker Remote clients <3.0.0 may send this - if (pcmk__str_eq(op, PCMK__CIB_REQUEST_IS_PRIMARY, pcmk__str_none)) { - if (based_is_primary) { - result = pcmk_ok; - } else { - result = -EPERM; - } - return result; - } - - if (pcmk__str_eq(op, PCMK__CIB_REQUEST_PRIMARY, pcmk__str_none)) { - if (!based_is_primary) { - pcmk__info("We are now in R/W mode"); - based_is_primary = true; - } else { - pcmk__debug("We are still in R/W mode"); - } - - } else if (based_is_primary) { - pcmk__info("We are now in R/O mode"); - based_is_primary = false; - } - - return result; -} - -/* 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; - -void -send_sync_request(const char *host) -{ - xmlNode *sync_me = pcmk__xe_create(NULL, "sync-me"); - pcmk__node_status_t *peer = NULL; - - pcmk__info("Requesting re-sync from %s", (host? host : "all peers")); - sync_in_progress = 1; - - 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); - - if (host != NULL) { - peer = pcmk__get_node(0, host, NULL, pcmk__node_search_cluster_member); - } - pcmk__cluster_send_message(peer, pcmk_ipc_based, sync_me); - pcmk__xml_free(sync_me); + return pcmk_rc_ok; } int -cib_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) { 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); + 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); - } - ); - } + shallow = pcmk__xe_create(NULL, (const char *) the_cib->name); + pcmk__xe_copy_attrs(shallow, the_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)); + host, digest, pcmk__xe_get(*cib, PCMK_XA_ADMIN_EPOCH), + pcmk__xe_get(*cib, PCMK_XA_EPOCH), + pcmk__xe_get(*cib, PCMK_XA_NUM_UPDATES)); free(digest); - return pcmk_ok; + return pcmk_rc_ok; } int -cib_process_sync(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) { - return sync_our_cib(req, true); + if (!based_is_primary) { + pcmk__info("We are now in R/W mode"); + based_is_primary = true; + + } else { + pcmk__debug("We are still in R/W mode"); + } + + return pcmk_rc_ok; } int -cib_process_upgrade_server(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 = pcmk_ok; + xmlNode *data = NULL; - *answer = NULL; + const char *after_ver = NULL; + GList *schemas = NULL; + GList *already_included = NULL; - if (pcmk__xe_get(req, PCMK__XA_CIB_SCHEMA_MAX) != NULL) { - /* The originator of an upgrade request sends it to the DC, without - * PCMK__XA_CIB_SCHEMA_MAX. If an upgrade is needed, the DC - * 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); + *answer = pcmk__xe_create(NULL, PCMK__XA_SCHEMAS); - } 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; - } + data = cib__get_calldata(req); + if (data == NULL) { + pcmk__warn("No data specified in request"); + return EPROTO; + } - rc = pcmk__update_schema(&scratch, NULL, true, true); - rc = pcmk_rc2legacy(rc); - new_schema = pcmk__xe_get(scratch, PCMK_XA_VALIDATE_WITH); + after_ver = pcmk__xe_get(data, PCMK_XA_VERSION); + if (after_ver == NULL) { + pcmk__warn("No version specified in request"); + return EPROTO; + } - if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) { - xmlNode *up = pcmk__xe_create(NULL, __func__); + /* The client requested all schemas after the latest one we know about, which + * means the client is fully up-to-date. Return a properly formatted reply + * with no schemas. + */ + if (pcmk__str_eq(after_ver, pcmk__highest_schema_name(), pcmk__str_none)) { + return pcmk_rc_ok; + } - rc = pcmk_ok; - pcmk__notice("Upgrade request from %s verified", host); + schemas = pcmk__schema_files_later_than(after_ver); - 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); + for (GList *iter = schemas; iter != NULL; iter = iter->next) { + pcmk__build_schema_xml_node(*answer, iter->data, &already_included); + } - pcmk__cluster_send_message(NULL, pcmk_ipc_based, up); + g_list_free_full(schemas, free); + g_list_free_full(already_included, free); + return pcmk_rc_ok; +} - pcmk__xml_free(up); +int +based_process_secondary(xmlNode *req, xmlNode **cib, xmlNode **answer) +{ + if (based_is_primary) { + pcmk__info("We are now in R/O mode"); + based_is_primary = false; - } else if(rc == pcmk_ok) { - rc = -pcmk_err_schema_unchanged; - } + } else { + pcmk__debug("We are still in R/O mode"); + } - 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); + return pcmk_rc_ok; +} + +int +based_process_shutdown(xmlNode *req, xmlNode **cib, xmlNode **answer) +{ + const char *host = pcmk__xe_get(req, PCMK__XA_SRC); + + if (pcmk__xe_get(req, PCMK__XA_CIB_ISREPLYTO) == NULL) { + pcmk__info("Peer %s is requesting to shut down", host); + return pcmk_rc_ok; } - return rc; + + 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_rc_ok; } int -cib_process_sync_one(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, false); + return sync_our_cib(req, true); } int -cib_server_process_diff(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) { - int rc = pcmk_ok; + int rc = pcmk_rc_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. + 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 + * PCMK__XA_CIB_SCHEMA_MAX. If an upgrade is needed, the DC + * re-broadcasts the request with PCMK__XA_CIB_SCHEMA_MAX, and each node + * performs the upgrade (and notifies its local clients) here. */ - sync_in_progress = 0; + return cib__process_upgrade(req, cib, answer); } - // The primary instance should never ignore a diff - if (sync_in_progress && !based_is_primary) { - int diff_add_updates = 0; - int diff_add_epoch = 0; - int diff_add_admin_epoch = 0; - - int diff_del_updates = 0; - int diff_del_epoch = 0; - int diff_del_admin_epoch = 0; - - cib_diff_version_details(input, - &diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates, - &diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates); - - sync_in_progress++; - pcmk__notice("Not applying diff %d.%d.%d -> %d.%d.%d (sync in " - "progress)", - diff_del_admin_epoch, diff_del_epoch, diff_del_updates, - diff_add_admin_epoch, diff_add_epoch, diff_add_updates); - return -pcmk_err_diff_resync; + scratch = pcmk__xml_copy(NULL, *cib); + + 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 = cib_process_diff(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")); + rc = pcmk__update_schema(&scratch, NULL, true, true); + new_schema = pcmk__xe_get(scratch, PCMK_XA_VALIDATE_WITH); - if ((rc == -pcmk_err_diff_resync) && !based_is_primary) { - pcmk__xml_free(*result_cib); - *result_cib = NULL; - send_sync_request(NULL); + if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) { + xmlNode *up = pcmk__xe_create(NULL, __func__); - } 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"); - } - } + rc = pcmk_rc_ok; + pcmk__notice("Upgrade request from %s verified", host); - return rc; -} + 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); -int -cib_process_replace_svr(const char *op, int options, const char *section, xmlNode * req, - xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, - xmlNode ** answer) -{ - int rc = - cib_process_replace(op, options, section, req, input, existing_cib, result_cib, answer); + pcmk__cluster_send_message(NULL, pcmk_ipc_based, up); - if ((rc == pcmk_ok) && pcmk__xe_is(input, PCMK_XE_CIB)) { - sync_in_progress = 0; + pcmk__xml_free(up); + goto done; } - return rc; -} -/* @COMPAT: Remove when PCMK__CIB_REQUEST_ABS_DELETE is removed - * (At least external client code <3.0.0 can send it) - */ -int -cib_process_delete_absolute(const char *op, int options, const char *section, xmlNode * req, - xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, - xmlNode ** answer) -{ - return -EINVAL; + if (rc == pcmk_rc_ok) { + rc = pcmk_rc_schema_unchanged; + } + + // 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); + } + +done: + pcmk__xml_free(scratch); + return rc; } static xmlNode * -cib_msg_copy(xmlNode *msg) +cib_msg_copy(const xmlNode *msg) { static const char *field_list[] = { PCMK__XA_T, @@ -405,18 +341,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)); @@ -440,89 +374,15 @@ sync_our_cib(xmlNode *request, bool all) digest = pcmk__digest_xml(the_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, the_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; -} - -int -cib_process_commit_transaction(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. - */ - int rc = pcmk_rc_ok; - 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); - - if (rc != pcmk_rc_ok) { - char *source = based_transaction_source_str(client, origin); - - pcmk__err("Could not commit transaction for %s: %s", source, - pcmk_rc_str(rc)); - free(source); - } - return pcmk_rc2legacy(rc); -} - -int -cib_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; - - *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); - if (data == NULL) { - pcmk__warn("No data specified in request"); - return -EPROTO; - } - - after_ver = pcmk__xe_get(data, PCMK_XA_VERSION); - if (after_ver == NULL) { - pcmk__warn("No version specified in request"); - return -EPROTO; - } - - /* The client requested all schemas after the latest one we know about, which - * means the client is fully up-to-date. Return a properly formatted reply - * with no schemas. - */ - if (pcmk__str_eq(after_ver, pcmk__highest_schema_name(), pcmk__str_none)) { - return pcmk_ok; - } - - schemas = pcmk__schema_files_later_than(after_ver); - - for (GList *iter = schemas; iter != NULL; iter = iter->next) { - pcmk__build_schema_xml_node(*answer, iter->data, &already_included); - } - - g_list_free_full(schemas, free); - g_list_free_full(already_included, free); - return pcmk_ok; + return rc; } diff --git a/daemons/based/based_messages.h b/daemons/based/based_messages.h new file mode 100644 index 00000000000..30b934a8600 --- /dev/null +++ b/daemons/based/based_messages.h @@ -0,0 +1,36 @@ +/* + * 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 Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef BASED_MESSAGES__H +#define BASED_MESSAGES__H + +#include + +#include // xmlNode * + +extern bool based_is_primary; +extern xmlNode *the_cib; + +int based_process_abs_delete(xmlNode *req, xmlNode **cib, xmlNode **answer); +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_is_primary(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 dba81da44c3..22686d1c8c5 100644 --- a/daemons/based/based_notify.c +++ b/daemons/based/based_notify.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,26 +9,22 @@ #include -#include +#include // EAGAIN +#include // PRIx64 #include -#include -#include -#include -#include // PRIx64 +#include // NULL +#include // int32_t, uint16_t +#include // ssize_t -#include -#include -#include +#include // gpointer, g_string_free +#include // xmlNode +#include // QB_XS -#include +#include // pcmk__client_t, etc. +#include // pcmk_free_ipc_event +#include // CRM_LOG_ASSERT +#include // pcmk_rc_* -#include -#include - -#include -#include - -#include #include struct cib_notification_s { @@ -37,11 +33,83 @@ struct cib_notification_s { int32_t iov_size; }; +/*! + * \internal + * \brief Flags for CIB manager client notification types + * + * These are used for setting the \c flags field of a \c pcmk__client_t. + */ +enum based_notify_flags { + //! This flag has no effect + based_nf_none = UINT64_C(0), + + //! Notify when the CIB changes + based_nf_diff = (UINT64_C(1) << 0), +}; + +/*! + * \internal + * \brief Parse a CIB manager client notification type string to a flag + * + * \param[in] text Notification type string + * + * \return Flag corresponding to \p text, or \c based_nf_none if none exists + */ +static enum based_notify_flags +based_parse_notify_flag(const char *text) +{ + if (pcmk__str_eq(text, PCMK__VALUE_CIB_DIFF_NOTIFY, pcmk__str_none)) { + return based_nf_diff; + } + + return based_nf_none; +} + +/*! + * \internal + * \brief Set or clear a notify flag in a client based on request XML + * + * \param[in] xml Request XML + * \param[in,out] client Client + * + * \return Standard Pacemaker return code + */ +int +based_update_notify_flags(const xmlNode *xml, pcmk__client_t *client) +{ + int rc = pcmk_rc_ok; + bool enable = false; + enum based_notify_flags notify_flag = based_nf_none; + const char *type = pcmk__xe_get(xml, PCMK__XA_CIB_NOTIFY_TYPE); + + rc = pcmk__xe_get_bool(xml, PCMK__XA_CIB_NOTIFY_ACTIVATE, &enable); + if (rc != pcmk_rc_ok) { + return pcmk_rc_bad_input; + } + + notify_flag = based_parse_notify_flag(type); + if (notify_flag == based_nf_none) { + return pcmk_rc_bad_input; + } + + if (enable) { + pcmk__debug("Enabling %s callbacks for client %s", type, + pcmk__client_name(client)); + pcmk__set_client_flags(client, notify_flag); + + } else { + pcmk__debug("Disabling %s callbacks for client %s", type, + pcmk__client_name(client)); + pcmk__clear_client_flags(client, notify_flag); + } + + return pcmk_rc_ok; +} + static void cib_notify_send_one(gpointer key, gpointer value, gpointer user_data) { const char *type = NULL; - bool do_send = false; int rc = pcmk_rc_ok; pcmk__client_t *client = value; @@ -55,28 +123,9 @@ cib_notify_send_one(gpointer key, gpointer value, gpointer user_data) type = pcmk__xe_get(update->msg, PCMK__XA_SUBT); CRM_LOG_ASSERT(type != NULL); - if (pcmk__is_set(client->flags, cib_notify_diff) - && pcmk__str_eq(type, PCMK__VALUE_CIB_DIFF_NOTIFY, pcmk__str_none)) { + if (!pcmk__is_set(client->flags, based_nf_diff) + || !pcmk__str_eq(type, PCMK__VALUE_CIB_DIFF_NOTIFY, pcmk__str_none)) { - do_send = true; - - } else if (pcmk__is_set(client->flags, cib_notify_confirm) - && pcmk__str_eq(type, PCMK__VALUE_CIB_UPDATE_CONFIRMATION, - pcmk__str_none)) { - do_send = true; - - } else if (pcmk__is_set(client->flags, cib_notify_pre) - && pcmk__str_eq(type, PCMK__VALUE_CIB_PRE_NOTIFY, - pcmk__str_none)) { - do_send = true; - - } else if (pcmk__is_set(client->flags, cib_notify_post) - && pcmk__str_eq(type, PCMK__VALUE_CIB_POST_NOTIFY, - pcmk__str_none)) { - do_send = true; - } - - if (!do_send) { return; } @@ -150,20 +199,8 @@ cib_notify_send(const xmlNode *xml) } void -cib_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) { - int add_updates = 0; - int add_epoch = 0; - int add_admin_epoch = 0; - - int del_updates = 0; - int del_epoch = 0; - int del_admin_epoch = 0; - - uint8_t log_level = LOG_TRACE; - xmlNode *update_msg = NULL; xmlNode *wrapper = NULL; @@ -171,49 +208,29 @@ cib_diff_notify(const char *op, int result, const char *call_id, return; } - if (result != pcmk_ok) { - log_level = LOG_WARNING; - } - - cib_diff_version_details(diff, &add_admin_epoch, &add_epoch, &add_updates, - &del_admin_epoch, &del_epoch, &del_updates); - - if ((add_admin_epoch != del_admin_epoch) - || (add_epoch != del_epoch) - || (add_updates != del_updates)) { - - do_crm_log(log_level, - "Updated CIB generation %d.%d.%d to %d.%d.%d from client " - "%s%s%s (%s) (%s)", - del_admin_epoch, del_epoch, del_updates, - add_admin_epoch, add_epoch, add_updates, - client_name, - ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""), - pcmk__s(origin, "unspecified peer"), pcmk_strerror(result)); - - } else if ((add_admin_epoch != 0) - || (add_epoch != 0) - || (add_updates != 0)) { - - do_crm_log(log_level, - "Local-only change to CIB generation %d.%d.%d from client " - "%s%s%s (%s) (%s)", - add_admin_epoch, add_epoch, add_updates, - client_name, - ((call_id != NULL)? " call " : ""), pcmk__s(call_id, ""), - pcmk__s(origin, "unspecified peer"), pcmk_strerror(result)); - } - 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 new file mode 100644 index 00000000000..aec79e7b0b7 --- /dev/null +++ b/daemons/based/based_notify.h @@ -0,0 +1,21 @@ +/* + * 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_NOTIFY__H +#define BASED_NOTIFY__H + +#include // xmlNode + +#include // pcmk__client_t + +int based_update_notify_flags(const xmlNode *xml, pcmk__client_t *client); + +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 6c8e93f4b83..97d1679e3b1 100644 --- a/daemons/based/based_operation.c +++ b/daemons/based/based_operation.c @@ -1,5 +1,5 @@ /* - * Copyright 2008-2024 the Pacemaker project contributors + * Copyright 2008-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -9,33 +9,33 @@ #include -#include +#include // NULL + +#include // cib__* +#include // pcmk__assert, PCMK__NELEM -#include -#include #include -static const cib__op_fn_t cib_op_functions[] = { - [cib__op_abs_delete] = cib_process_delete_absolute, - [cib__op_apply_patch] = cib_server_process_diff, - [cib__op_bump] = cib_process_bump, - [cib__op_commit_transact] = cib_process_commit_transaction, - [cib__op_create] = cib_process_create, - [cib__op_delete] = cib_process_delete, - [cib__op_erase] = cib_process_erase, - [cib__op_is_primary] = cib_process_readwrite, - [cib__op_modify] = cib_process_modify, - [cib__op_noop] = cib_process_noop, - [cib__op_ping] = cib_process_ping, - [cib__op_primary] = cib_process_readwrite, - [cib__op_query] = cib_process_query, - [cib__op_replace] = cib_process_replace_svr, - [cib__op_secondary] = cib_process_readwrite, - [cib__op_shutdown] = cib_process_shutdown_req, - [cib__op_sync_all] = cib_process_sync, - [cib__op_sync_one] = cib_process_sync_one, - [cib__op_upgrade] = cib_process_upgrade_server, - [cib__op_schemas] = cib_process_schemas, +static const cib__op_fn_t op_functions[] = { + [cib__op_abs_delete] = based_process_abs_delete, + [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] = cib__process_replace, + [cib__op_schemas] = based_process_schemas, + [cib__op_secondary] = based_process_secondary, + [cib__op_shutdown] = based_process_shutdown, + [cib__op_sync] = based_process_sync, + [cib__op_upgrade] = based_process_upgrade, }; /*! @@ -53,8 +53,8 @@ based_get_op_function(const cib__operation_t *operation) pcmk__assert(type >= 0); - if (type >= PCMK__NELEM(cib_op_functions)) { + if (type >= PCMK__NELEM(op_functions)) { return NULL; } - return cib_op_functions[type]; + return op_functions[type]; } diff --git a/daemons/based/based_operation.h b/daemons/based/based_operation.h new file mode 100644 index 00000000000..ee016466531 --- /dev/null +++ b/daemons/based/based_operation.h @@ -0,0 +1,17 @@ +/* + * Copyright 2023-2025 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_OPERATION__H +#define BASED_OPERATION__H + +#include // cib__* + +cib__op_fn_t based_get_op_function(const cib__operation_t *operation); + +#endif // BASED_OPERATION__H diff --git a/daemons/based/based_remote.c b/daemons/based/based_remote.c index c51d788d7f7..0d69a788bb4 100644 --- a/daemons/based/based_remote.c +++ b/daemons/based/based_remote.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,183 +8,264 @@ */ #include -#include -#include +#include // htons +#include // errno, EAGAIN +#include // getgrgid, getgrnam, group +#include // PRIx64 +#include // sockaddr_in, INADDR_ANY #include -#include -#include -#include -#include -#include // PRIx64 -#include -#include - -#include - -#include -#include - -#include -#include - -#include -#include -#include +#include // NULL +#include // calloc, free, getenv +#include // memset +#include // sockaddr{,_storage}, AF_INET, etc. +#include // close + +#include // gboolean, gpointer, g_source_remove, etc. +#include // gnutls_bye, gnutls_deinit +#include // xmlNode +#include // QB_XS + +#include // CRM_DAEMON_GROUP +#include // pcmk__client_t, etc. +#include // CRM_CHECK +#include // mainloop_* +#include // pcmk_rc_* +#include // PCMK_XA_* +#include // CRM_OP_REGISTER #include "pacemaker-based.h" -#include - -#include -#include #if HAVE_SECURITY_PAM_APPL_H -# include +# include // pam_*, PAM_* # define HAVE_PAM 1 #elif HAVE_PAM_PAM_APPL_H -# include +# include // pam_*, PAM_* # define HAVE_PAM 1 #endif static pcmk__tls_t *tls = NULL; -extern int remote_tls_fd; - -void cib_remote_connection_destroy(gpointer user_data); +static int remote_fd = -1; +static int remote_tls_fd = -1; // @TODO This is rather short for someone to type their password #define REMOTE_AUTH_TIMEOUT 10000 -int num_clients; -static bool authenticate_user(const char *user, const char *passwd); -static int cib_remote_listen(gpointer data); -static int cib_remote_msg(gpointer data); - -static void -remote_connection_destroy(gpointer user_data) +/*! + * \internal + * \brief Destroy a client if not authenticated after the timeout has expired + * + * This is used as a callback that runs \c REMOTE_AUTH_TIMEOUT milliseconds + * after a new remote CIB client connects. + * + * If the client is not authenticated, delete it from the mainloop. It will then + * be freed by \c based_remote_client_destroy() via + * \c remote_client_fd_callbacks. + * + * \param[in,out] data Remote CIB client (\c pcmk__client_t) + * + * \return \c G_SOURCE_REMOVE (to destroy the timeout) + */ +static gboolean +remote_auth_timeout_cb(gpointer data) { - pcmk__info("No longer listening for remote connections"); - return; + pcmk__client_t *client = data; + + client->remote->auth_timeout = 0; + + if (pcmk__is_set(client->flags, pcmk__client_authenticated)) { + return G_SOURCE_REMOVE; + } + + mainloop_del_fd(client->remote->source); + pcmk__err("Remote client authentication timed out"); + return G_SOURCE_REMOVE; } -int -init_remote_listener(int port, bool encrypted) +/*! + * \internal + * \brief Check whether a given user is a member of \c CRM_DAEMON_GROUP + * + * \param[in] user User name + * + * \return \c true if \p user is a member of \c CRM_DAEMON_GROUP, or \c false + * otherwise + */ +static bool +is_daemon_group_member(const char *user) { - int rc; - int *ssock = NULL; - struct sockaddr_in saddr; - int optval; - - static struct mainloop_fd_callbacks remote_listen_fd_callbacks = { - .dispatch = cib_remote_listen, - .destroy = remote_connection_destroy, - }; + const struct group *group = getgrnam(CRM_DAEMON_GROUP); - if (port <= 0) { - /* don't start it */ - return 0; + if (group == NULL) { + pcmk__err("Rejecting remote client: " CRM_DAEMON_GROUP " is not a " + "valid group"); + return false; } - if (encrypted) { - bool use_cert = pcmk__x509_enabled(); + for (const char *const *member = (const char *const *) group->gr_mem; + *member != NULL; member++) { - pcmk__notice("Starting TLS listener on port %d", port); - - rc = pcmk__init_tls(&tls, true, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_ANON); - if (rc != pcmk_rc_ok) { - return -1; + if (pcmk__str_eq(user, *member, pcmk__str_none)) { + return true; } - } else { - pcmk__warn("Starting plain-text listener on port %d", port); } -#ifndef HAVE_PAM - pcmk__warn("This build does not support remote administrators because PAM " - "support is not available"); -#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; - } + pcmk__notice("Rejecting remote client: User %s is not a member of group %s", + user, CRM_DAEMON_GROUP); + return false; +} - /* reuse address */ - optval = 1; - 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)); - } +#ifdef HAVE_PAM +/*! + * \internal + * \brief Pass remote user's password to PAM + * + * \param[in] num_msg Number of entries in \p msg + * \param[in] msg Array of PAM messages + * \param[out] response Where to set response to PAM + * \param[in] data User data (the password string) + * + * \return PAM return code (PAM_BUF_ERR for memory errors, PAM_CONV_ERR for all + * other errors, or PAM_SUCCESS on success) + * \note See pam_conv(3) for more explanation + */ +static int +construct_pam_passwd(int num_msg, const struct pam_message **msg, + struct pam_response **response, void *data) +{ + /* In theory, multiple messages are allowed, but due to OS compatibility + * issues, PAM implementations are recommended to only send one message at a + * time. We can require that here for simplicity. + */ + CRM_CHECK((num_msg == 1) && (msg != NULL) && (response != NULL) + && (data != NULL), return PAM_CONV_ERR); - /* bind server socket */ - memset(&saddr, '\0', sizeof(saddr)); - 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 (listen(*ssock, 10) == -1) { - pcmk__err("Cannot listen on socket: %s", pcmk_rc_str(errno)); - close(*ssock); - free(ssock); - return -3; + switch (msg[0]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + // Password requested + break; + case PAM_TEXT_INFO: + pcmk__info("PAM: %s", msg[0]->msg); + data = NULL; + break; + case PAM_ERROR_MSG: + /* In theory we should show msg[0]->msg, but that might + * contain the password, which we don't want in the logs + */ + pcmk__err("PAM reported an error"); + data = NULL; + break; + default: + pcmk__warn("Ignoring PAM message of unrecognized type %d", + msg[0]->msg_style); + return PAM_CONV_ERR; } - mainloop_add_fd("cib-remote", G_PRIORITY_DEFAULT, *ssock, ssock, &remote_listen_fd_callbacks); - pcmk__debug("Started listener on port %d", port); - - return *ssock; + *response = calloc(1, sizeof(struct pam_response)); + if (*response == NULL) { + return PAM_BUF_ERR; + } + (*response)->resp_retcode = 0; + (*response)->resp = pcmk__str_copy((const char *) data); // Caller will free + return PAM_SUCCESS; } +#endif +/*! + * \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 + * + * \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 -is_daemon_group_member(const char *usr) +authenticate_user(const char *user, const char *passwd) { - int index = 0; - gid_t gid = 0; - struct group *group = NULL; - int rc = pcmk_rc_ok; +#ifdef HAVE_PAM + int rc = 0; + bool pass = false; + const void *p_user = NULL; + struct pam_conv p_conv; + struct pam_handle *pam_h = NULL; - rc = pcmk__lookup_user(usr, NULL, &gid); - if (rc != pcmk_rc_ok) { - pcmk__notice("Rejecting remote client: could not find user '%s': %s", - usr, pcmk_rc_str(rc)); - return false; + static const char *pam_name = NULL; + + if (pam_name == NULL) { + pam_name = getenv("CIB_pam_service"); + if (pam_name == NULL) { + pam_name = "login"; + } } - group = getgrgid(gid); - if ((group != NULL) - && pcmk__str_eq(group->gr_name, CRM_DAEMON_GROUP, pcmk__str_none)) { - return true; + p_conv.conv = construct_pam_passwd; + p_conv.appdata_ptr = (void *) 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)); + goto bail; } - group = getgrnam(CRM_DAEMON_GROUP); - if (group == NULL) { - pcmk__err("Rejecting remote client: " CRM_DAEMON_GROUP " is not a " - "valid group"); - return false; + // Check user credentials + rc = pam_authenticate(pam_h, PAM_SILENT); + if (rc != PAM_SUCCESS) { + pcmk__notice("Access for remote user %s denied: %s", user, + pam_strerror(pam_h, rc)); + goto bail; } - while (true) { - char *member = group->gr_mem[index++]; + /* Get the authenticated user name (PAM modules can map the original name to + * something else). Since the CIB manager runs as the daemon user (not + * root), that is the only user that can be successfully authenticated. + */ + 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", + 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); + goto bail; + } - if (member == NULL) { - break; + // @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); + goto bail; + } - } else if (pcmk__str_eq(usr, member, pcmk__str_none)) { - return true; - } + // 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)); + goto bail; } + pass = true; - pcmk__notice("Rejecting remote client: User %s is not a member of group " - CRM_DAEMON_GROUP, usr); +bail: + pam_end(pam_h, rc); + return pass; +#else + // @TODO Implement for non-PAM environments + pcmk__warn("Rejecting remote user %s because this build does not have PAM " + "support", + user); return false; +#endif } static bool @@ -229,163 +310,26 @@ cib_remote_auth(xmlNode * login) return is_daemon_group_member(user) && authenticate_user(user, pass); } -static gboolean -remote_auth_timeout_cb(gpointer data) +static void +cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command) { - pcmk__client_t *client = data; - - client->remote->auth_timeout = 0; + int rc = pcmk_rc_ok; + uint32_t call_options = cib_none; + const char *op = pcmk__xe_get(command, PCMK__XA_CIB_OP); - if (pcmk__is_set(client->flags, pcmk__client_authenticated)) { - return FALSE; + if (!pcmk__xe_is(command, PCMK__XE_CIB_COMMAND)) { + pcmk__log_xml_trace(command, "bad"); + return; } - mainloop_del_fd(client->remote->source); - pcmk__err("Remote client authentication timed out"); + if (client->name == NULL) { + client->name = pcmk__str_copy(client->id); + } - return FALSE; -} - -static int -cib_remote_listen(gpointer data) -{ - 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, - .destroy = cib_remote_connection_destroy, - }; - - /* accept the connection */ - laddr = sizeof(addr); - memset(&addr, 0, sizeof(addr)); - csock = accept(ssock, (struct sockaddr *)&addr, &laddr); - if (csock == -1) { - pcmk__warn("Could not accept remote connection: %s", - pcmk_rc_str(errno)); - return 0; - } - - pcmk__sockaddr2str(&addr, ipstr); - - rc = pcmk__set_nonblocking(csock); - if (rc != pcmk_rc_ok) { - pcmk__warn("Dropping remote connection from %s because it could not be " - "set to non-blocking: %s", - ipstr, pcmk_rc_str(rc)); - close(csock); - return 0; - } - - num_clients++; - - new_client = pcmk__new_unauth_client(NULL); - new_client->remote = pcmk__assert_alloc(1, sizeof(pcmk__remote_t)); - - if (ssock == remote_tls_fd) { - 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) { - close(csock); - return 0; - } - } else { - pcmk__set_client_flags(new_client, pcmk__client_tcp); - new_client->remote->tcp_socket = csock; - } - - // Require the client to authenticate within this time - new_client->remote->auth_timeout = pcmk__create_timer(REMOTE_AUTH_TIMEOUT, - 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); - - new_client->remote->source = - mainloop_add_fd("cib-remote-client", G_PRIORITY_DEFAULT, csock, new_client, - &remote_client_fd_callbacks); - - return 0; -} - -void -cib_remote_connection_destroy(gpointer user_data) -{ - pcmk__client_t *client = user_data; - int csock = -1; - - if (client == NULL) { - return; - } - - pcmk__trace("Cleaning up after client %s disconnect", - pcmk__client_name(client)); - - num_clients--; - pcmk__trace("Num unfree'd clients: %d", num_clients); - - switch (PCMK__CLIENT_TYPE(client)) { - case pcmk__client_tcp: - csock = client->remote->tcp_socket; - break; - case pcmk__client_tls: - if (client->remote->tls_session) { - csock = pcmk__tls_get_client_sock(client->remote); - - if (pcmk__is_set(client->flags, - pcmk__client_tls_handshake_complete)) { - gnutls_bye(client->remote->tls_session, GNUTLS_SHUT_WR); - } - gnutls_deinit(client->remote->tls_session); - client->remote->tls_session = NULL; - } - break; - default: - pcmk__warn("Unknown transport for client %s " - QB_XS " flags=%#016" PRIx64, - pcmk__client_name(client), client->flags); - } - - if (csock >= 0) { - close(csock); - } - - pcmk__free_client(client); - - pcmk__trace("Freed the cib client"); - - if (cib_shutdown_flag) { - cib_shutdown(0); - } - return; -} - -static void -cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command) -{ - if (!pcmk__xe_is(command, PCMK__XE_CIB_COMMAND)) { - pcmk__log_xml_trace(command, "bad"); - 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); + /* 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); @@ -400,12 +344,32 @@ cib_handle_remote_msg(pcmk__client_t *client, xmlNode *command) free(call_uuid); } - if (pcmk__xe_get(command, PCMK__XA_CIB_CALLOPT) == NULL) { - pcmk__xe_set_int(command, PCMK__XA_CIB_CALLOPT, 0); + rc = pcmk__xe_get_flags(command, 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"); + } + + /* Requests with cib_transaction set should not be sent to based directly + * (that is, outside of a commit-transaction request) + */ + 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"); + return; + } + + pcmk__log_xml_trace(command, "remote-request"); + + if (pcmk__str_eq(op, PCMK__VALUE_CIB_NOTIFY, pcmk__str_none)) { + based_update_notify_flags(command, client); } - pcmk__log_xml_trace(command, "Remote command: "); - cib_common_callback_worker(0, 0, command, client, true); + based_process_request(command, true, client); } static int @@ -514,154 +478,293 @@ cib_remote_msg(gpointer data) return 0; } -#ifdef HAVE_PAM -/*! - * \internal - * \brief Pass remote user's password to PAM - * - * \param[in] num_msg Number of entries in \p msg - * \param[in] msg Array of PAM messages - * \param[out] response Where to set response to PAM - * \param[in] data User data (the password string) - * - * \return PAM return code (PAM_BUF_ERR for memory errors, PAM_CONV_ERR for all - * other errors, or PAM_SUCCESS on success) - * \note See pam_conv(3) for more explanation - */ -static int -construct_pam_passwd(int num_msg, const struct pam_message **msg, - struct pam_response **response, void *data) +static void +based_remote_client_destroy(gpointer user_data) { - /* In theory, multiple messages are allowed, but due to OS compatibility - * issues, PAM implementations are recommended to only send one message at a - * time. We can require that here for simplicity. - */ - CRM_CHECK((num_msg == 1) && (msg != NULL) && (response != NULL) - && (data != NULL), return PAM_CONV_ERR); + pcmk__client_t *client = user_data; + int csock = -1; - switch (msg[0]->msg_style) { - case PAM_PROMPT_ECHO_OFF: - case PAM_PROMPT_ECHO_ON: - // Password requested - break; - case PAM_TEXT_INFO: - pcmk__info("PAM: %s", msg[0]->msg); - data = NULL; + if (client == NULL) { + return; + } + + pcmk__trace("Cleaning up after client %s disconnect", + pcmk__client_name(client)); + + switch (PCMK__CLIENT_TYPE(client)) { + case pcmk__client_tcp: + csock = client->remote->tcp_socket; break; - case PAM_ERROR_MSG: - /* In theory we should show msg[0]->msg, but that might - * contain the password, which we don't want in the logs - */ - pcmk__err("PAM reported an error"); - data = NULL; + case pcmk__client_tls: + if (client->remote->tls_session) { + csock = pcmk__tls_get_client_sock(client->remote); + + if (pcmk__is_set(client->flags, + pcmk__client_tls_handshake_complete)) { + gnutls_bye(client->remote->tls_session, GNUTLS_SHUT_WR); + } + gnutls_deinit(client->remote->tls_session); + client->remote->tls_session = NULL; + } break; default: - pcmk__warn("Ignoring PAM message of unrecognized type %d", - msg[0]->msg_style); - return PAM_CONV_ERR; + pcmk__warn("Unknown transport for client %s " + QB_XS " flags=%#016" PRIx64, + pcmk__client_name(client), client->flags); } - *response = calloc(1, sizeof(struct pam_response)); - if (*response == NULL) { - return PAM_BUF_ERR; + if (csock >= 0) { + close(csock); } - (*response)->resp_retcode = 0; - (*response)->resp = pcmk__str_copy((const char *) data); // Caller will free - return PAM_SUCCESS; + + pcmk__free_client(client); + + pcmk__trace("Freed the cib client"); } + +static int +cib_remote_listen(gpointer data) +{ + 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, + .destroy = based_remote_client_destroy, + }; + + /* accept the connection */ + laddr = sizeof(addr); + memset(&addr, 0, sizeof(addr)); + csock = accept(ssock, (struct sockaddr *)&addr, &laddr); + if (csock == -1) { + pcmk__warn("Could not accept remote connection: %s", + pcmk_rc_str(errno)); + return 0; + } + + pcmk__sockaddr2str(&addr, ipstr); + + rc = pcmk__set_nonblocking(csock); + if (rc != pcmk_rc_ok) { + pcmk__warn("Dropping remote connection from %s because it could not be " + "set to non-blocking: %s", + ipstr, pcmk_rc_str(rc)); + close(csock); + return 0; + } + + new_client = pcmk__new_unauth_client(NULL); + new_client->remote = pcmk__assert_alloc(1, sizeof(pcmk__remote_t)); + + if (ssock == remote_tls_fd) { + 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; + } + + // Require the client to authenticate within this time + new_client->remote->auth_timeout = pcmk__create_timer(REMOTE_AUTH_TIMEOUT, + 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); + + new_client->remote->source = + mainloop_add_fd("cib-remote-client", G_PRIORITY_DEFAULT, csock, new_client, + &remote_client_fd_callbacks); + + return 0; +} + +static void +based_remote_listener_destroy(gpointer user_data) +{ + pcmk__info("No longer listening for remote connections"); +} + +static int +init_remote_listener(int port) +{ + int rc; + int *ssock = NULL; + struct sockaddr_in saddr; + int optval; + + static struct mainloop_fd_callbacks remote_listen_fd_callbacks = { + .dispatch = cib_remote_listen, + .destroy = based_remote_listener_destroy, + }; + +#ifndef HAVE_PAM + pcmk__warn("This build does not support remote administrators because PAM " + "support is not available"); #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; + } + + /* reuse address */ + optval = 1; + 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)); + } + + /* bind server socket */ + memset(&saddr, '\0', sizeof(saddr)); + 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 (listen(*ssock, 10) == -1) { + pcmk__err("Cannot listen on socket: %s", pcmk_rc_str(errno)); + close(*ssock); + free(ssock); + return -3; + } + + mainloop_add_fd("cib-remote", G_PRIORITY_DEFAULT, *ssock, ssock, &remote_listen_fd_callbacks); + pcmk__debug("Started listener on port %d", port); + + return *ssock; +} + /*! * \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 - * - * \return \c true if the username and password are accepted, otherwise \c false - * \note This function rejects all credentials when built without PAM support. + * \brief Initialize remote listeners using ports configured in the CIB */ -static bool -authenticate_user(const char *user, const char *passwd) +void +based_remote_init(void) { -#ifdef HAVE_PAM - int rc = 0; - bool pass = false; - const void *p_user = NULL; - struct pam_conv p_conv; - struct pam_handle *pam_h = NULL; + const char *port_s = NULL; + int port = 0; - static const char *pam_name = NULL; + port_s = pcmk__xe_get(the_cib, PCMK_XA_REMOTE_TLS_PORT); - if (pam_name == NULL) { - pam_name = getenv("CIB_pam_service"); - if (pam_name == NULL) { - pam_name = "login"; + if ((pcmk__scan_port(port_s, &port) == pcmk_rc_ok) && (port > 0)) { + // @TODO Implement pre-shared key authentication (see T961) + int rc = pcmk__init_tls(&tls, true, false); + + 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); } } - p_conv.conv = construct_pam_passwd; - p_conv.appdata_ptr = (void *) passwd; + port_s = pcmk__xe_get(the_cib, PCMK_XA_REMOTE_CLEAR_PORT); - 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)); - goto bail; + 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); } +} - // Check user credentials - rc = pam_authenticate(pam_h, PAM_SILENT); - if (rc != PAM_SUCCESS) { - pcmk__notice("Access for remote user %s denied: %s", user, - pam_strerror(pam_h, rc)); - goto bail; +/*! + * \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 + * \c pcmk__client_cleanup(). + */ +void +based_remote_cleanup(void) +{ + if (remote_fd >= 0) { + close(remote_fd); + remote_fd = -1; } - /* Get the authenticated user name (PAM modules can map the original name to - * something else). Since the CIB manager runs as the daemon user (not - * root), that is the only user that can be successfully authenticated. - */ - 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", - 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); - goto bail; + if (remote_tls_fd >= 0) { + close(remote_tls_fd); + remote_tls_fd = -1; } +} - // @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); - goto bail; - } +/*! + * \internal + * \brief Disconnect and free a remote CIB manager client + * + * Drop the client by deleting it 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 Remote client (pcmk__client_t *) + * \param[in] user_data Ignored + * + * \note It is safe to call this more than once for the same client, as long as + * the client pointer itself is still valid. \c mainloop_del_fd() calls + * \c g_source_remove(), which calls \c g_source_destroy(). See the + * documentation for \c g_source_destroy(). + * \note The client gets freed via \c mainloop_gio_destroy() when the last + * reference to the client's \c GSource has been removed. There is likely + * only one reference when we call this function, and thus the client is + * likely to be freed before we return. The current GLib code looks as if + * this is always the case. However, that is a GLib implementation detail. + */ +static void +drop_remote_client(gpointer key, gpointer value, gpointer user_data) +{ + pcmk__client_t *client = value; - // 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)); - goto bail; - } - pass = true; + CRM_CHECK((client->remote != NULL) && (client->remote->source != NULL), + return); -bail: - pam_end(pam_h, rc); - return pass; -#else - // @TODO Implement for non-PAM environments - pcmk__warn("Rejecting remote user %s because this build does not have PAM " - "support", - user); - return false; -#endif + 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 + * + * Drop each client by deleting it 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 Remote client (pcmk__client_t *) + * \param[in] user_data Ignored + */ +void +based_drop_remote_clients(void) +{ + pcmk__foreach_ipc_client(drop_remote_client, NULL); } diff --git a/daemons/based/based_remote.h b/daemons/based/based_remote.h new file mode 100644 index 00000000000..3e9eaa5639c --- /dev/null +++ b/daemons/based/based_remote.h @@ -0,0 +1,19 @@ +/* + * 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_REMOTE__H +#define BASED_REMOTE__H + +#include + +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 258861cdea6..28d3256ba4d 100644 --- a/daemons/based/based_transaction.c +++ b/daemons/based/based_transaction.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. * @@ -9,10 +9,17 @@ #include +#include // EOPNOTSUPP #include +#include // NULL +#include // free -#include -#include +#include // xmlNode + +#include // cib__* +#include // pcmk__client_t, pcmk__s, pcmk__xe_*, etc. +#include // CRM_CHECK +#include // pcmk_rc_* #include "pacemaker-based.h" @@ -54,8 +61,8 @@ based_transaction_source_str(const pcmk__client_t *client, const char *origin) * \return Standard Pacemaker return code */ static int -process_transaction_requests(xmlNodePtr transaction, - const pcmk__client_t *client, const char *source) +process_transaction_requests(xmlNode *transaction, const pcmk__client_t *client, + const char *source) { for (xmlNode *request = pcmk__xe_first_child(transaction, PCMK__XE_CIB_COMMAND, NULL, @@ -77,7 +84,7 @@ process_transaction_requests(xmlNodePtr transaction, /* Commit-transaction is a privileged operation. If we reached * this point, the request came from a privileged connection. */ - rc = cib_process_request(request, true, client); + rc = based_process_request(request, true, client); } } @@ -109,17 +116,17 @@ process_transaction_requests(xmlNodePtr transaction, * \return Standard Pacemaker return code * * \note This function is expected to be called only by - * \p cib_process_commit_transaction(). + * \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(xmlNodePtr transaction, const pcmk__client_t *client, - const char *origin, xmlNodePtr *result_cib) +based_commit_transaction(xmlNode *transaction, const pcmk__client_t *client, + const char *origin, xmlNode **result_cib) { - xmlNodePtr saved_cib = the_cib; + xmlNode *saved_cib = the_cib; int rc = pcmk_rc_ok; char *source = NULL; @@ -128,12 +135,10 @@ based_commit_transaction(xmlNodePtr 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 the_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)); diff --git a/daemons/based/based_transaction.h b/daemons/based/based_transaction.h index 9935c736a68..19dc01ba529 100644 --- a/daemons/based/based_transaction.h +++ b/daemons/based/based_transaction.h @@ -1,5 +1,5 @@ /* - * Copyright 2023 the Pacemaker project contributors + * Copyright 2023-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -10,15 +10,14 @@ #ifndef BASED_TRANSACTION__H #define BASED_TRANSACTION__H -#include +#include // xmlNode -#include +#include // pcmk__client_t char *based_transaction_source_str(const pcmk__client_t *client, const char *origin); -int based_commit_transaction(xmlNodePtr transaction, - const pcmk__client_t *client, - const char *origin, xmlNodePtr *result_cib); +int based_commit_transaction(xmlNode *transaction, const pcmk__client_t *client, + const char *origin, xmlNode **result_cib); #endif // BASED_TRANSACTION__H diff --git a/daemons/based/cib.pam b/daemons/based/cib.pam deleted file mode 100644 index 5d0f6553acc..00000000000 --- a/daemons/based/cib.pam +++ /dev/null @@ -1,6 +0,0 @@ -# login: auth account password session -# may require permission to read /etc/shadow -auth include common-auth -account include common-account -password include common-password -session include common-session diff --git a/daemons/based/pacemaker-based.c b/daemons/based/pacemaker-based.c index 41712b43af7..dd482f5a43c 100644 --- a/daemons/based/pacemaker-based.c +++ b/daemons/based/pacemaker-based.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,58 +9,40 @@ #include +#include // errno +#include // initgroups +#include // SIGTERM #include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include +#include // NULL, size_t +#include // LOG_INFO +#include // gid_t, uid_t +#include // setgid, setuid + +#include // g_*, G_*, etc. +#include // xmlNode + +#include // CRM_CONFIG_DIR, CRM_DAEMON_USER +#include // pcmk__node_update, etc. +#include // crm_ipc_* +#include // crm_log_* +#include // mainloop_add_signal +#include // CRM_EX_*, pcmk_rc_* #include +#define EXIT_ESCALATION_MS 10000 #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; - GMainLoop *mainloop = NULL; gchar *cib_root = NULL; -static bool preserve_status = false; -gboolean cib_writes_enabled = TRUE; gboolean stand_alone = FALSE; -int remote_fd = 0; -int remote_tls_fd = 0; - -GHashTable *config_hash = NULL; - -static void cib_init(void); -void cib_shutdown(int nsig); -static bool startCib(const char *filename); -extern int write_cib_contents(gpointer p); - static crm_exit_t exit_code = CRM_EX_OK; -static void -cib_enable_writes(int nsig) -{ - pcmk__info("(Re)enabling disk writes"); - cib_writes_enabled = TRUE; -} - /*! * \internal * \brief Set up options, users, and groups for stand-alone mode @@ -76,8 +58,7 @@ setup_stand_alone(GError **error) gid_t gid = 0; int rc = pcmk_rc_ok; - preserve_status = true; - cib_writes_enabled = FALSE; + based_is_primary = true; rc = pcmk__daemon_user(&uid, &gid); if (rc != pcmk_rc_ok) { @@ -135,12 +116,20 @@ based_metadata(pcmk__output_t *out) pcmk__opt_based); } +static gboolean +disk_writes_cb(const gchar *option_name, const gchar *optarg, gpointer data, + GError **error) +{ + based_enable_writes(0); + return TRUE; +} + static GOptionEntry entries[] = { { "stand-alone", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &stand_alone, "(Advanced use only) Run in stand-alone mode", NULL }, - { "disk-writes", 'w', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, - &cib_writes_enabled, + { "disk-writes", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + disk_writes_cb, "(Advanced use only) Enable disk writes (enabled by default unless in " "stand-alone mode)", NULL }, @@ -168,6 +157,57 @@ build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) return context; } +static gboolean +based_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 G_SOURCE_REMOVE; +} + +static void +based_shutdown(int nsig) +{ + int active = 0; + xmlNode *notification = NULL; + + if (cib_shutdown_flag) { + // Already shutting down + return; + } + + cib_shutdown_flag = true; + + /* The client count should be 0 by now. If it isn't, that means GLib had + * some other reference to a remote client's source when we dropped the + * remote client. This is believed not to be possible. + * + * If this occurs, continue the shutdown. The clients will be dropped + * eventually. + */ + CRM_LOG_ASSERT(pcmk__ipc_client_count() == 0); + + active = pcmk__cluster_num_active_nodes(); + if (active < 2) { + pcmk__info("Exiting without sending shutdown request (no active " + "peers)"); + based_terminate(CRM_EX_OK); + return; + } + + pcmk__info("Sending shutdown request to %d peers", active); + + notification = pcmk__xe_create(NULL, PCMK__XE_EXIT_NOTIFICATION); + pcmk__xe_set(notification, PCMK__XA_T, PCMK__VALUE_CIB); + pcmk__xe_set(notification, PCMK__XA_CIB_OP, PCMK__CIB_REQUEST_SHUTDOWN); + + pcmk__cluster_send_message(NULL, pcmk_ipc_based, notification); + pcmk__xml_free(notification); + + pcmk__create_timer(EXIT_ESCALATION_MS, based_force_exit, NULL); +} + int main(int argc, char **argv) { @@ -205,10 +245,7 @@ main(int argc, char **argv) goto done; } - mainloop_add_signal(SIGTERM, cib_shutdown); - mainloop_add_signal(SIGPIPE, cib_enable_writes); - - cib_writer = mainloop_add_trigger(G_PRIORITY_LOW, write_cib_contents, NULL); + mainloop_add_signal(SIGTERM, based_shutdown); if ((g_strv_length(processed_args) >= 2) && pcmk__str_eq(processed_args[1], "metadata", pcmk__str_none)) { @@ -270,9 +307,32 @@ main(int argc, char **argv) } 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(); - // Read initial CIB, connect to cluster, and start IPC servers - cib_init(); + if (!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; + } + + pcmk__info("Cluster connection active"); + } // Run the main loop mainloop = g_main_loop_new(NULL, FALSE); @@ -280,23 +340,12 @@ main(int argc, char **argv) "connections"); g_main_loop_run(mainloop); - /* If main loop returned, clean up and exit. We disconnect in case - * terminate_cib(-1) was called. - */ - pcmk_cluster_disconnect(crm_cluster); - pcmk__stop_based_ipc(ipcs_ro, ipcs_rw, ipcs_shm); - 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); + based_cluster_disconnect(); g_free(cib_root); pcmk__output_and_clear_error(&error, out); @@ -308,125 +357,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); - cib_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"); - terminate_cib(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"); - terminate_cib(-1); - } - break; - - default: - break; - } -} - -static void -cib_init(void) -{ - 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 - - config_hash = pcmk__strkey_table(free, free); - - if (!startCib("cib.xml")) { - pcmk__crit("Cannot start CIB... terminating"); - crm_exit(CRM_EX_NOINPUT); - } - - 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; - } -} - -static bool -startCib(const char *filename) -{ - xmlNode *cib = readCibXmlFile(cib_root, filename, !preserve_status); - int port = 0; - - if (activateCibXml(cib, true, "start") != 0) { - return false; - } - - cib_read_config(config_hash, cib); - - pcmk__scan_port(pcmk__xe_get(cib, PCMK_XA_REMOTE_TLS_PORT), &port); - if (port >= 0) { - remote_tls_fd = init_remote_listener(port, true); - } - - pcmk__scan_port(pcmk__xe_get(cib, PCMK_XA_REMOTE_CLEAR_PORT), &port); - if (port >= 0) { - remote_fd = init_remote_listener(port, false); - } - - return true; -} diff --git a/daemons/based/pacemaker-based.h b/daemons/based/pacemaker-based.h index 8ef9396f123..c901eb3d45b 100644 --- a/daemons/based/pacemaker-based.h +++ b/daemons/based/pacemaker-based.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,130 +8,31 @@ */ #ifndef PACEMAKER_BASED__H -# define PACEMAKER_BASED__H +#define PACEMAKER_BASED__H #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include // gboolean, gchar, GHashTable, GMainLoop -#include -#include -#include -#include -#include -#include -#include +#include // pcmk_cluster_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" -#include - -#define OUR_NODENAME (stand_alone? "localhost" : crm_cluster->priv->node_name) - -// CIB-specific client flags -enum cib_client_flags { - // Notifications - cib_notify_pre = (UINT64_C(1) << 0), - cib_notify_post = (UINT64_C(1) << 1), - cib_notify_confirm = (UINT64_C(1) << 3), - cib_notify_diff = (UINT64_C(1) << 4), -}; - -extern bool based_is_primary; -extern GHashTable *config_hash; -extern xmlNode *the_cib; -extern crm_trigger_t *cib_writer; -extern gboolean cib_writes_enabled; +#define OUR_NODENAME (stand_alone? "localhost" : based_cluster->priv->node_name) extern GMainLoop *mainloop; -extern pcmk_cluster_t *crm_cluster; +extern pcmk_cluster_t *based_cluster; extern gboolean stand_alone; extern bool cib_shutdown_flag; extern gchar *cib_root; extern int cib_status; -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; - -int init_remote_listener(int port, bool encrypted); - -void cib_peer_callback(xmlNode *msg, void *private_data); -void cib_common_callback_worker(uint32_t id, uint32_t flags, - xmlNode *op_request, pcmk__client_t *cib_client, - bool privileged); -int cib_process_request(xmlNode *request, bool privileged, - const pcmk__client_t *cib_client); -void cib_shutdown(int nsig); -void terminate_cib(int exit_status); - -void uninitializeCib(void); -xmlNode *readCibXmlFile(const char *dir, const char *file, bool discard_status); -int activateCibXml(xmlNode *doc, bool to_disk, const char *op); - -int cib_process_shutdown_req(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, - xmlNode *existing_cib, xmlNode **result_cib, - xmlNode **answer); -int cib_process_noop(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -int cib_process_ping(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -int cib_process_readwrite(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -int cib_process_replace_svr(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -int cib_server_process_diff(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -int cib_process_sync(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -int cib_process_sync_one(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -int cib_process_delete_absolute(const char *op, int options, - const char *section, xmlNode *req, - xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -int cib_process_upgrade_server(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, - xmlNode *existing_cib, xmlNode **result_cib, - xmlNode **answer); -int cib_process_commit_transaction(const char *op, int options, - const char *section, xmlNode *req, - xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -int cib_process_schemas(const char *op, int options, const char *section, - xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); - -void send_sync_request(const char *host); -int sync_our_cib(xmlNode *request, bool all); - -cib__op_fn_t based_get_op_function(const cib__operation_t *operation); -void cib_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); - -static inline const char * -cib_config_lookup(const char *opt) -{ - return g_hash_table_lookup(config_hash, opt); -} - #endif // PACEMAKER_BASED__H diff --git a/daemons/controld/controld_cib.c b/daemons/controld/controld_cib.c index bdfd8dd8d87..93008948c6d 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. * @@ -731,22 +731,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..57b81afa6c7 100644 --- a/daemons/controld/controld_control.c +++ b/daemons/controld/controld_control.c @@ -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(); @@ -295,13 +283,11 @@ crmd_exit(crm_exit_t exit_code) 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 +442,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/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c index 40f4fa284b3..da4fece9267 100644 --- a/daemons/execd/remoted_schemas.c +++ b/daemons/execd/remoted_schemas.c @@ -201,7 +201,8 @@ get_schema_files(void) * saving them to disk. */ static void -get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode) +get_schema_files_complete(mainloop_child_t *p, int core, int signo, + int exitcode) { const char *errmsg = "Could not load additional schema files"; @@ -217,12 +218,12 @@ get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, i } else { if (signo == 0) { - pcmk__err("%s: process %lld exited %d", errmsg, (long long) pid, + pcmk__err("%s: process %lld exited %d", errmsg, (long long) p->pid, exitcode); } else { pcmk__err("%s: process %lld terminated with signal %d (%s)%s", - errmsg, (long long) pid, signo, strsignal(signo), + errmsg, (long long) p->pid, signo, strsignal(signo), ((core != 0)? " and dumped core" : "")); } diff --git a/daemons/execd/remoted_tls.c b/daemons/execd/remoted_tls.c index 818b48caa4d..c9b2df13bd9 100644 --- a/daemons/execd/remoted_tls.c +++ b/daemons/execd/remoted_tls.c @@ -335,7 +335,6 @@ lrmd_init_remote_tls_server(void) int port = crm_default_remote_port(); struct addrinfo *res = NULL, *iter; const char *bind_name = pcmk__env_option(PCMK__ENV_REMOTE_ADDRESS); - bool use_cert = pcmk__x509_enabled(); static struct mainloop_fd_callbacks remote_listen_fd_callbacks = { .dispatch = lrmd_remote_listen, @@ -347,12 +346,12 @@ lrmd_init_remote_tls_server(void) pcmk__debug("Starting TLS listener on %s port %d", pcmk__s(bind_name, "all addresses on"), port); - rc = pcmk__init_tls(&tls, true, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_PSK); + rc = pcmk__init_tls(&tls, true, true); if (rc != pcmk_rc_ok) { return -1; } - if (!use_cert) { + if (!pcmk__x509_enabled()) { gnutls_datum_t psk_key = { NULL, 0 }; pcmk__tls_add_psk_callback(tls, lrmd_tls_server_key_cb); 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 33984212896..948fc143e06 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,9 +205,17 @@ fenced_cluster_connect(void) return rc; } +/*! + * \internal + * \brief Disconnect from the cluster layer and free \c fenced_cluster + */ void fenced_cluster_disconnect(void) { + if (fenced_cluster == NULL) { + return; + } + pcmk_cluster_disconnect(fenced_cluster); - pcmk_cluster_free(fenced_cluster); + g_clear_pointer(&fenced_cluster, pcmk_cluster_free); } diff --git a/daemons/fenced/pacemaker-fenced.c b/daemons/fenced/pacemaker-fenced.c index 4cd58ebdee2..1980a0c5a58 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(); diff --git a/daemons/pacemakerd/pcmkd_subdaemons.c b/daemons/pacemakerd/pcmkd_subdaemons.c index 7e785159cfc..954b922faf4 100644 --- a/daemons/pacemakerd/pcmkd_subdaemons.c +++ b/daemons/pacemakerd/pcmkd_subdaemons.c @@ -104,7 +104,8 @@ static bool fatal_error = false; static int child_liveness(pcmkd_child_t *child); static gboolean escalate_shutdown(gpointer data); static int start_child(pcmkd_child_t *child); -static void pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode); +static void pcmk_child_exit(mainloop_child_t *p, int core, int signo, + int exitcode); static void pcmk_process_exit(pcmkd_child_t *child); static gboolean pcmk_shutdown_worker(gpointer user_data); static void stop_child(pcmkd_child_t *child, int signal); @@ -253,7 +254,7 @@ escalate_shutdown(gpointer data) } static void -pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) +pcmk_child_exit(mainloop_child_t *p, int core, int signo, int exitcode) { pcmkd_child_t *child = mainloop_child_userdata(p); const char *name = mainloop_child_name(p); @@ -262,7 +263,7 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco // cts-lab looks for this message do_crm_log(((signo == SIGKILL)? LOG_WARNING : LOG_ERR), "%s[%d] terminated with signal %d (%s)%s", - name, pid, signo, strsignal(signo), + name, p->pid, signo, strsignal(signo), (core? " and dumped core" : "")); pcmk_process_exit(child); return; @@ -270,13 +271,13 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco switch(exitcode) { case CRM_EX_OK: - pcmk__info("%s[%d] exited with status %d (%s)", name, pid, exitcode, - crm_exit_str(exitcode)); + pcmk__info("%s[%d] exited with status %d (%s)", name, p->pid, + exitcode, crm_exit_str(exitcode)); break; case CRM_EX_FATAL: pcmk__warn("Shutting cluster down because %s[%d] had fatal failure", - name, pid); + name, p->pid); child->flags &= ~child_respawn; fatal_error = true; pcmk_shutdown(SIGTERM); @@ -289,7 +290,7 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco child->flags &= ~child_respawn; fatal_error = true; msg = pcmk__assert_asprintf("Subdaemon %s[%d] requested panic", - name, pid); + name, p->pid); pcmk__panic(msg); // Should never get here @@ -300,8 +301,8 @@ pcmk_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitco default: // cts-lab looks for this message - pcmk__err("%s[%d] exited with status %d (%s)", name, pid, exitcode, - crm_exit_str(exitcode)); + pcmk__err("%s[%d] exited with status %d (%s)", name, p->pid, + exitcode, crm_exit_str(exitcode)); break; } diff --git a/include/crm/cib/cib_types.h b/include/crm/cib/cib_types.h index 83509ce04e7..9cc5640244b 100644 --- a/include/crm/cib/cib_types.h +++ b/include/crm/cib/cib_types.h @@ -118,8 +118,14 @@ 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; @@ -160,7 +166,9 @@ typedef struct cib_api_operations_s { 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 4c4c54fd473..5e1a838f410 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,8 +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_SYNC "cib_sync" #define PCMK__CIB_REQUEST_IS_PRIMARY "cib_ismaster" #define PCMK__CIB_REQUEST_BUMP "cib_bump" #define PCMK__CIB_REQUEST_QUERY "cib_query" @@ -49,7 +48,7 @@ enum cib__op_attr { //! No special attributes cib__op_attr_none = 0, - //! Modifies CIB + //! May modify state (of the CIB itself or of the CIB manager) cib__op_attr_modifies = (UINT32_C(1) << 1), //! Requires privileges @@ -87,21 +86,24 @@ enum cib__op_type { cib__op_primary, cib__op_query, cib__op_replace, + cib__op_schemas, cib__op_secondary, cib__op_shutdown, - cib__op_sync_all, - cib__op_sync_one, + cib__op_sync, cib__op_upgrade, - cib__op_schemas, }; -gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, - int *_admin_epoch, int *_epoch, int *_updates); - -gboolean 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; @@ -184,12 +186,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(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_ro(cib__op_fn_t fn, xmlNode *req, xmlNode **current_cib, + 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, @@ -203,67 +208,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_query(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_bump(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_create(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_delete(const char *op, int options, const char *section, xmlNode * req, - xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, - xmlNode ** answer); - -int cib_process_diff(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); - -/*! - * \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 - */ -int cib_process_xpath(const char *op, int options, const char *section, - const 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/common/internal.h b/include/crm/common/internal.h index 874cd162a89..25f9731870a 100644 --- a/include/crm/common/internal.h +++ b/include/crm/common/internal.h @@ -12,8 +12,8 @@ #define PCMK__INCLUDED_CRM_COMMON_INTERNAL_H -#include #include +#include #include #include #include diff --git a/include/crm/common/ipc_internal.h b/include/crm/common/ipc_internal.h index 911979ebe2a..e96e8b1bd57 100644 --- a/include/crm/common/ipc_internal.h +++ b/include/crm/common/ipc_internal.h @@ -19,8 +19,6 @@ #include // struct iovec #include // uid_t, gid_t, pid_t, size_t -#include // gnutls_session_t - #include // guint, gpointer, GQueue, ... #include // xmlNode #include // qb_ipcs_connection_t, ... @@ -30,7 +28,7 @@ #include #include // pcmk_controld_api_reply #include // pcmk_pacemakerd_{api_reply,state} -#include // mainloop_io_t +#include // pcmk__remote_t #ifdef __cplusplus extern "C" { @@ -104,28 +102,6 @@ int pcmk__connect_ipc_retry_conrefused(pcmk_ipc_api_t *api, * Server-related */ -typedef struct pcmk__client_s pcmk__client_t; - -struct pcmk__remote_s { - /* Shared */ - char *buffer; - size_t buffer_size; - size_t buffer_offset; - int auth_timeout; - int tcp_socket; - mainloop_io_t *source; - time_t uptime; - char *start_state; - - /* CIB-only */ - char *token; - - /* TLS only */ - - // Must be created by pcmk__new_tls_session() - gnutls_session_t tls_session; -}; - enum pcmk__client_flags { // Lower 32 bits are reserved for server (not library) use @@ -164,7 +140,7 @@ enum pcmk__client_flags { #define PCMK__CLIENT_TYPE(client) ((client)->flags & UINT64_C(0xff00000000)) -struct pcmk__client_s { +typedef struct { unsigned int pid; char *id; @@ -189,11 +165,11 @@ struct pcmk__client_s { qb_ipcs_connection_t *ipcs; /* IPC */ - struct pcmk__remote_s *remote; /* TCP/TLS */ + pcmk__remote_t *remote; // TCP/TLS unsigned int queue_backlog; /* IPC queue length after last flush */ unsigned int queue_max; /* Evict client whose queue grows this big */ -}; +} pcmk__client_t; #define pcmk__set_client_flags(client, flags_to_set) do { \ (client)->flags = pcmk__set_flags_as(__func__, __LINE__, \ diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index 30bde33111a..e10e0625976 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -44,6 +44,10 @@ typedef struct mainloop_child_s mainloop_child_t; // NOTE: sbd (as of at least 1.5.2) uses this typedef struct mainloop_timer_s mainloop_timer_t; +//! \deprecated This has been for internal use only since its creation. +typedef void (*pcmk__mainloop_child_exit_fn_t)(mainloop_child_t *p, int core, + int signo, int exitcode); + void mainloop_cleanup(void); // NOTE: sbd (as of at least 1.5.2) uses this @@ -167,18 +171,13 @@ void mainloop_del_fd(mainloop_io_t * client); * Create a new tracked process * To track a process group, use -pid */ -void mainloop_child_add(pid_t pid, - int timeout, - const char *desc, +void mainloop_child_add(pid_t pid, int timeout, const char *desc, void *userdata, - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)); + pcmk__mainloop_child_exit_fn_t exit_fn); -void mainloop_child_add_with_flags(pid_t pid, - int timeout, - const char *desc, - void *userdata, - enum mainloop_child_flags, - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)); +void mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, + void *userdata, enum mainloop_child_flags, + pcmk__mainloop_child_exit_fn_t exit_fn); void *mainloop_child_userdata(mainloop_child_t * child); int mainloop_child_timeout(mainloop_child_t * child); diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 3b5d07ec1d7..db3a49b64b4 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -1,5 +1,5 @@ /* - * Copyright 2015-2026 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -14,7 +14,9 @@ #ifndef PCMK__CRM_COMMON_MAINLOOP_INTERNAL__H #define PCMK__CRM_COMMON_MAINLOOP_INTERNAL__H -#include // guint +#include // pid_t + +#include // gboolean, guint #include // crm_ipc_t #include // ipc_client_callbacks, mainloop_* @@ -23,6 +25,19 @@ extern "C" { #endif +struct mainloop_child_s { + pid_t pid; + char *desc; + unsigned timerid; + gboolean timeout; + void *privatedata; + + enum mainloop_child_flags flags; + + /* Called when a process dies */ + pcmk__mainloop_child_exit_fn_t exit_fn; +}; + 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 1cf836e7feb..9d4247c6349 100644 --- a/include/crm/common/options_internal.h +++ b/include/crm/common/options_internal.h @@ -212,9 +212,6 @@ bool pcmk__valid_fencing_watchdog_timeout(const char *value); #define PCMK__VALUE_CIB "cib" #define PCMK__VALUE_CIB_DIFF_NOTIFY "cib_diff_notify" #define PCMK__VALUE_CIB_NOTIFY "cib_notify" -#define PCMK__VALUE_CIB_POST_NOTIFY "cib_post_notify" -#define PCMK__VALUE_CIB_PRE_NOTIFY "cib_pre_notify" -#define PCMK__VALUE_CIB_UPDATE_CONFIRMATION "cib_update_confirmation" #define PCMK__VALUE_CLUSTER "cluster" #define PCMK__VALUE_CRMD "crmd" #define PCMK__VALUE_EN "en" diff --git a/include/crm/common/remote_internal.h b/include/crm/common/remote_internal.h index 6656d9c0d1b..51a99354d0c 100644 --- a/include/crm/common/remote_internal.h +++ b/include/crm/common/remote_internal.h @@ -14,13 +14,15 @@ #ifndef PCMK__CRM_COMMON_REMOTE_INTERNAL__H #define PCMK__CRM_COMMON_REMOTE_INTERNAL__H -#include // NULL -#include // bool -#include // xmlNode +#include // bool +#include // NULL +#include // xmlNode -#include // pcmk__client_t +#include // gnutls_session_t + +#include // mainloop_io_t #include // pcmk__node_variant_remote, etc. -#include // struct pcmk__remote_private +#include // launcher #include // pcmk_node_t #ifdef __cplusplus @@ -29,7 +31,25 @@ extern "C" { // internal functions from remote.c -typedef struct pcmk__remote_s pcmk__remote_t; +typedef struct { + // Shared + char *buffer; + size_t buffer_size; + size_t buffer_offset; + int auth_timeout; + int tcp_socket; + mainloop_io_t *source; + time_t uptime; + char *start_state; + + // CIB-only + char *token; + + // TLS-only + + // Must be created by pcmk__new_tls_session() + gnutls_session_t tls_session; +} pcmk__remote_t; int pcmk__remote_send_xml(pcmk__remote_t *remote, const xmlNode *msg); int pcmk__remote_ready(const pcmk__remote_t *remote, int timeout_ms); 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..fa4c1039099 100644 --- a/include/crm/common/schemas_internal.h +++ b/include/crm/common/schemas_internal.h @@ -36,8 +36,7 @@ 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, diff --git a/include/crm/common/tls_internal.h b/include/crm/common/tls_internal.h index 819dbbd5fbe..700efb8632f 100644 --- a/include/crm/common/tls_internal.h +++ b/include/crm/common/tls_internal.h @@ -52,22 +52,7 @@ typedef struct { */ void pcmk__free_tls(pcmk__tls_t *tls); -/*! - * \internal - * \brief Initialize a new TLS object - * - * Unlike \p pcmk__new_tls_session, this function is used for creating the - * global environment for TLS connections. - * - * \param[in,out] tls The object to be allocated and initialized - * \param[in] server Is this a server or not? - * \param[in] cred_type What type of gnutls credentials are in use? - * (GNUTLS_CRD_* constants) - * - * \returns Standard Pacemaker return code - */ -int pcmk__init_tls(pcmk__tls_t **tls, bool server, - gnutls_credentials_type_t cred_type); +int pcmk__init_tls(pcmk__tls_t **tls, bool server, bool psk_fallback); /*! * \internal 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/lib/cib/cib_client.c b/lib/cib/cib_client.c index 38fae89be7c..8d44a34bf10 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. * @@ -281,6 +281,7 @@ cib_client_upgrade(cib_t * cib, int call_options) NULL, call_options, cib->user); } +// @COMPAT cib_api_operations_t:sync is deprecated since 3.0.2 static int cib_client_sync(cib_t * cib, const char *section, int call_options) { @@ -290,8 +291,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 diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c index d827e582bd3..55ebb8639fa 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. * @@ -30,33 +30,50 @@ #define CIB_SERIES "cib" #define CIB_SERIES_MAX 100 -#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are - created with hard links - */ - #define CIB_LIVE_NAME CIB_SERIES ".xml" // key: client ID (const char *) -> value: client (cib_t *) static GHashTable *client_table = NULL; -enum cib_file_flags { - cib_file_flag_dirty = (UINT32_C(1) << 0), - cib_file_flag_live = (UINT32_C(1) << 1), +enum file_flags { + file_flag_dirty = (UINT32_C(1) << 0), + file_flag_live = (UINT32_C(1) << 1), }; -typedef struct cib_file_opaque_s { +typedef struct { char *id; char *filename; - uint32_t flags; // Group of enum cib_file_flags + uint32_t flags; // Group of enum file_flags xmlNode *cib_xml; -} cib_file_opaque_t; +} file_opaque_t; + +/* backup_cib_file() and cib_file_write_with_digest() need to chown the + * written files only in limited circumstances, so these variables allow + * that to be indicated without affecting external callers + */ +static uid_t file_owner = 0; +static uid_t file_group = 0; +static bool do_chown = false; + +static cib__op_fn_t get_op_function(const cib__operation_t *operation); + +#define set_file_flags(cibfile, flags_to_set) do { \ + (cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \ + LOG_TRACE, "CIB file", \ + cibfile->filename, \ + (cibfile)->flags, \ + (flags_to_set), \ + #flags_to_set); \ + } while (0) -static int cib_file_process_commit_transaction(const char *op, int options, - const char *section, - xmlNode *req, xmlNode *input, - xmlNode *existing_cib, - xmlNode **result_cib, - xmlNode **answer); +#define clear_file_flags(cibfile, flags_to_clear) do { \ + (cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ + LOG_TRACE, "CIB file", \ + cibfile->filename, \ + (cibfile)->flags, \ + (flags_to_clear), \ + #flags_to_clear); \ + } while (0) /*! * \internal @@ -67,7 +84,7 @@ static int cib_file_process_commit_transaction(const char *op, int options, static void register_client(const cib_t *cib) { - cib_file_opaque_t *private = cib->variant_opaque; + file_opaque_t *private = cib->variant_opaque; if (client_table == NULL) { client_table = pcmk__strkey_table(NULL, NULL); @@ -84,7 +101,7 @@ register_client(const cib_t *cib) static void unregister_client(const cib_t *cib) { - cib_file_opaque_t *private = cib->variant_opaque; + file_opaque_t *private = cib->variant_opaque; if (client_table == NULL) { return; @@ -118,44 +135,235 @@ get_client(const char *client_id) return g_hash_table_lookup(client_table, (gpointer) client_id); } -static const cib__op_fn_t cib_op_functions[] = { - [cib__op_apply_patch] = cib_process_diff, - [cib__op_bump] = cib_process_bump, - [cib__op_commit_transact] = cib_file_process_commit_transaction, - [cib__op_create] = cib_process_create, - [cib__op_delete] = cib_process_delete, - [cib__op_erase] = cib_process_erase, - [cib__op_modify] = cib_process_modify, - [cib__op_query] = cib_process_query, - [cib__op_replace] = cib_process_replace, - [cib__op_upgrade] = cib_process_upgrade, -}; +static int +process_request(cib_t *cib, xmlNode *request, xmlNode **output) +{ + int rc = pcmk_rc_ok; + const cib__operation_t *operation = NULL; + cib__op_fn_t op_function = NULL; -/* cib_file_backup() and cib_file_write_with_digest() need to chown the - * written files only in limited circumstances, so these variables allow - * that to be indicated without affecting external callers + uint32_t call_options = cib_none; + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + + 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); + + 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); + + if (read_only) { + 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); + } + + 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. + */ + goto done; + } + + if (rc == pcmk_rc_schema_validation) { + // Show validation errors to stderr + pcmk__validate_xml(result_cib, NULL, NULL); + + } else if ((rc == pcmk_rc_ok) && !read_only) { + if (result_cib != private->cib_xml) { + pcmk__xml_free(private->cib_xml); + private->cib_xml = result_cib; + } + 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) { + pcmk__xml_free(result_cib); + } + pcmk__xml_free(cib_diff); + return rc; +} + +/*! + * \internal + * \brief Process requests in a CIB transaction + * + * Stop when a request fails or when all requests have been processed. + * + * \param[in,out] cib CIB client + * \param[in,out] transaction CIB transaction + * + * \return Standard Pacemaker return code */ -static uid_t cib_file_owner = 0; -static uid_t cib_file_group = 0; -static gboolean cib_do_chown = FALSE; +static int +process_transaction_requests(cib_t *cib, xmlNode *transaction) +{ + file_opaque_t *private = cib->variant_opaque; -#define cib_set_file_flags(cibfile, flags_to_set) do { \ - (cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \ - LOG_TRACE, "CIB file", \ - cibfile->filename, \ - (cibfile)->flags, \ - (flags_to_set), \ - #flags_to_set); \ - } while (0) + 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)) { -#define cib_clear_file_flags(cibfile, flags_to_clear) do { \ - (cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ - LOG_TRACE, "CIB file", \ - cibfile->filename, \ - (cibfile)->flags, \ - (flags_to_clear), \ - #flags_to_clear); \ - } while (0) + xmlNode *output = NULL; + const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); + + int rc = process_request(cib, request, &output); + + 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", + private->id, private->filename, op, pcmk_rc_str(rc)); + pcmk__log_xml_info(request, "Failed request"); + return rc; + } + + pcmk__trace("Applied %s request to transaction working CIB for CIB " + "file client (%s) on file '%s'", + op, private->id, private->filename); + pcmk__log_xml_trace(request, "Successful request"); + } + + return pcmk_rc_ok; +} + +/*! + * \internal + * \brief Commit a given CIB file client's transaction to a working CIB copy + * + * \param[in,out] cib CIB file client + * \param[in] transaction CIB transaction + * \param[in,out] result_cib Where to store result CIB + * + * \return Standard Pacemaker return code + * + * \note The caller is responsible for replacing the \p cib argument's + * \p private->cib_xml with \p result_cib on success, and for freeing + * \p result_cib using \p pcmk__xml_free() on failure. + */ +static int +commit_transaction(cib_t *cib, xmlNode *transaction, xmlNode **result_cib) +{ + int rc = pcmk_rc_ok; + file_opaque_t *private = cib->variant_opaque; + xmlNode *saved_cib = private->cib_xml; + + CRM_CHECK(pcmk__xe_is(transaction, PCMK__XE_CIB_TRANSACTION), + return pcmk_rc_no_transaction); + + /* *result_cib should be a copy of private->cib_xml (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 != private->cib_xml), + *result_cib = pcmk__xml_copy(NULL, private->cib_xml)); + + pcmk__trace("Committing transaction for CIB file client (%s) on file '%s' " + "to working CIB", + private->id, private->filename); + + // Apply all changes to a working copy of the CIB + private->cib_xml = *result_cib; + + rc = process_transaction_requests(cib, transaction); + + pcmk__trace("Transaction commit %s for CIB file client (%s) on file '%s'", + ((rc == pcmk_rc_ok)? "succeeded" : "failed"), + private->id, private->filename); + + /* Some request types (for example, erase) may have freed private->cib_xml + * (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 private->cib_xml. + */ + *result_cib = private->cib_xml; + + // Point private->cib_xml back to the unchanged original copy + private->cib_xml = saved_cib; + + return rc; +} + +static int +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; + + CRM_CHECK(client_id != NULL, return -EINVAL); + + cib = get_client(client_id); + CRM_CHECK(cib != NULL, return -EINVAL); + + rc = commit_transaction(cib, input, cib_xml); + if (rc != pcmk_rc_ok) { + file_opaque_t *private = cib->variant_opaque; + + pcmk__err("Could not commit transaction for CIB file client (%s) on " + "file '%s': %s", + private->id, private->filename, pcmk_rc_str(rc)); + } + return pcmk_rc2legacy(rc); +} + +static const cib__op_fn_t op_functions[] = { + [cib__op_apply_patch] = cib__process_apply_patch, + [cib__op_bump] = cib__process_bump, + [cib__op_commit_transact] = process_commit_transact, + [cib__op_create] = cib__process_create, + [cib__op_delete] = cib__process_delete, + [cib__op_erase] = cib__process_erase, + [cib__op_modify] = cib__process_modify, + [cib__op_query] = cib__process_query, + [cib__op_replace] = cib__process_replace, + [cib__op_upgrade] = cib__process_upgrade, +}; /*! * \internal @@ -166,16 +374,16 @@ static gboolean cib_do_chown = FALSE; * \return Function that performs \p operation for a CIB file client */ static cib__op_fn_t -file_get_op_function(const cib__operation_t *operation) +get_op_function(const cib__operation_t *operation) { enum cib__op_type type = operation->type; pcmk__assert(type >= 0); - if (type >= PCMK__NELEM(cib_op_functions)) { + if (type >= PCMK__NELEM(op_functions)) { return NULL; } - return cib_op_functions[type]; + return op_functions[type]; } /*! @@ -184,12 +392,13 @@ file_get_op_function(const cib__operation_t *operation) * * \param[in] filename Name of file to check * - * \return TRUE if file exists and its real path is same as live CIB's + * \return \c true if file exists and its real path is same as the live CIB's, + * or \c false otherwise */ -static gboolean -cib_file_is_live(const char *filename) +static bool +is_live(const char *filename) { - gboolean same = FALSE; + bool same = false; if (filename != NULL) { // Canonicalize file names for true comparison @@ -210,124 +419,48 @@ cib_file_is_live(const char *filename) } static int -cib_file_process_request(cib_t *cib, xmlNode *request, xmlNode **output) -{ - int rc = pcmk_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; - - cib_file_opaque_t *private = cib->variant_opaque; - - // We error checked these in callers - cib__get_operation(op, &operation); - op_function = file_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); - } - - 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. - */ - goto done; - } - - if (rc == -pcmk_err_schema_validation) { - // Show validation errors to stderr - pcmk__validate_xml(result_cib, NULL, NULL, NULL); - - } else if ((rc == pcmk_ok) && !read_only) { - pcmk__log_xml_patchset(LOG_DEBUG, cib_diff); - - if (result_cib != private->cib_xml) { - pcmk__xml_free(private->cib_xml); - private->cib_xml = result_cib; - } - cib_set_file_flags(private, cib_file_flag_dirty); - } - -done: - if ((result_cib != private->cib_xml) && (result_cib != *output)) { - pcmk__xml_free(result_cib); - } - pcmk__xml_free(cib_diff); - return rc; -} - -static int -cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host, - const char *section, xmlNode *data, - xmlNode **output_data, int call_options, - const char *user_name) +file_perform_op_delegate(cib_t *cib, const char *op, const char *host, + const char *section, xmlNode *data, + xmlNode **output_data, int call_options, + const char *user_name) { int rc = pcmk_ok; xmlNode *request = NULL; - xmlNode *output = NULL; - cib_file_opaque_t *private = cib->variant_opaque; + 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 (file_get_op_function(operation) == NULL) { + 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); @@ -336,25 +469,11 @@ cib_file_perform_op_delegate(cib_t *cib, const char *op, const char *host, goto done; } - rc = cib_file_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); } /*! @@ -406,10 +525,10 @@ load_file_cib(const char *filename, xmlNode **output) } static int -cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type) +file_signon(cib_t *cib, const char *name, enum cib_conn_type type) { int rc = pcmk_ok; - cib_file_opaque_t *private = cib->variant_opaque; + file_opaque_t *private = cib->variant_opaque; if (private->filename == NULL) { rc = -EINVAL; @@ -443,7 +562,7 @@ cib_file_signon(cib_t *cib, const char *name, enum cib_conn_type type) * \return Standard Pacemaker return code */ static int -cib_file_write_live(xmlNode *cib_root, char *path) +write_live(xmlNode *cib_root, char *path) { uid_t euid = geteuid(); uid_t daemon_uid = 0; @@ -489,9 +608,9 @@ cib_file_write_live(xmlNode *cib_root, char *path) /* if we're root, we want to update the file ownership */ if (euid == 0) { - cib_file_owner = daemon_uid; - cib_file_group = daemon_gid; - cib_do_chown = TRUE; + file_owner = daemon_uid; + file_group = daemon_gid; + do_chown = true; } /* write the file */ @@ -500,7 +619,7 @@ cib_file_write_live(xmlNode *cib_root, char *path) /* turn off file ownership changes, for other callers */ if (euid == 0) { - cib_do_chown = FALSE; + do_chown = false; } /* undo fancy stuff */ @@ -525,10 +644,10 @@ cib_file_write_live(xmlNode *cib_root, char *path) * running. */ static int -cib_file_signoff(cib_t *cib) +file_signoff(cib_t *cib) { int rc = pcmk_ok; - cib_file_opaque_t *private = cib->variant_opaque; + file_opaque_t *private = cib->variant_opaque; pcmk__debug("Disconnecting from the CIB manager"); cib->state = cib_disconnected; @@ -537,11 +656,11 @@ cib_file_signoff(cib_t *cib) cib->cmds->end_transaction(cib, false, cib_none); /* If the in-memory CIB has been changed, write it to disk */ - if (pcmk__is_set(private->flags, cib_file_flag_dirty)) { + if (pcmk__is_set(private->flags, file_flag_dirty)) { /* If this is the live CIB, write it out with a digest */ - if (pcmk__is_set(private->flags, cib_file_flag_live)) { - rc = cib_file_write_live(private->cib_xml, private->filename); + if (pcmk__is_set(private->flags, file_flag_live)) { + rc = write_live(private->cib_xml, private->filename); rc = pcmk_rc2legacy(rc); /* Otherwise, it's a simple write */ @@ -556,7 +675,7 @@ cib_file_signoff(cib_t *cib) if (rc == pcmk_ok) { pcmk__info("Wrote CIB to %s", private->filename); - cib_clear_file_flags(private, cib_file_flag_dirty); + clear_file_flags(private, file_flag_dirty); } else { pcmk__err("Could not write CIB to %s", private->filename); } @@ -569,16 +688,16 @@ cib_file_signoff(cib_t *cib) } static int -cib_file_free(cib_t *cib) +file_free(cib_t *cib) { int rc = pcmk_ok; if (cib->state != cib_disconnected) { - rc = cib_file_signoff(cib); + rc = file_signoff(cib); } if (rc == pcmk_ok) { - cib_file_opaque_t *private = cib->variant_opaque; + file_opaque_t *private = cib->variant_opaque; free(private->id); free(private->filename); @@ -595,14 +714,13 @@ cib_file_free(cib_t *cib) } static int -cib_file_register_notification(cib_t *cib, const char *callback, int enabled) +file_register_notification(cib_t *cib, const char *callback, int enabled) { return -EPROTONOSUPPORT; } static int -cib_file_set_connection_dnotify(cib_t *cib, - void (*dnotify) (gpointer user_data)) +file_set_connection_dnotify(cib_t *cib, void (*dnotify)(gpointer user_data)) { return -EPROTONOSUPPORT; } @@ -621,10 +739,9 @@ cib_file_set_connection_dnotify(cib_t *cib, * \p cib_api_operations_t:client_id(). */ static int -cib_file_client_id(const cib_t *cib, const char **async_id, - const char **sync_id) +file_client_id(const cib_t *cib, const char **async_id, const char **sync_id) { - cib_file_opaque_t *private = cib->variant_opaque; + file_opaque_t *private = cib->variant_opaque; if (async_id != NULL) { *async_id = private->id; @@ -639,7 +756,7 @@ cib_t * cib_file_new(const char *cib_location) { cib_t *cib = NULL; - cib_file_opaque_t *private = NULL; + file_opaque_t *private = NULL; char *filename = NULL; if (cib_location == NULL) { @@ -660,7 +777,7 @@ cib_file_new(const char *cib_location) return NULL; } - private = calloc(1, sizeof(cib_file_opaque_t)); + private = calloc(1, sizeof(file_opaque_t)); if (private == NULL) { free(cib); free(filename); @@ -674,20 +791,20 @@ cib_file_new(const char *cib_location) cib->variant_opaque = private; private->flags = 0; - if (cib_file_is_live(cib_location)) { - cib_set_file_flags(private, cib_file_flag_live); + if (is_live(cib_location)) { + set_file_flags(private, file_flag_live); pcmk__trace("File %s detected as live CIB", cib_location); } /* assign variant specific ops */ - cib->delegate_fn = cib_file_perform_op_delegate; - cib->cmds->signon = cib_file_signon; - cib->cmds->signoff = cib_file_signoff; - cib->cmds->free = cib_file_free; - cib->cmds->register_notification = cib_file_register_notification; - cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify; + cib->delegate_fn = file_perform_op_delegate; + cib->cmds->signon = file_signon; + cib->cmds->signoff = file_signoff; + cib->cmds->free = file_free; + cib->cmds->register_notification = file_register_notification; + cib->cmds->set_connection_dnotify = file_set_connection_dnotify; - cib->cmds->client_id = cib_file_client_id; + cib->cmds->client_id = file_client_id; return cib; } @@ -699,12 +816,13 @@ cib_file_new(const char *cib_location) * \param[in] root Root of XML tree to compare * \param[in] sigfile Name of signature file containing digest to compare * - * \return TRUE if digests match or signature file does not exist, else FALSE + * \return \c true if digests match or signature file does not exist, or + * \c false otherwise */ -static gboolean -cib_file_verify_digest(xmlNode *root, const char *sigfile) +static bool +verify_digest(xmlNode *root, const char *sigfile) { - gboolean passed = FALSE; + bool passed = false; char *expected; int rc = pcmk__file_contents(sigfile, &expected); @@ -712,16 +830,16 @@ cib_file_verify_digest(xmlNode *root, const char *sigfile) case pcmk_rc_ok: if (expected == NULL) { pcmk__err("On-disk digest at %s is empty", sigfile); - return FALSE; + return false; } break; case ENOENT: pcmk__warn("No on-disk digest present at %s", sigfile); - return TRUE; + return true; default: pcmk__err("Could not read on-disk digest from %s: %s", sigfile, pcmk_rc_str(rc)); - return FALSE; + return false; } passed = pcmk__verify_digest(root, expected); free(expected); @@ -784,7 +902,7 @@ cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **ro } /* Verify that digests match */ - if (cib_file_verify_digest(local_root, sigfile) == FALSE) { + if (!verify_digest(local_root, sigfile)) { free(local_sigfile); pcmk__xml_free(local_root); return -pcmk_err_cib_modified; @@ -809,7 +927,7 @@ cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **ro * \return 0 on success, -1 on error */ static int -cib_file_backup(const char *cib_dirname, const char *cib_filename) +backup_cib_file(const char *cib_dirname, const char *cib_filename) { int rc = 0; unsigned int seq = 0U; @@ -824,8 +942,9 @@ cib_file_backup(const char *cib_dirname, const char *cib_filename) // @TODO maybe handle errors better ... seq = 0U; } - backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq, - CIB_SERIES_BZIP); + + // Must pass false because archived copies are created with hard links + backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq, false); backup_digest = pcmk__assert_asprintf("%s.sig", backup_path); /* Remove the old backups if they exist */ @@ -848,17 +967,17 @@ cib_file_backup(const char *cib_dirname, const char *cib_filename) } else { pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq, CIB_SERIES_MAX); - if (cib_do_chown) { + if (do_chown) { int rc2; - if ((chown(backup_path, cib_file_owner, cib_file_group) < 0) + if ((chown(backup_path, file_owner, file_group) < 0) && (errno != ENOENT)) { pcmk__err("Could not set owner of %s: %s", backup_path, strerror(errno)); rc = -1; } - if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0) + if ((chown(backup_digest, file_owner, file_group) < 0) && (errno != ENOENT)) { pcmk__err("Could not set owner of %s: %s", backup_digest, @@ -866,7 +985,7 @@ cib_file_backup(const char *cib_dirname, const char *cib_filename) rc = -1; } rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES, - cib_file_owner, cib_file_group); + file_owner, file_group); if (rc2 != pcmk_rc_ok) { pcmk__err("Could not set owner of sequence file in %s: %s", cib_dirname, pcmk_rc_str(rc2)); @@ -896,7 +1015,7 @@ cib_file_backup(const char *cib_dirname, const char *cib_filename) * \return void */ static void -cib_file_prepare_xml(xmlNode *root) +prepare_xml(xmlNode *root) { xmlNode *cib_status_root = NULL; @@ -955,14 +1074,14 @@ cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, } /* Back up the existing CIB */ - if (cib_file_backup(cib_dirname, cib_filename) < 0) { + if (backup_cib_file(cib_dirname, cib_filename) < 0) { exit_rc = pcmk_err_cib_backup; goto cleanup; } pcmk__debug("Writing CIB to disk"); umask(S_IWGRP | S_IWOTH | S_IROTH); - cib_file_prepare_xml(cib_root); + prepare_xml(cib_root); /* Write the CIB to a temporary file, so we can deploy (near) atomically */ fd = mkstemp(tmp_cib); @@ -980,7 +1099,7 @@ cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, exit_rc = pcmk_err_cib_save; goto cleanup; } - if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { + if (do_chown && (fchown(fd, file_owner, file_group) < 0)) { pcmk__err("Couldn't protect temporary file %s for writing CIB: %s", tmp_cib, strerror(errno)); exit_rc = pcmk_err_cib_save; @@ -1008,7 +1127,7 @@ cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, exit_rc = pcmk_err_cib_save; goto cleanup; } - if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) { + if (do_chown && (fchown(fd, file_owner, file_group) < 0)) { pcmk__err("Couldn't protect temporary file %s for writing CIB: %s", tmp_cib, strerror(errno)); exit_rc = pcmk_err_cib_save; @@ -1054,136 +1173,3 @@ cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, free(tmp_cib); return exit_rc; } - -/*! - * \internal - * \brief Process requests in a CIB transaction - * - * Stop when a request fails or when all requests have been processed. - * - * \param[in,out] cib CIB client - * \param[in,out] transaction CIB transaction - * - * \return Standard Pacemaker return code - */ -static int -cib_file_process_transaction_requests(cib_t *cib, xmlNode *transaction) -{ - cib_file_opaque_t *private = cib->variant_opaque; - - 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)) { - - xmlNode *output = NULL; - const char *op = pcmk__xe_get(request, PCMK__XA_CIB_OP); - - int rc = cib_file_process_request(cib, request, &output); - - rc = pcmk_legacy2rc(rc); - if (rc != pcmk_rc_ok) { - pcmk__err("Aborting transaction for CIB file client (%s) on file " - "'%s' due to failed %s request: %s", - private->id, private->filename, op, pcmk_rc_str(rc)); - pcmk__log_xml_info(request, "Failed request"); - return rc; - } - - pcmk__trace("Applied %s request to transaction working CIB for CIB " - "file client (%s) on file '%s'", - op, private->id, private->filename); - pcmk__log_xml_trace(request, "Successful request"); - } - - return pcmk_rc_ok; -} - -/*! - * \internal - * \brief Commit a given CIB file client's transaction to a working CIB copy - * - * \param[in,out] cib CIB file client - * \param[in] transaction CIB transaction - * \param[in,out] result_cib Where to store result CIB - * - * \return Standard Pacemaker return code - * - * \note The caller is responsible for replacing the \p cib argument's - * \p private->cib_xml with \p result_cib on success, and for freeing - * \p result_cib using \p pcmk__xml_free() on failure. - */ -static int -cib_file_commit_transaction(cib_t *cib, xmlNode *transaction, - xmlNode **result_cib) -{ - int rc = pcmk_rc_ok; - cib_file_opaque_t *private = cib->variant_opaque; - xmlNode *saved_cib = private->cib_xml; - - CRM_CHECK(pcmk__xe_is(transaction, PCMK__XE_CIB_TRANSACTION), - 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. - */ - CRM_CHECK((*result_cib != NULL) && (*result_cib != private->cib_xml), - *result_cib = pcmk__xml_copy(NULL, private->cib_xml)); - - pcmk__trace("Committing transaction for CIB file client (%s) on file '%s' " - "to working CIB", - private->id, private->filename); - - // Apply all changes to a working copy of the CIB - private->cib_xml = *result_cib; - - rc = cib_file_process_transaction_requests(cib, transaction); - - pcmk__trace("Transaction commit %s for CIB file client (%s) on file '%s'", - ((rc == pcmk_rc_ok)? "succeeded" : "failed"), - private->id, private->filename); - - /* Some request types (for example, erase) may have freed private->cib_xml - * (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 private->cib_xml. - */ - *result_cib = private->cib_xml; - - // Point private->cib_xml back to the unchanged original copy - private->cib_xml = saved_cib; - - return rc; -} - -static int -cib_file_process_commit_transaction(const char *op, int options, - const char *section, xmlNode *req, - xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) -{ - int rc = pcmk_rc_ok; - const char *client_id = pcmk__xe_get(req, PCMK__XA_CIB_CLIENTID); - cib_t *cib = NULL; - - CRM_CHECK(client_id != NULL, return -EINVAL); - - cib = get_client(client_id); - CRM_CHECK(cib != NULL, return -EINVAL); - - rc = cib_file_commit_transaction(cib, input, result_cib); - if (rc != pcmk_rc_ok) { - cib_file_opaque_t *private = cib->variant_opaque; - - pcmk__err("Could not commit transaction for CIB file client (%s) on " - "file '%s': %s", - private->id, private->filename, pcmk_rc_str(rc)); - } - return pcmk_rc2legacy(rc); -} diff --git a/lib/cib/cib_native.c b/lib/cib/cib_native.c index 2c54b4a7bab..d7876f7488c 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. * @@ -68,12 +68,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 +103,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 +141,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: @@ -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 c390f71a6d2..0c062bc2d22 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. * @@ -10,6 +10,7 @@ #include #include +#include // uint32_t #include #include #include @@ -107,6 +108,9 @@ static const cib__operation_t cib_ops[] = { |cib__op_attr_writes_through |cib__op_attr_transaction }, + { + PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, cib__op_attr_local + }, { PCMK__CIB_REQUEST_SECONDARY, cib__op_secondary, cib__op_attr_privileged|cib__op_attr_local @@ -115,10 +119,7 @@ static const cib__operation_t cib_ops[] = { PCMK__CIB_REQUEST_SHUTDOWN, cib__op_shutdown, cib__op_attr_privileged }, { - PCMK__CIB_REQUEST_SYNC_TO_ALL, cib__op_sync_all, cib__op_attr_privileged - }, - { - PCMK__CIB_REQUEST_SYNC_TO_ONE, cib__op_sync_one, cib__op_attr_privileged + PCMK__CIB_REQUEST_SYNC, cib__op_sync, cib__op_attr_privileged }, { PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade, @@ -127,9 +128,6 @@ static const cib__operation_t cib_ops[] = { |cib__op_attr_writes_through |cib__op_attr_transaction }, - { - PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, cib__op_attr_local - } }; /*! @@ -166,251 +164,265 @@ cib__get_operation(const char *op, const cib__operation_t **operation) } 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_apply_patch(xmlNode *req, xmlNode **cib, xmlNode **answer) { - xmlNode *obj_root = NULL; - int result = pcmk_ok; - - pcmk__trace("Processing %s for %s section", op, - pcmk__s(section, "unspecified")); - - if (options & cib_xpath) { - return cib_process_xpath(op, options, section, req, input, - existing_cib, result_cib, answer); - } + const xmlNode *input = cib__get_calldata(req); + int rc = xml_apply_patchset(*cib, input, true); - CRM_CHECK(*answer == NULL, pcmk__xml_free(*answer)); - *answer = NULL; - - if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { - section = NULL; - } - - obj_root = pcmk_find_cib_element(existing_cib, section); - - if (obj_root == NULL) { - result = -ENXIO; + return pcmk_legacy2rc(rc); +} - } else if (options & cib_no_children) { - xmlNode *shallow = pcmk__xe_create(*answer, - (const char *) obj_root->name); +static void +update_counter(xmlNode *xml, const char *field, bool reset) +{ + 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)); - pcmk__xe_copy_attrs(shallow, obj_root, pcmk__xaf_none); - *answer = shallow; + if (was_set) { + pcmk__trace("Updating %s from %d to %d", field, old_value, new_value); } else { - *answer = obj_root; + pcmk__trace("Updating %s from unset to %d", field, new_value); } - if (result == pcmk_ok && *answer == NULL) { - pcmk__err("Error creating query response"); - result = -ENOMSG; - } + pcmk__xe_set_int(xml, field, new_value); +} - return result; +int +cib__process_bump(xmlNode *req, xmlNode **cib, xmlNode **answer) +{ + update_counter(*cib, PCMK_XA_EPOCH, false); + return pcmk_rc_ok; } static int -update_counter(xmlNode *xml_obj, const char *field, bool reset) +add_cib_object(xmlNode *parent, xmlNode *new_obj) { - char *new_value = NULL; - char *old_value = NULL; - int int_value = -1; + const char *object_name = NULL; + const char *object_id = NULL; - if (!reset && pcmk__xe_get(xml_obj, field) != NULL) { - old_value = pcmk__xe_get_copy(xml_obj, field); + if ((parent == NULL) || (new_obj == NULL)) { + return EINVAL; } - if (old_value != NULL) { - int_value = atoi(old_value); - new_value = pcmk__itoa(++int_value); - } else { - new_value = pcmk__str_copy("1"); + + object_name = (const char *) new_obj->name; + if (object_name == NULL) { + return EINVAL; } - pcmk__trace("Update %s from %s to %s", field, pcmk__s(old_value, "unset"), - new_value); - pcmk__xe_set(xml_obj, field, new_value); + 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; + } - free(new_value); - free(old_value); + if (object_id != NULL) { + pcmk__trace("Processing creation of <%s " PCMK_XA_ID "='%s'>", + object_name, object_id); + } else { + pcmk__trace("Processing creation of <%s>", object_name); + } - return pcmk_ok; + /* @COMPAT PCMK__XA_REPLACE is deprecated since 2.1.6. Due to a legacy use + * case, PCMK__XA_REPLACE has special meaning and should not be included in + * the newly created object until we can break behavioral backward + * compatibility. + * + * At a compatibility break, drop this and drop the definition of + * PCMK__XA_REPLACE. Treat it like any other attribute. + */ + pcmk__xml_tree_foreach(new_obj, pcmk__xe_remove_attr_cb, + (void *) PCMK__XA_REPLACE); + + pcmk__xml_copy(parent, new_obj); + 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) +static void +update_results(xmlNode *failed, xmlNode *target, const char *operation, int rc) { - int result = pcmk_ok; + xmlNode *failed_update = pcmk__xe_create(failed, PCMK__XE_FAILED_UPDATE); - pcmk__trace("Processing \"%s\" event", op); + pcmk__xml_copy(failed_update, target); - if (*result_cib != existing_cib) { - pcmk__xml_free(*result_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; + pcmk__xe_set(failed_update, PCMK_XA_ID, pcmk__xe_id(target)); + pcmk__xe_set(failed_update, PCMK_XA_OBJECT_TYPE, + (const char *) target->name); + pcmk__xe_set(failed_update, PCMK_XA_OPERATION, operation); + pcmk__xe_set(failed_update, PCMK_XA_REASON, pcmk_rc_str(rc)); - return result; + pcmk__warn("Action %s failed: %s", operation, pcmk_rc_str(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) +static int +process_create_xpath(const char *op, const char *xpath, xmlNode *input, + xmlNode *cib) { - int rc = 0; - const char *max_schema = pcmk__xe_get(req, PCMK__XA_CIB_SCHEMA_MAX); - const char *original_schema = NULL; - const char *new_schema = NULL; - - *answer = NULL; - pcmk__trace("Processing \"%s\" event with max=%s", op, max_schema); - - original_schema = pcmk__xe_get(existing_cib, PCMK_XA_VALIDATE_WITH); - rc = pcmk__update_schema(result_cib, max_schema, true, - !pcmk__is_set(options, cib_verbose)); - rc = pcmk_rc2legacy(rc); - new_schema = pcmk__xe_get(*result_cib, PCMK_XA_VALIDATE_WITH); + int num_results = 0; + int rc = pcmk_rc_ok; + xmlXPathObject *xpath_obj = pcmk__xpath_search(cib->doc, xpath); + xmlNode *match = NULL; + xmlChar *path = NULL; - 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; + 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; } - return rc; -} - -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 result = pcmk_ok; + match = pcmk__xpath_result(xpath_obj, 0); + if (match == NULL) { + goto done; + } - pcmk__trace("Processing %s for epoch='%s'", op, - pcmk__s(pcmk__xe_get(existing_cib, PCMK_XA_EPOCH), "")); + path = xmlGetNodePath(match); + pcmk__debug("Processing %s op for %s with %s", op, xpath, path); + free(path); - *answer = NULL; - update_counter(*result_cib, PCMK_XA_EPOCH, false); + pcmk__xml_copy(match, input); - return result; +done: + xmlXPathFreeObject(xpath_obj); + return rc; } int -cib_process_replace(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) { - int result = pcmk_ok; - - pcmk__trace("Processing %s for %s section", op, - pcmk__s(section, "unspecified")); + 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 rc = pcmk_rc_ok; + xmlNode *update_section = NULL; - if (options & cib_xpath) { - return cib_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); } - *answer = NULL; - if (input == NULL) { - return -EINVAL; + pcmk__err("Cannot perform modification with no data"); + return EINVAL; } - if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) { - section = NULL; + if (pcmk__strcase_any_of(section, PCMK__XE_ALL, PCMK_XE_CIB, NULL) + || pcmk__xe_is(input, PCMK_XE_CIB)) { - } else if (pcmk__xe_is(input, section)) { - section = NULL; + return cib__process_modify(req, cib, answer); } - 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); + // @COMPAT Deprecated since 2.1.8 + failed = pcmk__xe_create(NULL, PCMK__XE_FAILED); + + 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)) { + + rc = add_cib_object(update_section, a_child); + if (rc != pcmk_rc_ok) { + update_results(failed, a_child, op, rc); + break; } - free(digest_verify); + } - } else { - pcmk__trace("No digest to verify"); + } else { + rc = add_cib_object(update_section, input); + if (rc != pcmk_rc_ok) { + update_results(failed, input, op, rc); } + } - cib_version_details(existing_cib, &admin_epoch, &epoch, &updates); - cib_version_details(input, &replace_admin_epoch, &replace_epoch, &replace_updates); + if ((rc == pcmk_rc_ok) && (failed->children != NULL)) { + rc = EINVAL; + } - if (replace_admin_epoch < admin_epoch) { - reason = PCMK_XA_ADMIN_EPOCH; + if (rc != pcmk_rc_ok) { + pcmk__log_xml_err(failed, "CIB Update failures"); + *answer = failed; - } else if (replace_admin_epoch > admin_epoch) { - /* no more checks */ + } else { + pcmk__xml_free(failed); + } - } else if (replace_epoch < epoch) { - reason = PCMK_XA_EPOCH; + return rc; +} - } else if (replace_epoch > epoch) { - /* no more checks */ +static int +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; - } else if (replace_updates < updates) { - reason = PCMK_XA_NUM_UPDATES; - } + int num_results = 0; + int rc = pcmk_rc_ok; + + xmlXPathObject *xpath_obj = pcmk__xpath_search(cib->doc, xpath); - 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; + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none); + + num_results = pcmk__xpath_num_results(xpath_obj); + if (num_results == 0) { + pcmk__debug("%s was already removed", xpath); + goto done; + } + + for (int i = 0; i < num_results; i++) { + xmlNode *match = NULL; + xmlChar *path = NULL; + + /* If we're deleting multiple nodes, go in reverse document order. + * If we go in forward order and the node set contains both a parent and + * its descendant, then deleting the parent frees the descendant before + * the loop reaches the descendant. This is a use-after-free error. + * + * @COMPAT cib_multiple is only ever used with delete operations. The + * correct order to process multiple nodes for operations other than + * query (forward) and delete (reverse) is less clear but likely should + * be reverse. If we ever replace the CIB public API with libpacemaker + * functions, revisit this. For now, we keep forward order for other + * operations to preserve backward compatibility, even though external + * callers of other ops with cib_multiple might segfault. + * + * For more info, see comment in xpath2.c:update_xpath_nodes() in + * libxml2. + */ + if (pcmk__is_set(options, cib_multiple)) { + match = pcmk__xpath_result(xpath_obj, num_results - 1 - i); } 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); + match = pcmk__xpath_result(xpath_obj, i); } - if (*result_cib != existing_cib) { - pcmk__xml_free(*result_cib); + if (match == NULL) { + continue; } - *result_cib = pcmk__xml_copy(NULL, input); - } else { - xmlNode *obj_root = NULL; + path = xmlGetNodePath(match); + pcmk__debug("Processing %s op for %s with %s", op, xpath, path); + free(path); - 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 (match == cib) { + pcmk__warn("Cannot perform %s for %s: the XPath is addressing the " + "whole /cib", op, xpath); + rc = EINVAL; + break; + } + + pcmk__xml_free(match); + if (!pcmk__is_set(options, cib_multiple)) { + break; } } - return result; +done: + xmlXPathFreeObject(xpath_obj); + return rc; } static int @@ -426,407 +438,567 @@ 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 cib_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_modify(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) { - xmlNode *obj_root = NULL; - uint32_t flags = pcmk__xaf_none; + uint32_t options = cib_none; - pcmk__trace("Processing \"%s\" event", op); + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none); - if (options & cib_xpath) { - return cib_process_xpath(op, options, section, req, input, - existing_cib, result_cib, answer); + if (pcmk__is_set(options, cib_xpath)) { + return process_delete_xpath(req, *cib); } - if (input == NULL) { - pcmk__err("Cannot perform modification with no data"); - return -EINVAL; + return process_delete_section(req, *cib); +} + +int +cib__process_erase(xmlNode *req, xmlNode **cib, xmlNode **answer) +{ + 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); } - obj_root = pcmk_find_cib_element(*result_cib, section); - if (obj_root == NULL) { - xmlNode *tmp_section = NULL; - const char *path = pcmk_cib_parent_name_for(section); + /* Copying is a 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); - if (path == NULL) { - return -EINVAL; - } + update_counter(*cib, PCMK_XA_ADMIN_EPOCH, false); - tmp_section = pcmk__xe_create(NULL, section); - cib_process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section, - NULL, result_cib, answer); - pcmk__xml_free(tmp_section); + pcmk__xml_free(empty); + return pcmk_rc_ok; +} - obj_root = pcmk_find_cib_element(*result_cib, section); - } +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; - CRM_CHECK(obj_root != NULL, return -EINVAL); + int num_results = 0; + int rc = pcmk_rc_ok; + xmlXPathObject *xpath_obj = NULL; + uint32_t flags = pcmk__xaf_none; + 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; + flags = pcmk__xaf_score_update; } - 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 (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; } } - return pcmk_ok; +done: + xmlXPathFreeObject(xpath_obj); + return rc; } static int -add_cib_object(xmlNode * parent, xmlNode * new_obj) +process_modify_section(const xmlNode *request, xmlNode *cib) { - const char *object_name = NULL; - const char *object_id = NULL; + const char *section = pcmk__xe_get(request, PCMK__XA_CIB_SECTION); + xmlNode *input = cib__get_calldata(request); + uint32_t options = cib_none; - if ((parent == NULL) || (new_obj == NULL)) { - return -EINVAL; + 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; } - object_name = (const char *) new_obj->name; - if (object_name == NULL) { - return -EINVAL; + if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) { + input = pcmk_find_cib_element(input, section); } - 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; + if (input == NULL) { + pcmk__err("Cannot complete CIB modify request with no input data"); + return EINVAL; } - if (object_id != NULL) { - pcmk__trace("Processing creation of <%s " PCMK_XA_ID "='%s'>", - object_name, object_id); - } else { - pcmk__trace("Processing creation of <%s>", object_name); + 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; + } + + tmp_section = pcmk__xe_create(NULL, section); + + // @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(cib, section); } - /* @COMPAT PCMK__XA_REPLACE is deprecated since 2.1.6. Due to a legacy use - * case, PCMK__XA_REPLACE has special meaning and should not be included in - * the newly created object until we can break behavioral backward - * compatibility. - * - * At a compatibility break, drop this and drop the definition of - * PCMK__XA_REPLACE. Treat it like any other attribute. - */ - pcmk__xml_tree_foreach(new_obj, pcmk__xe_remove_attr_cb, - (void *) PCMK__XA_REPLACE); + // Should be impossible, as we just created this section if it didn't exist + CRM_CHECK(obj_root != NULL, return EINVAL); - pcmk__xml_copy(parent, new_obj); - return pcmk_ok; + if (pcmk__xe_update_match(obj_root, input, flags) == pcmk_rc_ok) { + return pcmk_rc_ok; + } + + if (!pcmk__is_set(options, cib_can_create)) { + return ENXIO; + } + + pcmk__xml_copy(obj_root, input); + return pcmk_rc_ok; } -static void -update_results(xmlNode *failed, xmlNode *target, const char *operation, int rc) +int +cib__process_modify(xmlNode *req, xmlNode **cib, xmlNode **answer) { - xmlNode *failed_update = pcmk__xe_create(failed, PCMK__XE_FAILED_UPDATE); + uint32_t options = cib_none; - pcmk__xml_copy(failed_update, target); + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none); - pcmk__xe_set(failed_update, PCMK_XA_ID, pcmk__xe_id(target)); - pcmk__xe_set(failed_update, PCMK_XA_OBJECT_TYPE, - (const char *) target->name); - pcmk__xe_set(failed_update, PCMK_XA_OPERATION, operation); - pcmk__xe_set(failed_update, PCMK_XA_REASON, pcmk_rc_str(rc)); + if (pcmk__is_set(options, cib_xpath)) { + return process_modify_xpath(req, *cib); + } - pcmk__warn("Action %s failed: %s", operation, pcmk_rc_str(rc)); + return process_modify_section(req, *cib); } -int -cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, - xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) +static int +process_query_xpath(const xmlNode *request, xmlNode *cib, xmlNode **answer) { - xmlNode *failed = NULL; - int result = pcmk_ok; - xmlNode *update_section = NULL; + 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; - 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; + int num_results = 0; + int rc = pcmk_rc_ok; + xmlXPathObject *xpath_obj = pcmk__xpath_search(cib->doc, xpath); - } else if (pcmk__str_eq(section, PCMK_XE_CIB, pcmk__str_casei)) { - section = NULL; + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none); - } else if (pcmk__xe_is(input, PCMK_XE_CIB)) { - section = 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; } - CRM_CHECK(strcmp(op, PCMK__CIB_REQUEST_CREATE) == 0, return -EINVAL); - - if (input == NULL) { - pcmk__err("Cannot perform modification with no data"); - return -EINVAL; + if (num_results > 1) { + *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); } - if (section == NULL) { - return cib_process_modify(op, options, section, req, input, existing_cib, result_cib, - answer); - } + for (int i = 0; i < num_results; i++) { + xmlChar *path = NULL; + xmlNode *match = pcmk__xpath_result(xpath_obj, i); - // @COMPAT Deprecated since 2.1.8 - failed = pcmk__xe_create(NULL, PCMK__XE_FAILED); + if (match == NULL) { + continue; + } - update_section = pcmk_find_cib_element(*result_cib, section); - if (pcmk__xe_is(input, section)) { - xmlNode *a_child = NULL; + path = xmlGetNodePath(match); + pcmk__debug("Processing %s op for %s with %s", op, xpath, path); + free(path); - for (a_child = pcmk__xml_first_child(input); a_child != NULL; - a_child = pcmk__xml_next(a_child)) { + if (pcmk__is_set(options, cib_no_children)) { + xmlNode *shallow = pcmk__xe_create(*answer, + (const char *) match->name); - result = add_cib_object(update_section, a_child); - if (result != pcmk_ok) { - update_results(failed, a_child, op, pcmk_legacy2rc(result)); - break; + pcmk__xe_copy_attrs(shallow, match, pcmk__xaf_none); + + if (*answer == NULL) { + *answer = shallow; } + + continue; } - } else { - result = add_cib_object(update_section, input); - if (result != pcmk_ok) { - update_results(failed, input, op, pcmk_legacy2rc(result)); + 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; } - if ((result == pcmk_ok) && (failed->children != NULL)) { - result = -EINVAL; + obj_root = pcmk_find_cib_element(cib, section); + if (obj_root == NULL) { + return ENXIO; } - if (result != pcmk_ok) { - pcmk__log_xml_err(failed, "CIB Update failures"); - *answer = failed; + /* 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 { - pcmk__xml_free(failed); + *answer = obj_root; } - return result; + return pcmk_rc_ok; } int -cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, - xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) +cib__process_query(xmlNode *req, xmlNode **cib, xmlNode **answer) { - const bool force = pcmk__is_set(options, cib_force_diff); - const char *originator = NULL; + uint32_t options = cib_none; - if (req != NULL) { - originator = pcmk__xe_get(req, PCMK__XA_SRC); + 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); } - pcmk__trace("Processing \"%s\" event from %s%s", op, originator, - (force? " (global update)" : "")); + return process_query_section(req, *cib, answer); +} + +static bool +replace_cib_digest_matches(const xmlNode *request) +{ + 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 (*result_cib != existing_cib) { - pcmk__xml_free(*result_cib); + if (expected == NULL) { + // Nothing to verify + return true; } - *result_cib = pcmk__xml_copy(NULL, existing_cib); - return xml_apply_patchset(*result_cib, input, TRUE); + calculated = pcmk__digest_xml(input, true); + matches = pcmk__str_eq(calculated, expected, pcmk__str_none); + + if (matches) { + pcmk__info("Digest matched on replace from %s: %s", peer, expected); + + } else { + pcmk__err("Digest mismatch on replace from %s: %s vs. %s (expected)", + peer, calculated, expected); + } + + free(calculated); + return matches; } -int -cib_process_xpath(const char *op, int options, const char *section, - const xmlNode *req, xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer) +static int +replace_cib(xmlNode *request, xmlNode **cib) { - 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 updates = 0; + int epoch = 0; + int admin_epoch = 0; - pcmk__trace("Processing \"%s\" event", op); + int replace_updates = 0; + int replace_epoch = 0; + int replace_admin_epoch = 0; - if (is_query) { - xpathObj = pcmk__xpath_search(existing_cib->doc, section); - } else { - xpathObj = pcmk__xpath_search((*result_cib)->doc, section); + 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; } - num_results = pcmk__xpath_num_results(xpathObj); - if (num_results == 0) { - if (pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { - pcmk__debug("%s was already removed", section); + if (replace_admin_epoch < admin_epoch) { + reason = PCMK_XA_ADMIN_EPOCH; - } else { - pcmk__debug("%s: %s does not exist", op, section); - rc = -ENXIO; - } - goto done; + } 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 (is_query && (num_results > 1)) { - *answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY); + 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; + } + + *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; +} + +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; + + int num_results = 0; + int rc = pcmk_rc_ok; + xmlXPathObject *xpath_obj = pcmk__xpath_search((*cib)->doc, xpath); + + pcmk__xe_get_flags(request, PCMK__XA_CIB_CALLOPT, &options, cib_none); + + 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; + xmlNode *parent = NULL; xmlChar *path = NULL; - /* If we're deleting multiple nodes, go in reverse document order. - * If we go in forward order and the node set contains both a parent and - * its descendant, then deleting the parent frees the descendant before - * the loop reaches the descendant. This is a use-after-free error. - * - * @COMPAT cib_multiple is only ever used with delete operations. The - * correct order to process multiple nodes for operations other than - * query (forward) and delete (reverse) is less clear but likely should - * be reverse. If we ever replace the CIB public API with libpacemaker - * functions, revisit this. For now, we keep forward order for other - * operations to preserve backward compatibility, even though external - * callers of other ops with cib_multiple might segfault. - * - * 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); - } else { - match = pcmk__xpath_result(xpathObj, i); - } - + match = pcmk__xpath_result(xpath_obj, i); if (match == NULL) { continue; } 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; - } + if (match == *cib) { + rc = replace_cib(request, cib); + break; + } - pcmk__xml_free(match); - if ((options & cib_multiple) == 0) { - break; - } + parent = match->parent; - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) { - uint32_t flags = pcmk__xaf_none; + pcmk__xml_free(match); + pcmk__xml_copy(parent, input); - if (pcmk__is_set(options, cib_score_update)) { - flags |= pcmk__xaf_score_update; - } + if (!pcmk__is_set(options, cib_multiple)) { + break; + } + } - if (pcmk__xe_update_match(match, input, flags) != pcmk_rc_ok) { - rc = -ENXIO; - } else if ((options & cib_multiple) == 0) { - break; - } +done: + xmlXPathFreeObject(xpath_obj); + return rc; +} - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) { - pcmk__xml_copy(match, input); - break; +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); - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) { + int rc = pcmk_rc_ok; + xmlNode *obj_root = NULL; - if (options & cib_no_children) { - xmlNode *shallow = pcmk__xe_create(*answer, - (const char *) match->name); + if ((section != NULL) && pcmk__xe_is(input, PCMK_XE_CIB)) { + input = pcmk_find_cib_element(input, section); + } - pcmk__xe_copy_attrs(shallow, match, pcmk__xaf_none); + if (input == NULL) { + pcmk__err("Cannot find matching section to replace with no input data"); + return EINVAL; + } - if (*answer == NULL) { - *answer = shallow; - } + if (pcmk__xe_is(input, PCMK_XE_CIB)) { + return replace_cib(request, cib); + } - } 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 (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei) + || pcmk__xe_is(input, section)) { - 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); + section = NULL; + } - } else if (*answer) { - pcmk__xml_copy(*answer, match); + obj_root = pcmk_find_cib_element(*cib, section); - } else { - *answer = match; - } + rc = pcmk__xe_replace_match(obj_root, input); + if (rc != pcmk_rc_ok) { + pcmk__trace("No matching object to replace"); + } + + return rc; +} - } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, - pcmk__str_none)) { - xmlNode *parent = match->parent; +int +cib__process_replace(xmlNode *req, xmlNode **cib, xmlNode **answer) +{ + uint32_t options = cib_none; - pcmk__xml_free(match); - pcmk__xml_copy(parent, input); + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none); - if ((options & cib_multiple) == 0) { - break; - } - } + 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 = pcmk__xe_get(*cib, PCMK_XA_VALIDATE_WITH); + const char *new_schema = NULL; + xmlNode *updated = pcmk__xml_copy(NULL, *cib); + + pcmk__xe_get_flags(req, PCMK__XA_CIB_CALLOPT, &options, cib_none); + + rc = pcmk__update_schema(&updated, max_schema, true, + !pcmk__is_set(options, cib_verbose)); + *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(*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; } -done: - xmlXPathFreeObject(xpathObj); return rc; } diff --git a/lib/cib/cib_remote.c b/lib/cib/cib_remote.c index d812857bf6b..6f0b95d89e6 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. * @@ -78,6 +78,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 +86,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); @@ -162,11 +163,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"); @@ -179,9 +175,7 @@ cib_remote_perform_op(cib_t *cib, const char *op, const char *host, /* 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); + xmlNode *tmp = cib__get_calldata(op_reply); if (tmp == NULL) { pcmk__trace("No output in reply to \"%s\" command %d", op, @@ -375,10 +369,10 @@ cib_tls_signon(cib_t *cib, pcmk__remote_t *connection, gboolean event_channel) } if (private->encrypted) { - bool use_cert = pcmk__x509_enabled(); int tls_rc = GNUTLS_E_SUCCESS; - rc = pcmk__init_tls(&tls, false, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_ANON); + // @TODO Implement pre-shared key authentication (see T961) + rc = pcmk__init_tls(&tls, false, false); if (rc != pcmk_rc_ok) { return -1; } diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 043c57e14ec..57fe5ed03a1 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.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. * @@ -39,26 +39,6 @@ cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates) return TRUE; } -gboolean -cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, - int *_admin_epoch, int *_epoch, int *_updates) -{ - int add[] = { 0, 0, 0 }; - int del[] = { 0, 0, 0 }; - - pcmk__xml_patchset_versions(diff, del, add); - - *admin_epoch = add[0]; - *epoch = add[1]; - *updates = add[2]; - - *_admin_epoch = del[0]; - *_epoch = del[1]; - *_updates = del[2]; - - return TRUE; -} - /*! * \internal * \brief Get the XML patchset from a CIB diff notification @@ -120,9 +100,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); @@ -149,22 +129,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; - cib_read_config(options, xml); - value = pcmk__cluster_option(options, PCMK_OPT_ENABLE_ACL); - rc = pcmk__is_true(value); - g_hash_table_destroy(options); + 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)) { + + if (cib_filtered == NULL) { + pcmk__debug("Pre-filtered the entire cib"); + return EACCES; + } + cib = cib_filtered; + pcmk__log_xml_trace(cib, "filtered"); } - pcmk__trace("CIB ACL is %s", (rc? "enabled" : "disabled")); + 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; + } + + 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; } @@ -216,152 +322,235 @@ 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_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); + 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); +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); + 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_ok && scratch == NULL) { - rc = -EINVAL; + if (rc != pcmk_rc_ok) { goto done; + } - } else if(rc == pcmk_ok && xml_acl_denied(scratch)) { - pcmk__trace("ACL rejected part or all of the proposed changes"); - rc = -EACCES; + if (*cib == NULL) { + rc = EINVAL; goto done; + } - } else if (rc != pcmk_ok) { + if (xml_acl_denied(*cib)) { + pcmk__trace("ACL rejected part or all of the proposed changes"); + rc = EACCES; goto done; } @@ -369,194 +558,76 @@ cib_perform_op(cib_t *cib, const char *op, uint32_t call_options, * 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); - rc = pcmk_rc2legacy(rc); 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_err_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_err_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_err_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_ok) && check_schema - && !pcmk__configured_schema_validates(scratch)) { - rc = -pcmk_err_schema_validation; - } - - done: + // 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)) { - *result_cib = scratch; + rc = pcmk_rc_schema_validation; + } - /* @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_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; } @@ -566,7 +637,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); @@ -586,14 +657,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; } /*! @@ -638,11 +704,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)); @@ -655,18 +723,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 @@ -676,12 +742,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); @@ -694,11 +757,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); @@ -750,45 +808,15 @@ cib_native_notify(gpointer data, gpointer user_data) pcmk__trace("Callback invoked..."); } -gboolean -cib_read_config(GHashTable * options, xmlNode * current_cib) -{ - xmlNode *config = NULL; - crm_time_t *now = NULL; - - if (options == NULL || current_cib == NULL) { - return FALSE; - } - - 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); - - return TRUE; -} - 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, @@ -835,7 +863,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; } @@ -843,24 +871,29 @@ cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output, pcmk__log_xml_patchset(level, diff); } - if (input != NULL) { - rc = cib_process_diff(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..f578c2b8e03 100644 --- a/lib/cluster/cluster.c +++ b/lib/cluster/cluster.c @@ -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..e911d9c6a02 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" @@ -466,8 +470,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/mainloop.c b/lib/common/mainloop.c index b38ed21dfb7..da5a1082734 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. * @@ -23,19 +23,6 @@ #include -struct mainloop_child_s { - pid_t pid; - char *desc; - unsigned timerid; - gboolean timeout; - void *privatedata; - - enum mainloop_child_flags flags; - - /* Called when a process dies */ - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode); -}; - struct trigger_s { GSource source; gboolean running; @@ -967,58 +954,55 @@ 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; } void -mainloop_del_fd(mainloop_io_t * client) +mainloop_del_fd(mainloop_io_t *client) { - if (client != NULL) { - pcmk__trace("Removing client %s[%p]", client->name, client); - if (client->source) { - /* Results in mainloop_gio_destroy() being called just - * before the source is removed from mainloop - */ - g_source_remove(client->source); - } + if ((client == NULL) || (client->source == 0)) { + return; } + + pcmk__trace("Removing client %s[%p]", client->name, client); + + // mainloop_gio_destroy() gets called during source removal + g_source_remove(client->source); } static GList *child_list = NULL; @@ -1171,8 +1155,8 @@ child_waitpid(mainloop_child_t *child, int flags) callback_needed = false; } - if (callback_needed && child->callback) { - child->callback(child, child->pid, core, signo, exitcode); + if (callback_needed && child->exit_fn) { + child->exit_fn(child, core, signo, exitcode); } return callback_needed; } @@ -1264,8 +1248,10 @@ mainloop_child_kill(pid_t pid) * completed process. */ void -mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, void *privatedata, enum mainloop_child_flags flags, - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)) +mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, + void *privatedata, + enum mainloop_child_flags flags, + pcmk__mainloop_child_exit_fn_t exit_fn) { static bool need_init = TRUE; mainloop_child_t *child = pcmk__assert_alloc(1, sizeof(mainloop_child_t)); @@ -1274,7 +1260,7 @@ mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, void *pr child->timerid = 0; child->timeout = FALSE; child->privatedata = privatedata; - child->callback = callback; + child->exit_fn = exit_fn; child->flags = flags; child->desc = pcmk__str_copy(desc); @@ -1296,9 +1282,9 @@ mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, void *pr void mainloop_child_add(pid_t pid, int timeout, const char *desc, void *privatedata, - void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)) + pcmk__mainloop_child_exit_fn_t exit_fn) { - mainloop_child_add_with_flags(pid, timeout, desc, privatedata, 0, callback); + mainloop_child_add_with_flags(pid, timeout, desc, privatedata, 0, exit_fn); } static gboolean 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..c006c34d012 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; } /*! @@ -1126,12 +1125,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 +1158,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) { @@ -1182,7 +1184,7 @@ 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)) { + || 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,17 +1192,19 @@ 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); } } diff --git a/lib/common/tests/io/pcmk__get_tmpdir_test.c b/lib/common/tests/io/pcmk__get_tmpdir_test.c index 7a0cf901a80..e9ccf901a9d 100644 --- a/lib/common/tests/io/pcmk__get_tmpdir_test.c +++ b/lib/common/tests/io/pcmk__get_tmpdir_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 the Pacemaker project contributors + * Copyright 2021-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * diff --git a/lib/common/tls.c b/lib/common/tls.c index 0fa7973cde9..f20d5518753 100644 --- a/lib/common/tls.c +++ b/lib/common/tls.c @@ -135,8 +135,35 @@ pcmk__free_tls(pcmk__tls_t *tls) tls = NULL; } +/*! + * \internal + * \brief Initialize a new TLS object + * + * This function initializes \p tls as an environment for TLS connections. This + * is in contrast to \c pcmk__new_tls_session(), which initializes a single + * session within that environment. + * + * X.509 certificates are used if configured via environment variables. + * Otherwise, we fall back to either pre-shared keys (PSK) or anonymous + * authentication, depending on the value of \p have_psk. + * + * \param[out] tls Where to store new TLS object + * \param[in] server Current process is a server if \c true or a client if + * \c false + * \param[in] have_psk If X.509 certificates are not enabled, then use + * \c GNUTLS_CRD_PSK (pre-shared keys) if this is \c true + * or \c GNUTLS_CRD_ANON (anonymous authentication) if + * this is \c false + * + * \return Standard Pacemaker return code + * + * \note CIB remote clients and the CIB manager's remote listener are the only + * things that use anonymous authentication when X.509 is disabled. Task + * T961 is open to implement PSK for those. The only other callers are + * executor clients and listeners, which already use PSK. + */ int -pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_type) +pcmk__init_tls(pcmk__tls_t **tls, bool server, bool have_psk) { int rc = pcmk_rc_ok; @@ -160,10 +187,19 @@ pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_ty } } - (*tls)->cred_type = cred_type; + if (pcmk__x509_enabled()) { + (*tls)->cred_type = GNUTLS_CRD_CERTIFICATE; + + } else if (have_psk) { + (*tls)->cred_type = GNUTLS_CRD_PSK; + + } else { + (*tls)->cred_type = GNUTLS_CRD_ANON; + } + (*tls)->server = server; - if (cred_type == GNUTLS_CRD_ANON) { + if ((*tls)->cred_type == GNUTLS_CRD_ANON) { if (server) { gnutls_anon_allocate_server_credentials(&(*tls)->credentials.anon_s); gnutls_anon_set_server_dh_params((*tls)->credentials.anon_s, @@ -171,7 +207,8 @@ pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_ty } else { gnutls_anon_allocate_client_credentials(&(*tls)->credentials.anon_c); } - } else if (cred_type == GNUTLS_CRD_CERTIFICATE) { + + } else if ((*tls)->cred_type == GNUTLS_CRD_CERTIFICATE) { /* Try the PCMK_ version of each environment variable first, and if * it's not set then try the CIB_ version. */ @@ -209,7 +246,7 @@ pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_ty *tls = NULL; return rc; } - } else if (cred_type == GNUTLS_CRD_PSK) { + } else { // GNUTLS_CRD_PSK if (server) { gnutls_psk_allocate_server_credentials(&(*tls)->credentials.psk_s); gnutls_psk_set_server_dh_params((*tls)->credentials.psk_s, diff --git a/lib/common/utils.c b/lib/common/utils.c index 79b3d43ca0b..2872fb80e31 100644 --- a/lib/common/utils.c +++ b/lib/common/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. * @@ -372,6 +372,11 @@ pcmk__sleep_ms(unsigned int ms) * \param[in] data Data to be passed to fn (can be NULL) * * \return The ID of the event source + * + * \note If \p fn returns \c G_SOURCE_CONTINUE, then it will be called again + * after \p interval_ms. If \p fn returns \c G_SOURCE_REMOVE, then the + * timeout is destroyed and \c fn will not be called again. Note that no + * \c GDestroyNotify function is set, so only the timeout is destroyed. */ guint pcmk__create_timer(guint interval_ms, GSourceFunc fn, gpointer data) 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 82acc186b81..24c9819f0eb 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; @@ -833,8 +801,8 @@ pcmk__xe_replace_match(xmlNode *xml, xmlNode *replace) { /* @COMPAT Some of this behavior (like not matching the tree root, which is * allowed by pcmk__xe_update_match()) is questionable for general use but - * required for backward compatibility by cib_process_replace() and - * cib_process_delete(). Behavior can change at a major version release if + * required for backward compatibility by cib__process_replace() and + * cib__process_delete(). Behavior can change at a major version release if * desired. */ CRM_CHECK((xml != NULL) && (replace != NULL), return EINVAL); @@ -1530,17 +1498,17 @@ pcmk__xe_set_bool(xmlNode *xml, const char *attr, bool value) * \param[in] node XML node to get attribute from * \param[in] name XML attribute to get * - * \return True if the given \p name is an attribute on \p node and has - * the value \c PCMK_VALUE_TRUE, False in all other cases + * \return \c true if the given \p name is an attribute on \p node whose value + * parses to \c true (see \c pcmk__parse_bool()), or \c false otherwise */ bool pcmk__xe_attr_is_true(const xmlNode *node, const char *name) { bool value = false; - int rc; - rc = pcmk__xe_get_bool(node, name, &value); - return rc == pcmk_rc_ok && value == true; + // value remains false on error, so don't check return value + pcmk__xe_get_bool(node, name, &value); + return value; } // Deprecated functions kept only for backward API compatibility diff --git a/lib/lrmd/lrmd_client.c b/lib/lrmd/lrmd_client.c index 54a65c41b13..73f164f9375 100644 --- a/lib/lrmd/lrmd_client.c +++ b/lib/lrmd/lrmd_client.c @@ -1518,7 +1518,6 @@ lrmd_tcp_connect_cb(void *userdata, int rc, int sock) lrmd_t *lrmd = userdata; lrmd_private_t *native = lrmd->lrmd_private; int tls_rc = GNUTLS_E_SUCCESS; - bool use_cert = pcmk__x509_enabled(); native->async_timer = 0; @@ -1536,7 +1535,7 @@ lrmd_tcp_connect_cb(void *userdata, int rc, int sock) native->sock = sock; if (native->tls == NULL) { - rc = pcmk__init_tls(&native->tls, false, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_PSK); + rc = pcmk__init_tls(&native->tls, false, true); if ((rc != pcmk_rc_ok) || (native->tls == NULL)) { lrmd_tls_connection_destroy(lrmd); @@ -1545,7 +1544,7 @@ lrmd_tcp_connect_cb(void *userdata, int rc, int sock) } } - if (!use_cert) { + if (!pcmk__x509_enabled()) { gnutls_datum_t psk_key = { NULL, 0 }; rc = lrmd__init_remote_key(&psk_key); @@ -1621,7 +1620,6 @@ static int lrmd_tls_connect(lrmd_t * lrmd, int *fd) { int rc = pcmk_rc_ok; - bool use_cert = pcmk__x509_enabled(); lrmd_private_t *native = lrmd->lrmd_private; native->sock = -1; @@ -1636,7 +1634,7 @@ lrmd_tls_connect(lrmd_t * lrmd, int *fd) } if (native->tls == NULL) { - rc = pcmk__init_tls(&native->tls, false, use_cert ? GNUTLS_CRD_CERTIFICATE : GNUTLS_CRD_PSK); + rc = pcmk__init_tls(&native->tls, false, true); if (rc != pcmk_rc_ok) { lrmd_tls_connection_destroy(lrmd); @@ -1644,7 +1642,7 @@ lrmd_tls_connect(lrmd_t * lrmd, int *fd) } } - if (!use_cert) { + if (!pcmk__x509_enabled()) { gnutls_datum_t psk_key = { NULL, 0 }; rc = lrmd__init_remote_key(&psk_key); 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 7bfc751d30c..29b22d2e6cf 100644 --- a/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c +++ b/lib/pacemaker/tests/pcmk_resource/pcmk_resource_delete_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -95,9 +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); @@ -130,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/pacemaker/tests/pcmk_ticket/pcmk_ticket_delete_test.c b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_delete_test.c index 76e0839c0bc..c25ac96b109 100644 --- a/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_delete_test.c +++ b/lib/pacemaker/tests/pcmk_ticket/pcmk_ticket_delete_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * 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/lib/services/services_linux.c b/lib/services/services_linux.c index 2c1218a96d1..28057b002e3 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -705,19 +705,17 @@ parse_exit_reason_from_stderr(svc_action_t *op) * \brief Process the completion of an asynchronous child process * * \param[in,out] p Child process that completed - * \param[in] pid Process ID of child * \param[in] core (Unused) * \param[in] signo Signal that interrupted child, if any * \param[in] exitcode Exit status of child process */ static void -async_action_complete(mainloop_child_t *p, pid_t pid, int core, int signo, - int exitcode) +async_action_complete(mainloop_child_t *p, int core, int signo, int exitcode) { svc_action_t *op = mainloop_child_userdata(p); mainloop_clear_child_userdata(p); - CRM_CHECK(op->pid == pid, + CRM_CHECK(op->pid == p->pid, services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR, "Bug in mainloop handling"); return); diff --git a/tools/cibadmin.c b/tools/cibadmin.c index fc20bdea950..0c769aa91ed 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. * @@ -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_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; } diff --git a/tools/crm_verify.c b/tools/crm_verify.c index 6da34e0259d..145fb1d0220 100644 --- a/tools/crm_verify.c +++ b/tools/crm_verify.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * diff --git a/tools/crmadmin.c b/tools/crmadmin.c index 281705327d2..62b8aadcdfc 100644 --- a/tools/crmadmin.c +++ b/tools/crmadmin.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2024 the Pacemaker project contributors + * Copyright 2004-2025 the Pacemaker project contributors * * The version control history for this file may have further details. *