Skip to content

Install callbacks#2910

Merged
jreidinger merged 9 commits intoapi-v2from
install_callbacks
Nov 26, 2025
Merged

Install callbacks#2910
jreidinger merged 9 commits intoapi-v2from
install_callbacks

Conversation

@jreidinger
Copy link
Copy Markdown
Contributor

@jreidinger jreidinger commented Nov 25, 2025

Problem

The last missing piece for libzypp callbacks are the ones used for package installation.

Previously in this series: #2901

Solution

Implement them. Also improve previous ones with override keyword in C++ to ensure we use proper method to avoid "overload" instead of "override".

Testing

No testing as it is postponed to time when we have working install phase with new stack.

TODO: test this as part of this PBI:

@jreidinger jreidinger changed the base branch from master to api-v2 November 25, 2025 13:55
Copy link
Copy Markdown
Contributor

@imobachgs imobachgs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C/C++ part is a little bit of dark magic for me :-) The Rust code looks fine, perhaps @mvidner could have a closer look to the C/C++ one.

impl install::Callback for Install {
fn package_start(&self, package_name: String) {
tracing::info!("Installing package {}", package_name);
// TODO: report progress
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still not sure how to properly report progress when there is two callbacks struct where the first one basically mainly download packages and here it install packages. It also need a bit of additional support from libzypp part as yast precompute for progress amount of packages to install, so it can create nicer sub-progress.

.with_data(&[("filename", file.as_str())]);
let result = Handle::current()
.block_on(async move { ask_question(&self.questions, question).await });
let result = ask_software_question(&self.questions, question);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 that's what I meant with this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to commit it in previous PR. It was already there :)

}

inline zypp::target::rpm::InstallResolvableReport::Action
convert_action(PROBLEM_RESPONSE response) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming: I would expect convert_response since that is the argument type.

(Actually I would like to see Rust-like function names, like to_foo, as_foo for standalone functions or from_foo for methods)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, using such method names can be really helpful. So I will change it

const char *description, void *user_data);
/**
* @brief Callback invoked when the installation of a package finishes.
* @note We could add a finish message after package install, but YaST does not.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean, YaST does nothing special on this callback and just continues with the next package? If it works, then fine, we can keep doing nothing in Agama too

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more tricky as YaST for backward compatibility reason use identical callback for Problem and Finish, so it do a bit of magic there. But part specific to finish is really quite trivial logging.

return callback(data, user_data);
} else {
return zypp::ProgressReport::progress(task);
return true;
Copy link
Copy Markdown
Contributor

@mvidner mvidner Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the new callbacks (PatchScriptReport, InstallResolvableReport) we do call the base class method but here we avoid it. Why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, it should also call the original one which will just call that true. Good catch, I overlook this change.


struct PatchScriptReport
: public zypp::callback::ReceiveReport<zypp::target::PatchScriptReport> {
struct InstallCallbacks *callbacks;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I've missed this: we need at least a lifetime comment, better a smart pointer that provides some safety.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, smart pointer won't help you when rust deallocate it. Comment would be OK. Basically lifetime is for commit phase where we set and unset it ( it is same for all callbacks. I want to have it living only for limited time needed for operation ).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// lifetime of pointer is quite short. Only during operation which takes
// callbacks as parameter.

"Lifetime of pointer is quite short" is a nice philosophical take 🤣
but I wanted something more specific:
Why is PatchScriptReport::callbacks valid at all times? It is trivial to see that PatchScriptReport has a static instance so the danger of the pointer outliving its target is real.

IIUC, the answer is: callbacks is only assigned via set_callbacks, which is in turn called only from {set,unset}_zypp_foo_callbacks, in this fashion which makes sure that foo_callbacks are only borrowed by FooReport for the duration of the method.

some_zypp_method(foo_callbacks) {
  set_zypp_foo_callbacks(foo_callbacks);
  zypp::some_method_using_the_callbacks();
  unset_zypp_foo_callbacks();
}

As you can see I had to do quite some explaining. It would be nice to design the API in such a way where it would be more obvious and ideally checked by the C++ compiler.


inline DownloadResolvableError
from_dre_error(zypp::repo::DownloadResolvableReport::Error error) {
from_libzypp_error(zypp::repo::DownloadResolvableReport::Error error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I'm nitpicking, but:

IMHO something named from_foo must be a constructor, but here we return DownloadResolvableError in a method of DownloadResolvableReport.

So I would use into_error or into_dre_error here. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is fine for me. Probably I will use simple into_error because I like how they are consistent across different callbacks

Copy link
Copy Markdown
Contributor

@mvidner mvidner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the C++ part looks good now

@jreidinger
Copy link
Copy Markdown
Contributor Author

@mvidner what about having this doc at top of file to make it clear lifetime at single place instead of all pointers?

/**
 * @file callbacks.cxx
 * @brief Implements C-style wrappers for libzypp callbacks.
 *
 * This file defines a mechanism to expose libzypp's C++ callback system
 * to C-compatible interfaces, primarily for use with FFI (Foreign Function Interface)
 * bindings, such as those used in Rust.
 *
 * Overall Approach:
 * -----------------
 * 1.  **Custom `ReceiveReport` Implementations:** For each type of libzypp report
 *     (e.g., `ProgressReport`, `DownloadProgressReport`, `KeyRingReport`), a custom
 *     C++ class (e.g., `ProgressReceive`, `DownloadProgressReceive`) is defined.
 *     These classes inherit from `zypp::callback::ReceiveReport<T>` where `T` is
 *     the specific libzypp report type.
 * 2.  **Static Instances:** A single, static instance of each custom `ReceiveReport`
 *     implementation is declared (e.g., `static ProgressReceive progress_receive;`).
 *     These static instances act as the central point for registering and
 *     deregistering with libzypp's global callback system.
 * 3.  **C-style Callback Structs:** Each custom `ReceiveReport` instance holds a
 *     pointer to a C-style `struct` (e.g., `struct DownloadProgressCallbacks *callbacks;`).
 *     These structs are defined in `callbacks.h` and contain function pointers
 *     for the actual C-compatible callback functions, along with `void *user_data`
 *     for passing context back to the caller.
 * 4.  **Registration (`set_zypp_*_callbacks`):** Functions like
 *     `set_zypp_progress_callback` or `set_zypp_download_callbacks` are provided
 *     to allow external C/FFI code to register its callbacks. These functions:
 *     -   Store the provided C-style callback struct pointer in the static
 *         `ReceiveReport` instance.
 *     -   Call `connect()` on the static `ReceiveReport` instance, which registers
 *         it with the appropriate libzypp global reporter.
 * 5.  **Deregistration (`unset_zypp_*_callbacks`):** Corresponding `unset_zypp_*_callbacks`
 *     functions are provided to deregister callbacks. These functions:
 *     -   Set the internal C-style callback struct pointer to `NULL` to prevent
 *         dangling pointer issues if the caller's struct is deallocated.
 *     -   Call `disconnect()` on the static `ReceiveReport` instance, removing
 *         it from libzypp's global reporter.
 *
 * Lifetime of Callback Structs:
 * -----------------------------
 * The `struct` containing the C-style callback function pointers and `user_data`
 * (e.g., `DownloadProgressCallbacks`) is passed by pointer to the `set_zypp_*_callbacks`
 * functions. The static `ReceiveReport` instances *store this pointer*.
 *
 * **It is critical that the caller of `set_zypp_*_callbacks` ensures the lifetime
 * of the provided callback struct.** The struct must remain valid and in scope
 * for the entire duration that the callbacks are registered with libzypp.
 * If the struct is deallocated or goes out of scope while libzypp still holds
 * a pointer to it (i.e., before `unset_zypp_*_callbacks` is called), any subsequent
 * callback invocation by libzypp will result in undefined behavior, likely a crash
 * due to use-after-free.
 *
 * Therefore, callers must always pair `set_zypp_*_callbacks` with a corresponding
 * `unset_zypp_*_callbacks` call when the callback struct is no longer valid or needed.
 */

