Refactor backend return code#8862
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors and simplifies error handling across SSSD providers and responders by removing the redundant dp_error (Data Provider error) parameter and unifying error reporting under standard SSSD error codes. However, this refactoring introduces a critical bug: by discarding the sdap_ret status (which contains ENOENT when a user or group is not found), lookup operations incorrectly return EOK instead of ENOENT. This breaks critical AD failover and fallback mechanisms, as the system can no longer detect missing entries to trigger alternative connection attempts.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
b171fc9 to
830cebb
Compare
698bdc1 to
0a4d279
Compare
Assisted by: Claude code (Sonnet 4.5)
Replace LDAP connection related failure error codes EIO/ERR_NETWORK_IO/ETIMEDOUT with ERR_SERVER_FAILURE
Replace LDAP connection related failure error codes EIO/ERR_NETWORK_IO/ETIMEDOUT with ERR_SERVER_FAILURE
Replace LDAP connection related failure error codes EIO/ERR_NETWORK_IO/ETIMEDOUT with ERR_SERVER_FAILURE
The data provider handler methods now return a single output argument. Remove 'dp_error/dp_err' and 'error_message' usage across provider code. The getAccountDomain method still needs to return 'domain_name' string. Assisted by: Claude code (Sonnet 4.5)
0a4d279 to
a122699
Compare
Object lookup sdap `_done()` functions call tevent_req_done() for EOK and ENOENT, removal of 'sdap_ret/sdap_err' causes us to lose ENOENT which breaks ENOENT checks done by some callers. Previously actual sdap return code(sdap_ret) was stored. Callers still receive tevent error code from tevent_req_error(), but callers need some way to check if the sdap lookup function results in ENOENT.
a122699 to
56c8d38
Compare
Uhm, this looks weird and breaks the purpose of having one return code. Why don't you call tevent_req_error(req, ENOENT)? This looks like the cleanest thing to do. |
Hi Pavel, thanks for taking a look at this PR. I tried going with the approach you mention here first, but I found it to be much more invasive and require a lot more changes in the provider to adapt to this behavior change. In my testing I see much of the provider code was not written with ENOENT as tevent error in mind, and calling functions expect to see 'tevent_req_done' when the connection is successful and the ldap search does not find the object. Of course I am okay going with your approach, in general I was attempting to lower the amount of invasive changes if possible., however this PR will be invasive in the end regardless. |
|
I think we should go for it and adapt the code to correctly handle ENOENT and one return code. But we can continue with this pull request as is so we move forward and then implement this refactoring on top of it. I will continue with review tomorrow. |
| bool sdap_enoent = false; | ||
| struct sdap_id_conn_ctx *user_conn; | ||
|
|
||
| ret = groups_get_recv(subreq, &state->dp_error, &state->sdap_ret); | ||
| ret = groups_get_recv(subreq, &sdap_enoent); | ||
| talloc_zfree(subreq); | ||
|
|
||
| if (ret != EOK) { /* Fatal error while looking up group */ | ||
| tevent_req_error(req, ret); | ||
| return; | ||
| } | ||
| state->sdap_enoent = sdap_enoent; | ||
|
|
||
| if (state->sdap_ret == EOK) { /* Matching group found */ | ||
| if (ret == EOK) { /* Matching group found */ | ||
| tevent_req_done(req); | ||
| return; | ||
| } else if (state->sdap_ret != ENOENT) { | ||
| tevent_req_error(req, EIO); | ||
| } else if (ret != EOK && !sdap_enoent) { /* Fatal error while looking up group */ | ||
| tevent_req_error(req, ret); | ||
| return; | ||
| } |
There was a problem hiding this comment.
This nicely demonstrates how multiple return codes are bad design practice.
The current code can return EOK and sdap_enoent = true from groups_get_recv, the code behind the if statement that handles ENOENT will not execute.
| switch (retval) { | ||
| case EIO: | ||
| case ETIMEDOUT: | ||
| /* this currently the only possible communication error after connection is established */ | ||
| communication_error = true; | ||
| break; | ||
|
|
||
| default: | ||
| communication_error = false; | ||
| break; | ||
| } |
There was a problem hiding this comment.
EIO and ETIMEDOUT were removed and we should not get them anymore. But ERR_SERVER_FAILURE must be handled here.
| state->conn = conn; | ||
| state->dp_error = DP_ERR_FATAL; | ||
| state->noexist_delete = noexist_delete; | ||
| state->sdap_enoent = true; |
There was a problem hiding this comment.
Should be set to false, as you later set it to true on ENOENT.
| "Trying LDAP search while not connected.\n"); | ||
| ret = ERR_NETWORK_IO; | ||
| ret = ERR_SERVER_FAILURE; | ||
| goto fail; |
There was a problem hiding this comment.
ERR_SERVER_FAILURE is switched for ERR_AUTH_FAILED in "fail".
| DEBUG(SSSDBG_CRIT_FAILURE, | ||
| "Trying LDAP rebind while not connected.\n"); | ||
| return ERR_NETWORK_IO; | ||
| return ERR_SERVER_FAILURE; |
There was a problem hiding this comment.
This is ldap callback, expecting ldap return codes. We should return LDAP_SERVER_DOWN or perhaps LDAP_LOCAL_ERROR.
| <method name="sudoHandler"> | ||
| <annotation name="codegen.CustomInputHandler" value="true" /> | ||
| <arg name="dp_error" type="q" direction="out" /> | ||
| <arg name="error" type="u" direction="out" /> | ||
| <arg name="error_message" type="s" direction="out" /> | ||
| </method> |
There was a problem hiding this comment.
This still returns two error codes: dbus and operation. Let's just return the dbus one.
Instead of finishing with a valid reply (dbus returns success, sends reply that contains the error code), just fail with dbus error (dbus returns error).
| msg = reply->message; | ||
| } | ||
|
|
||
| void dp_req_reply_std_with_msg(const char *request_name, |
There was a problem hiding this comment.
We don't need the message in the dbus reply, we want to carry all information in the return code. Potential message can be logged in the provider.
| * Data provider error code is returned: | ||
| * ERR_OK - connection established | ||
| * ERR_OFFLINE - backend is offline, operation result is set EAGAIN | ||
| * ERR_FATAL - operation failed |
| * DP_ERR_FATAL - operation failed | ||
| * In data provider error code is returned: | ||
| * ERR_OK - connection established | ||
| * ERR_OFFLINE - backend is offline, operation result is set EAGAIN |
| @@ -230,7 +230,6 @@ struct ad_handle_pac_initgr_state { | |||
| struct dp_id_data *ar; | |||
| const char *err; | |||
| int dp_error; | |||
|
There is /* FIXME: do we need some special handling of ENOENT */ |
Summary of changes (written by me, not AI):
Return only a single error code through sssd.dataprovider interfaces
Remove
sdap_ret/sdap_errfrom codebaseENOENTafter removingsdap_retwith commitsdap: Add output variable to store sdap result of ENOENTsdap_id_op_{connect}_done returns ERR_OFFLINE when offline instead of EAGAIN
sdap_id_op_done() now returns ERR_SERVER_FAILURE when previously setting DP_ERR_FATAL
sdap_id_op_done() returns EAGAIN when advising retry and dp_error is no longer, callers performing retry logic with this code:
are converted to
LDAP connection failure error codes are converted from EIO/ERR_NETWORK_IO/ETIMEDOUT to ERR_SERVER_FAILURE. Callers checking for ERR_NETWORK_IO/ETIMEDOUT now check for ERR_SERVER_FAILURE
Switch checks of if (dp_error == DP_ERR_OFFLINE) to if (ret == ERR_OFFLINE)