From: David Woodhouse <dwmw@amazon.co.uk>
Both i386 and x86_64 now copy the relocate_kernel function into the control
page and execute it from there, using an open-coded function pointer.
Use a typedef for it instead.
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
arch/x86/include/asm/kexec.h | 26 +++++++++++++-------------
arch/x86/kernel/machine_kexec_32.c | 7 +------
arch/x86/kernel/machine_kexec_64.c | 6 +-----
3 files changed, 15 insertions(+), 24 deletions(-)
diff --git a/arch/x86/include/asm/kexec.h b/arch/x86/include/asm/kexec.h
index 48e4f44f794f..8ad187462b68 100644
--- a/arch/x86/include/asm/kexec.h
+++ b/arch/x86/include/asm/kexec.h
@@ -111,21 +111,21 @@ static inline void crash_setup_regs(struct pt_regs *newregs,
}
#ifdef CONFIG_X86_32
-asmlinkage unsigned long
-relocate_kernel(unsigned long indirection_page,
- unsigned long control_page,
- unsigned long start_address,
- unsigned int has_pae,
- unsigned int preserve_context);
+typedef asmlinkage unsigned long
+relocate_kernel_fn(unsigned long indirection_page,
+ unsigned long control_page,
+ unsigned long start_address,
+ unsigned int has_pae,
+ unsigned int preserve_context);
#else
-unsigned long
-relocate_kernel(unsigned long indirection_page,
- unsigned long pa_control_page,
- unsigned long start_address,
- unsigned int preserve_context,
- unsigned int host_mem_enc_active);
+typedef unsigned long
+relocate_kernel_fn(unsigned long indirection_page,
+ unsigned long pa_control_page,
+ unsigned long start_address,
+ unsigned int preserve_context,
+ unsigned int host_mem_enc_active);
#endif
-
+extern relocate_kernel_fn relocate_kernel;
#define ARCH_HAS_KIMAGE_ARCH
#ifdef CONFIG_X86_32
diff --git a/arch/x86/kernel/machine_kexec_32.c b/arch/x86/kernel/machine_kexec_32.c
index 1b373d79cedc..80265162aeff 100644
--- a/arch/x86/kernel/machine_kexec_32.c
+++ b/arch/x86/kernel/machine_kexec_32.c
@@ -160,15 +160,10 @@ void machine_kexec_cleanup(struct kimage *image)
*/
void machine_kexec(struct kimage *image)
{
+ relocate_kernel_fn *relocate_kernel_ptr;
unsigned long page_list[PAGES_NR];
void *control_page;
int save_ftrace_enabled;
- asmlinkage unsigned long
- (*relocate_kernel_ptr)(unsigned long indirection_page,
- unsigned long control_page,
- unsigned long start_address,
- unsigned int has_pae,
- unsigned int preserve_context);
#ifdef CONFIG_KEXEC_JUMP
if (image->preserve_context)
diff --git a/arch/x86/kernel/machine_kexec_64.c b/arch/x86/kernel/machine_kexec_64.c
index 1440f792a86d..dd75a51463a2 100644
--- a/arch/x86/kernel/machine_kexec_64.c
+++ b/arch/x86/kernel/machine_kexec_64.c
@@ -344,12 +344,8 @@ void machine_kexec_cleanup(struct kimage *image)
*/
void __nocfi machine_kexec(struct kimage *image)
{
- unsigned long (*relocate_kernel_ptr)(unsigned long indirection_page,
- unsigned long pa_control_page,
- unsigned long start_address,
- unsigned int preserve_context,
- unsigned int host_mem_enc_active);
unsigned long reloc_start = (unsigned long)__relocate_kernel_start;
+ relocate_kernel_fn *relocate_kernel_ptr;
unsigned int host_mem_enc_active;
int save_ftrace_enabled;
void *control_page;
--
2.47.0
On Tue, 17 Dec 2024 at 00:37, David Woodhouse <dwmw2@infradead.org> wrote:
>
> From: David Woodhouse <dwmw@amazon.co.uk>
>
> Both i386 and x86_64 now copy the relocate_kernel function into the control
> page and execute it from there, using an open-coded function pointer.
>
> Use a typedef for it instead.
>
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> ---
> arch/x86/include/asm/kexec.h | 26 +++++++++++++-------------
> arch/x86/kernel/machine_kexec_32.c | 7 +------
> arch/x86/kernel/machine_kexec_64.c | 6 +-----
> 3 files changed, 15 insertions(+), 24 deletions(-)
>
> diff --git a/arch/x86/include/asm/kexec.h b/arch/x86/include/asm/kexec.h
> index 48e4f44f794f..8ad187462b68 100644
> --- a/arch/x86/include/asm/kexec.h
> +++ b/arch/x86/include/asm/kexec.h
> @@ -111,21 +111,21 @@ static inline void crash_setup_regs(struct pt_regs *newregs,
> }
>
> #ifdef CONFIG_X86_32
> -asmlinkage unsigned long
> -relocate_kernel(unsigned long indirection_page,
> - unsigned long control_page,
> - unsigned long start_address,
> - unsigned int has_pae,
> - unsigned int preserve_context);
> +typedef asmlinkage unsigned long
> +relocate_kernel_fn(unsigned long indirection_page,
> + unsigned long control_page,
> + unsigned long start_address,
> + unsigned int has_pae,
> + unsigned int preserve_context);
linkage is not part of the type. 'asmlinkage' is #define'd to the
empty string today, so it doesn't matter, but better to omit it here.
> #else
> -unsigned long
> -relocate_kernel(unsigned long indirection_page,
> - unsigned long pa_control_page,
> - unsigned long start_address,
> - unsigned int preserve_context,
> - unsigned int host_mem_enc_active);
> +typedef unsigned long
> +relocate_kernel_fn(unsigned long indirection_page,
> + unsigned long pa_control_page,
> + unsigned long start_address,
> + unsigned int preserve_context,
> + unsigned int host_mem_enc_active);
> #endif
> -
> +extern relocate_kernel_fn relocate_kernel;
> #define ARCH_HAS_KIMAGE_ARCH
>
> #ifdef CONFIG_X86_32
> diff --git a/arch/x86/kernel/machine_kexec_32.c b/arch/x86/kernel/machine_kexec_32.c
> index 1b373d79cedc..80265162aeff 100644
> --- a/arch/x86/kernel/machine_kexec_32.c
> +++ b/arch/x86/kernel/machine_kexec_32.c
> @@ -160,15 +160,10 @@ void machine_kexec_cleanup(struct kimage *image)
> */
> void machine_kexec(struct kimage *image)
> {
> + relocate_kernel_fn *relocate_kernel_ptr;
> unsigned long page_list[PAGES_NR];
> void *control_page;
> int save_ftrace_enabled;
> - asmlinkage unsigned long
> - (*relocate_kernel_ptr)(unsigned long indirection_page,
> - unsigned long control_page,
> - unsigned long start_address,
> - unsigned int has_pae,
> - unsigned int preserve_context);
>
> #ifdef CONFIG_KEXEC_JUMP
> if (image->preserve_context)
> diff --git a/arch/x86/kernel/machine_kexec_64.c b/arch/x86/kernel/machine_kexec_64.c
> index 1440f792a86d..dd75a51463a2 100644
> --- a/arch/x86/kernel/machine_kexec_64.c
> +++ b/arch/x86/kernel/machine_kexec_64.c
> @@ -344,12 +344,8 @@ void machine_kexec_cleanup(struct kimage *image)
> */
> void __nocfi machine_kexec(struct kimage *image)
> {
> - unsigned long (*relocate_kernel_ptr)(unsigned long indirection_page,
> - unsigned long pa_control_page,
> - unsigned long start_address,
> - unsigned int preserve_context,
> - unsigned int host_mem_enc_active);
> unsigned long reloc_start = (unsigned long)__relocate_kernel_start;
> + relocate_kernel_fn *relocate_kernel_ptr;
> unsigned int host_mem_enc_active;
> int save_ftrace_enabled;
> void *control_page;
> --
> 2.47.0
>
On 17 December 2024 09:49:04 CET, Ard Biesheuvel <ardb@kernel.org> wrote: >On Tue, 17 Dec 2024 at 00:37, David Woodhouse <dwmw2@infradead.org> wrote: >> >> From: David Woodhouse <dwmw@amazon.co.uk> >> >> Both i386 and x86_64 now copy the relocate_kernel function into the control >> page and execute it from there, using an open-coded function pointer. >> >> Use a typedef for it instead. >> >> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk> >> --- >> arch/x86/include/asm/kexec.h | 26 +++++++++++++------------- >> arch/x86/kernel/machine_kexec_32.c | 7 +------ >> arch/x86/kernel/machine_kexec_64.c | 6 +----- >> 3 files changed, 15 insertions(+), 24 deletions(-) >> >> diff --git a/arch/x86/include/asm/kexec.h b/arch/x86/include/asm/kexec.h >> index 48e4f44f794f..8ad187462b68 100644 >> --- a/arch/x86/include/asm/kexec.h >> +++ b/arch/x86/include/asm/kexec.h >> @@ -111,21 +111,21 @@ static inline void crash_setup_regs(struct pt_regs *newregs, >> } >> >> #ifdef CONFIG_X86_32 >> -asmlinkage unsigned long >> -relocate_kernel(unsigned long indirection_page, >> - unsigned long control_page, >> - unsigned long start_address, >> - unsigned int has_pae, >> - unsigned int preserve_context); >> +typedef asmlinkage unsigned long >> +relocate_kernel_fn(unsigned long indirection_page, >> + unsigned long control_page, >> + unsigned long start_address, >> + unsigned int has_pae, >> + unsigned int preserve_context); > >linkage is not part of the type. 'asmlinkage' is #define'd to the >empty string today, so it doesn't matter, but better to omit it here. This is the i386 version. I thought ut was something like regparm(3) there? And... WTF? How is the calling convention not part of the fundamental type of the function? If I have a pointer to such a function, using this typedef to ensure we all share the same prototype, are you telling me all the users of the typedef have to remember to tag that part on for themselves?
On Tue, 17 Dec 2024 at 10:21, David Woodhouse <dwmw2@infradead.org> wrote: > > On 17 December 2024 09:49:04 CET, Ard Biesheuvel <ardb@kernel.org> wrote: > >On Tue, 17 Dec 2024 at 00:37, David Woodhouse <dwmw2@infradead.org> wrote: > >> > >> From: David Woodhouse <dwmw@amazon.co.uk> > >> > >> Both i386 and x86_64 now copy the relocate_kernel function into the control > >> page and execute it from there, using an open-coded function pointer. > >> > >> Use a typedef for it instead. > >> > >> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk> > >> --- > >> arch/x86/include/asm/kexec.h | 26 +++++++++++++------------- > >> arch/x86/kernel/machine_kexec_32.c | 7 +------ > >> arch/x86/kernel/machine_kexec_64.c | 6 +----- > >> 3 files changed, 15 insertions(+), 24 deletions(-) > >> > >> diff --git a/arch/x86/include/asm/kexec.h b/arch/x86/include/asm/kexec.h > >> index 48e4f44f794f..8ad187462b68 100644 > >> --- a/arch/x86/include/asm/kexec.h > >> +++ b/arch/x86/include/asm/kexec.h > >> @@ -111,21 +111,21 @@ static inline void crash_setup_regs(struct pt_regs *newregs, > >> } > >> > >> #ifdef CONFIG_X86_32 > >> -asmlinkage unsigned long > >> -relocate_kernel(unsigned long indirection_page, > >> - unsigned long control_page, > >> - unsigned long start_address, > >> - unsigned int has_pae, > >> - unsigned int preserve_context); > >> +typedef asmlinkage unsigned long > >> +relocate_kernel_fn(unsigned long indirection_page, > >> + unsigned long control_page, > >> + unsigned long start_address, > >> + unsigned int has_pae, > >> + unsigned int preserve_context); > > > >linkage is not part of the type. 'asmlinkage' is #define'd to the > >empty string today, so it doesn't matter, but better to omit it here. > > This is the i386 version. I thought ut was something like regparm(3) there? > > And... WTF? How is the calling convention not part of the fundamental type of the function? If I have a pointer to such a function, using this typedef to ensure we all share the same prototype, are you telling me all the users of the typedef have to remember to tag that part on for themselves? No. I am talking about linkage not the calling convention. Look at how __efiapi is used in the kernel if you would like to understand the difference.
On 17 December 2024 10:29:21 CET, Ard Biesheuvel <ardb@kernel.org> wrote: >On Tue, 17 Dec 2024 at 10:21, David Woodhouse <dwmw2@infradead.org> wrote: >> >> On 17 December 2024 09:49:04 CET, Ard Biesheuvel <ardb@kernel.org> wrote: >> >On Tue, 17 Dec 2024 at 00:37, David Woodhouse <dwmw2@infradead.org> wrote: >> >> >> >> From: David Woodhouse <dwmw@amazon.co.uk> >> >> >> >> Both i386 and x86_64 now copy the relocate_kernel function into the control >> >> page and execute it from there, using an open-coded function pointer. >> >> >> >> Use a typedef for it instead. >> >> >> >> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk> >> >> --- >> >> arch/x86/include/asm/kexec.h | 26 +++++++++++++------------- >> >> arch/x86/kernel/machine_kexec_32.c | 7 +------ >> >> arch/x86/kernel/machine_kexec_64.c | 6 +----- >> >> 3 files changed, 15 insertions(+), 24 deletions(-) >> >> >> >> diff --git a/arch/x86/include/asm/kexec.h b/arch/x86/include/asm/kexec.h >> >> index 48e4f44f794f..8ad187462b68 100644 >> >> --- a/arch/x86/include/asm/kexec.h >> >> +++ b/arch/x86/include/asm/kexec.h >> >> @@ -111,21 +111,21 @@ static inline void crash_setup_regs(struct pt_regs *newregs, >> >> } >> >> >> >> #ifdef CONFIG_X86_32 >> >> -asmlinkage unsigned long >> >> -relocate_kernel(unsigned long indirection_page, >> >> - unsigned long control_page, >> >> - unsigned long start_address, >> >> - unsigned int has_pae, >> >> - unsigned int preserve_context); >> >> +typedef asmlinkage unsigned long >> >> +relocate_kernel_fn(unsigned long indirection_page, >> >> + unsigned long control_page, >> >> + unsigned long start_address, >> >> + unsigned int has_pae, >> >> + unsigned int preserve_context); >> > >> >linkage is not part of the type. 'asmlinkage' is #define'd to the >> >empty string today, so it doesn't matter, but better to omit it here. >> >> This is the i386 version. I thought ut was something like regparm(3) there? >> >> And... WTF? How is the calling convention not part of the fundamental type of the function? If I have a pointer to such a function, using this typedef to ensure we all share the same prototype, are you telling me all the users of the typedef have to remember to tag that part on for themselves? > >No. I am talking about linkage not the calling convention. Hm, I am perfectly happy to believe that my memory is failing me, especially when it comes to specifics of i386 assembler code. But are you also telling me that <https://kernelnewbies.org/FAQ/asmlinkage> is a lie?
On Tue, 17 Dec 2024 at 10:42, David Woodhouse <dwmw2@infradead.org> wrote: > > On 17 December 2024 10:29:21 CET, Ard Biesheuvel <ardb@kernel.org> wrote: > >On Tue, 17 Dec 2024 at 10:21, David Woodhouse <dwmw2@infradead.org> wrote: > >> > >> On 17 December 2024 09:49:04 CET, Ard Biesheuvel <ardb@kernel.org> wrote: > >> >On Tue, 17 Dec 2024 at 00:37, David Woodhouse <dwmw2@infradead.org> wrote: > >> >> > >> >> From: David Woodhouse <dwmw@amazon.co.uk> > >> >> > >> >> Both i386 and x86_64 now copy the relocate_kernel function into the control > >> >> page and execute it from there, using an open-coded function pointer. > >> >> > >> >> Use a typedef for it instead. > >> >> > >> >> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk> > >> >> --- > >> >> arch/x86/include/asm/kexec.h | 26 +++++++++++++------------- > >> >> arch/x86/kernel/machine_kexec_32.c | 7 +------ > >> >> arch/x86/kernel/machine_kexec_64.c | 6 +----- > >> >> 3 files changed, 15 insertions(+), 24 deletions(-) > >> >> > >> >> diff --git a/arch/x86/include/asm/kexec.h b/arch/x86/include/asm/kexec.h > >> >> index 48e4f44f794f..8ad187462b68 100644 > >> >> --- a/arch/x86/include/asm/kexec.h > >> >> +++ b/arch/x86/include/asm/kexec.h > >> >> @@ -111,21 +111,21 @@ static inline void crash_setup_regs(struct pt_regs *newregs, > >> >> } > >> >> > >> >> #ifdef CONFIG_X86_32 > >> >> -asmlinkage unsigned long > >> >> -relocate_kernel(unsigned long indirection_page, > >> >> - unsigned long control_page, > >> >> - unsigned long start_address, > >> >> - unsigned int has_pae, > >> >> - unsigned int preserve_context); > >> >> +typedef asmlinkage unsigned long > >> >> +relocate_kernel_fn(unsigned long indirection_page, > >> >> + unsigned long control_page, > >> >> + unsigned long start_address, > >> >> + unsigned int has_pae, > >> >> + unsigned int preserve_context); > >> > > >> >linkage is not part of the type. 'asmlinkage' is #define'd to the > >> >empty string today, so it doesn't matter, but better to omit it here. > >> > >> This is the i386 version. I thought ut was something like regparm(3) there? > >> > >> And... WTF? How is the calling convention not part of the fundamental type of the function? If I have a pointer to such a function, using this typedef to ensure we all share the same prototype, are you telling me all the users of the typedef have to remember to tag that part on for themselves? > > > >No. I am talking about linkage not the calling convention. > > Hm, I am perfectly happy to believe that my memory is failing me, especially when it comes to specifics of i386 assembler code. But are you also telling me that > <https://kernelnewbies.org/FAQ/asmlinkage> is a lie? > It seems wildly out of date, at least. Commit 96a388de5dc53a8b2 from 2007 removed the asmlinkage definition containing regparm(0) from include/asm-i386/linkage.h, and I'm not convinced it was ever sound to conflate linkage with calling convention like that. Today, asmlinkage evaluates to 'extern "C"' when using a C++ compiler, which is also not part of the type. However, I failed to notice that this just moves code around, and only applies to 32-bit in the first place. So I won't waste any more of your time obsessing over this.
On 17 December 2024 10:54:19 CET, Ard Biesheuvel <ardb@kernel.org> wrote: >On Tue, 17 Dec 2024 at 10:42, David Woodhouse <dwmw2@infradead.org> wrote: >> Hm, I am perfectly happy to believe that my memory is failing me, especially when it comes to specifics of i386 assembler code. But are you also telling me that >> <https://kernelnewbies.org/FAQ/asmlinkage> is a lie? >> > >It seems wildly out of date, at least. > >Commit 96a388de5dc53a8b2 from 2007 removed the asmlinkage definition >containing regparm(0) from include/asm-i386/linkage.h, No, it's still there in arch/x86/include/asm/linkage.h: <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/linkage.h#n20> And maybe you're right that it's a poorly named macro and we shouldn't conflate calling convention with linkage. But I think it *should* be part of the typedef.
On 17 December 2024 10:54:19 CET, Ard Biesheuvel <ardb@kernel.org> wrote: >On Tue, 17 Dec 2024 at 10:42, David Woodhouse <dwmw2@infradead.org> wrote: >> Hm, I am perfectly happy to believe that my memory is failing me, especially when it comes to specifics of i386 assembler code. But are you also telling me that >> <https://kernelnewbies.org/FAQ/asmlinkage> is a lie? >> > >It seems wildly out of date, at least. > >Commit 96a388de5dc53a8b2 from 2007 removed the asmlinkage definition >containing regparm(0) from include/asm-i386/linkage.h, and I'm not >convinced it was ever sound to conflate linkage with calling >convention like that. Today, asmlinkage evaluates to 'extern "C"' when >using a C++ compiler, which is also not part of the type. > >However, I failed to notice that this just moves code around, and only >applies to 32-bit in the first place. So I won't waste any more of >your time obsessing over this. Too late :) You've already made me concerned about what the calling convention *is* for relocate_kernel() on i386. Because if asmlinkage doesn't mean regparm(0) any more and the i386 kernel is still built with -mregparm=3, then how does the asm code (which seems to believe all its arguments are on the stack) actually work? It seems slightly unlikely that kexec on i386 has just been broken since 2007 but I'm not sure I'd completely rule it out. So now I guess I have to actually build a 32-bit userspace test case and *test* it. And that means I no longer have any excuse for not doing all the same cleanups in the i386 version of the code that I've done for x86_64... Thanks for that :)
On Tue, 17 Dec 2024 at 11:07, David Woodhouse <dwmw2@infradead.org> wrote: > > On 17 December 2024 10:54:19 CET, Ard Biesheuvel <ardb@kernel.org> wrote: > >On Tue, 17 Dec 2024 at 10:42, David Woodhouse <dwmw2@infradead.org> wrote: > >> Hm, I am perfectly happy to believe that my memory is failing me, especially when it comes to specifics of i386 assembler code. But are you also telling me that > >> <https://kernelnewbies.org/FAQ/asmlinkage> is a lie? > >> > > > >It seems wildly out of date, at least. > > > >Commit 96a388de5dc53a8b2 from 2007 removed the asmlinkage definition > >containing regparm(0) from include/asm-i386/linkage.h, and I'm not > >convinced it was ever sound to conflate linkage with calling > >convention like that. Today, asmlinkage evaluates to 'extern "C"' when > >using a C++ compiler, which is also not part of the type. > > > >However, I failed to notice that this just moves code around, and only > >applies to 32-bit in the first place. So I won't waste any more of > >your time obsessing over this. > > Too late :) > > You've already made me concerned about what the calling convention *is* for relocate_kernel() on i386. Because if asmlinkage doesn't mean regparm(0) any more and the i386 kernel is still built with -mregparm=3, then how does the asm code (which seems to believe all its arguments are on the stack) actually work? > > It seems slightly unlikely that kexec on i386 has just been broken since 2007 but I'm not sure I'd completely rule it out. > > So now I guess I have to actually build a 32-bit userspace test case and *test* it. > > And that means I no longer have any excuse for not doing all the same cleanups in the i386 version of the code that I've done for x86_64... > > Thanks for that :) > Actually, asmlinkage still means regparm(0) on i386, so I'm going to have to apologise again, for my poor git foo this time.
On 17 December 2024 11:14:29 CET, Ard Biesheuvel <ardb@kernel.org> wrote: >On Tue, 17 Dec 2024 at 11:07, David Woodhouse <dwmw2@infradead.org> wrote: >> >> On 17 December 2024 10:54:19 CET, Ard Biesheuvel <ardb@kernel.org> wrote: >> >On Tue, 17 Dec 2024 at 10:42, David Woodhouse <dwmw2@infradead.org> wrote: >> >> Hm, I am perfectly happy to believe that my memory is failing me, especially when it comes to specifics of i386 assembler code. But are you also telling me that >> >> <https://kernelnewbies.org/FAQ/asmlinkage> is a lie? >> >> >> > >> >It seems wildly out of date, at least. >> > >> >Commit 96a388de5dc53a8b2 from 2007 removed the asmlinkage definition >> >containing regparm(0) from include/asm-i386/linkage.h, and I'm not >> >convinced it was ever sound to conflate linkage with calling >> >convention like that. Today, asmlinkage evaluates to 'extern "C"' when >> >using a C++ compiler, which is also not part of the type. >> > >> >However, I failed to notice that this just moves code around, and only >> >applies to 32-bit in the first place. So I won't waste any more of >> >your time obsessing over this. >> >> Too late :) >> >> You've already made me concerned about what the calling convention *is* for relocate_kernel() on i386. Because if asmlinkage doesn't mean regparm(0) any more and the i386 kernel is still built with -mregparm=3, then how does the asm code (which seems to believe all its arguments are on the stack) actually work? >> >> It seems slightly unlikely that kexec on i386 has just been broken since 2007 but I'm not sure I'd completely rule it out. >> >> So now I guess I have to actually build a 32-bit userspace test case and *test* it. >> >> And that means I no longer have any excuse for not doing all the same cleanups in the i386 version of the code that I've done for x86_64... >> >> Thanks for that :) >> > >Actually, asmlinkage still means regparm(0) on i386, so I'm going to >have to apologise again, for my poor git foo this time. Heh, no problem. I'm pleased to find my memory wasn't failing me after all, so I might have a bit more time left before they put me out to pasture. I'll take that win :)
© 2016 - 2025 Red Hat, Inc.