@jreidinger jreidinger merged commit 9da092c into api-v2 Nov 26, 2025
15 checks passed
@jreidinger jreidinger deleted the install_callbacks branch November 26, 2025 14:04
imobachgs added a commit that referenced this pull request Jan 10, 2026
Merge the new HTTP API. Each PR has been already reviewed, so it should
be safe to merge it.

* #1829
* #2508
* #2772
* #2826
* #2848
* #2860
* #2863
* #2866
* #2867
* #2869
* #2870
* #2871
* #2872
* #2873
* #2874
* #2875
* #2876
* #2877
* #2880
* #2881
* #2882
* #2884
* #2885
* #2886
* #2891
* #2892
* #2893
* #2894
* #2895
* #2896
* #2897
* #2898
* #2899
* #2900
* #2901
* #2902
* #2903
* #2904
* #2908
* #2909
* #2910
* #2912
* #2913
* #2914
* #2915
* #2916
* #2917
* #2918
* #2920
* #2921
* #2923
* #2924
* #2926
* #2928
* #2929
* #2930
* #2933
* #2934
* #2935
* #2936
* #2938
* #2939
* #2942
* #2943
* #2944
* #2945
* #2946
* #2947
* #2948
* #2950
* #2951
* #2952
* #2954
* #2955
* #2956
* #2957
* #2958
* #2959
* #2960
* #2961
* #2963
* #2964
* #2965
* #2967
* #2968
* #2969
* #2970
* #2971
* #2972
* #2974
* #2975
* #2977
* #2978
* #2980
* #2982
* #2983
* #2984
* #2988
* #2989
* #2991
* #2992
* #2993
* #2994
* #2995
* #2996
* #2997
* #2999
@imobachgs imobachgs mentioned this pull request Mar 17, 2026
imobachgs added a commit that referenced this pull request Mar 17, 2026
Prepare to release version 19.

* #1829
* #2508
* #2772
* #2818
* #2826
* #2848
* #2860
* #2863
* #2864
* #2866
* #2867
* #2869
* #2870
* #2871
* #2872
* #2873
* #2874
* #2875
* #2876
* #2877
* #2880
* #2881
* #2882
* #2884
* #2885
* #2886
* #2891
* #2892
* #2893
* #2894
* #2895
* #2896
* #2897
* #2898
* #2899
* #2900
* #2901
* #2902
* #2903
* #2904
* #2908
* #2909
* #2910
* #2912
* #2913
* #2914
* #2915
* #2916
* #2917
* #2918
* #2920
* #2921
* #2923
* #2924
* #2926
* #2928
* #2929
* #2930
* #2933
* #2934
* #2935
* #2936
* #2937
* #2938
* #2939
* #2942
* #2943
* #2944
* #2945
* #2946
* #2947
* #2948
* #2949
* #2950
* #2951
* #2952
* #2954
* #2955
* #2956
* #2957
* #2958
* #2959
* #2960
* #2961
* #2963
* #2964
* #2965
* #2967
* #2968
* #2969
* #2970
* #2971
* #2972
* #2974
* #2975
* #2977
* #2978
* #2980
* #2981
* #2982
* #2983
* #2984
* #2988
* #2989
* #2990
* #2991
* #2992
* #2993
* #2994
* #2995
* #2996
* #2997
* #2998
* #2999
* #3000
* #3001
* #3002
* #3004
* #3005
* #3006
* #3007
* #3008
* #3009
* #3011
* #3012
* #3013
* #3014
* #3015
* #3016
* #3018
* #3019
* #3020
* #3021
* #3022
* #3023
* #3024
* #3025
* #3026
* #3027
* #3028
* #3029
* #3030
* #3031
* #3033
* #3034
* #3035
* #3036
* #3037
* #3039
* #3040
* #3041
* #3042
* #3043
* #3044
* #3045
* #3046
* #3047
* #3048
* #3049
* #3050
* #3051
* #3052
* #3053
* #3054
* #3055
* #3056
* #3057
* #3058
* #3060
* #3061
* #3062
* #3063
* #3064
* #3065
* #3066
* #3067
* #3068
* #3069
* #3070
* #3071
* #3072
* #3073
* #3074
* #3075
* #3076
* #3077
* #3078
* #3079
* #3086
* #3087
* #3088
* #3089
* #3090
* #3091
* #3092
* #3093
* #3094
* #3095
* #3096
* #3097
* #3098
* #3099
* #3100
* #3101
* #3102
* #3103
* #3104
* #3105
* #3106
* #3107
* #3108
* #3109
* #3110
* #3112
* #3113
* #3114
* #3115
* #3116
* #3117
* #3118
* #3119
* #3120
* #3122
* #3123
* #3124
* #3127
* #3128
* #3129
* #3130
* #3131
* #3133
* #3134
* #3135
* #3136
* #3137
* #3138
* #3139
* #3140
* #3141
* #3142
* #3143
* #3144
* #3145
* #3146
* #3147
* #3148
* #3149
* #3150
* #3151
* #3152
* #3153
* #3154
* #3155
* #3157
* #3158
* #3159
* #3160
* #3161
* #3162
* #3163
* #3164
* #3165
* #3166
* #3167
* #3168
* #3169
* #3170
* #3174
* #3175
* #3176
* #3177
* #3178
* #3179
* #3181
* #3182
* #3184
* #3185
* #3186
* #3188
* #3189
* #3190
* #3191
* #3192
* #3194
* #3195
* #3196
* #3197
* #3198
* #3199
* #3200
* #3201
* #3202
* #3203
* #3205
* #3206
* #3208
* #3209
* #3210
* #3213
* #3214
* #3215
* #3216
* #3217
* #3218
* #3219
* #3220
* #3222
* #3223
* #3224
* #3225
* #3226
* #3227
* #3228
* #3229
* #3230
* #3231
* #3232
* #3233
* #3234
* #3235
* #3236
* #3237
* #3238
* #3239
* #3240
* #3241
* #3242
* #3243
* #3244
* #3246
* #3247
* #3248
* #3250
* #3251
* #3252
* #3253
* #3254
* #3255
* #3256
* #3257
* #3258
* #3259
* #3260
* #3261
* #3262
* #3263
* #3265
* #3266
* #3267
* #3268
* #3269
* #3270
* #3271
* #3272
* #3273
* #3274
* #3275
* #3276
* #3277
* #3278
* #3279
* #3280
* #3281
* #3282
* #3283
* #3284
* #3285
* #3286
* #3287
* #3288
* #3289
* #3290
* #3291
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants