Hi Timo,Aki, I have been investigating why the IMAP hibernation feature (imap-hibernate) fails natively under FreeBSD 15.x, and I managed to trace down and fix the core architectural issues. The Root Cause Analysis Integer Overflow on Hibernation Entry: Under FreeBSD 15.x, running fstat() on a network/virtual socket descriptor returns NODEV (UINT64_MAX or 18446744073709551615) for both major() and minor() device numbers. When an IMAP process tries to hibernate, it serializes these values and sends them to the imap-hibernate service. However, both src/imap-hibernate/imap-hibernate-client.c and src/imap/imap-master-client.c utilize 32-bit unsigned int storage for peer_dev_majorand peer_dev_minor, paired with str_to_uint(). This triggers an instant integer overflow parsing error on entry: imap-hibernate: Error: conn unix:imap-hibernate (uid=125): Failed to parse client input: Invalid peer_dev_major value: 18446744073709551615 Strict Verification Bug on Unhibernation (Exit): When a client wakes up, imap_master_client_verify() in src/imap/imap-master-client.c checks if the current peer_st.st_dev matches the recorded master_input->peer_dev via the CMP_DEV_T macro. Under FreeBSD, since these are virtual network descriptors, the kernel's device mapping evaluation causes a mismatch check failure, triggering a false positive BUG panic loop during the restore assertion. The Complete 64-bit IPC Solution To resolve this fundamentally without masking data or dropping validation, we need to expand the internal serialization parsers to uint64_t using str_to_uint64() on both ends of the IPC boundary, and add an explicit exemption for FreeBSD's NODEV (-1) sockets during verification. Here are the patches required for a complete solution: 1. Inside src/imap-hibernate/imap-hibernate-client.c --- src/imap-hibernate/imap-hibernate-client.c.orig 2026-06-25 19:49:30 UTC +++ src/imap-hibernate/imap-hibernate-client.c @@ -51,8 +51,7 @@ imap_hibernate_client_parse_input(const char *const *a const char **error_r) { const char *key, *value; - unsigned int peer_dev_major = 0, peer_dev_minor = 0; - + uint64_t peer_dev_major = 0, peer_dev_minor = 0; i_zero(state_r); if (args[0] == NULL) { *error_r = "Missing username in input"; @@ -98,13 +97,13 @@ imap_hibernate_client_parse_input(const char *const *a return -1; } } else if (strcmp(key, "peer_dev_major") == 0) { - if (str_to_uint(value, &peer_dev_major) < 0) { + if (str_to_uint64(value, &peer_dev_major) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_major value: %s", value); return -1; } } else if (strcmp(key, "peer_dev_minor") == 0) { - if (str_to_uint(value, &peer_dev_minor) < 0) { + if (str_to_uint64(value, &peer_dev_minor) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_minor value: %s", value); return -1; @@ -244,7 +243,7 @@ imap_hibernate_client_parse_input(const char *const *a return -1; } if (peer_dev_major != 0 || peer_dev_minor != 0) - state_r->peer_dev = makedev(peer_dev_major, peer_dev_minor); + state_r->peer_dev = makedev((unsigned long)peer_dev_major, (unsigned long)peer_dev_minor); return 0; } 2. Inside src/imap/imap-master-client.c --- src/imap/imap-master-client.c.orig 2026-05-12 10:48:57 UTC +++ src/imap/imap-master-client.c @@ -75,7 +75,7 @@ imap_master_client_parse_input(const char *const *args const char **error_r) { const char *key, *value; - unsigned int peer_dev_major = 0, peer_dev_minor = 0; + uint64_t peer_dev_major = 0, peer_dev_minor = 0; i_zero(input_r); i_zero(stats_r); @@ -128,13 +128,13 @@ imap_master_client_parse_input(const char *const *args return -1; } } else if (strcmp(key, "peer_dev_major") == 0) { - if (str_to_uint(value, &peer_dev_major) < 0) { + if (str_to_uint64(value, &peer_dev_major) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_major value: %s", value); return -1; } } else if (strcmp(key, "peer_dev_minor") == 0) { - if (str_to_uint(value, &peer_dev_minor) < 0) { + if (str_to_uint64(value, &peer_dev_minor) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_minor value: %s", value); return -1; @@ -265,7 +265,7 @@ imap_master_client_parse_input(const char *const *args } if (peer_dev_major != 0 || peer_dev_minor != 0) { master_input_r->peer_dev = - makedev(peer_dev_major, peer_dev_minor); + makedev((unsigned long)peer_dev_major, (unsigned long)peer_dev_minor); } return 0; } @@ -410,7 +410,8 @@ static int imap_master_client_verify(const struct imap return -1; } if (peer_st.st_ino != master_input->peer_ino || - !CMP_DEV_T(peer_st.st_dev, master_input->peer_dev)) { + (!CMP_DEV_T(peer_st.st_dev, master_input->peer_dev) && + major(peer_st.st_dev) != (unsigned long)-1)) { *error_r = t_strdup_printf( "BUG: Expected peer device=%lu,%lu inode=%s doesn't match " "client fd's actual device=%lu,%lu inode=%s", Empirical Testing & Results To answer your question regarding whether incoming mail or flag modifications on SELECTED mailboxes are still properly noticed during background hibernation—yes, I have performed live production tests with these patches applied, and it works perfectly. The Test Scenario: An active connection entered an authenticated session on the INBOX and executed an explicit IDLE sequence. It was successfully sent to sleep and offloaded to imap-hibernate (verified via doveadm who). The Notification Event: A new message was transmitted to the mailbox externally via Gmail. The Outcome: The internal event system seamlessly triggered the kernel notification layers. The imap-hibernateservice immediately picked up the modification event, successfully unhibernated the session, and seamlessly handed the socket descriptor back over to a standard imap worker process. The active client instantly pushed raw streaming updates to the live terminal without dropping connection parameters or printing any errors: Plaintext + idling * OK Still here * 4249 EXISTS * 1 RECENT * 4249 FETCH (FLAGS (\Recent $NotJunk NotJunk)) Long-term stability tests show that idle sessions remain safely tucked away under imap-hibernate for hours until client-side teardowns or standard network NAT lease expirations occur, at which point the process terminates gracefully with a routine Disconnected: Connection closed log record. I hope this native 64-bit conversion can be merged into standard upstream branches to officially unbreak the feature for FreeBSD server deployment. Best regards, Jordan
On 25 Jun 2026, at 19:34, Aki Tuomi <aki.tuomi@open-xchange.com> wrote:
Did you also test that incoming mail, or changes to SELECTed mailboxes is noticed while hibernated?
Hi Timo,Aki, I have been investigating why the IMAP hibernation feature (imap-hibernate) fails natively under FreeBSD 15.x, and I managed to trace down and fix the core architectural issues. The Root Cause Analysis 1. Integer Overflow on Hibernation Entry: Under FreeBSD 15.x, running fstat() on a network/virtual socket descriptor returns NODEV (UINT64_MAX or 18446744073709551615) for both major() and minor() device numbers. When an IMAP process tries to hibernate, it serializes these values and sends them to the imap-hibernate service. However, both src/imap-hibernate/imap-hibernate-client.c and src/imap/imap-master-client.c utilize 32-bit unsigned int storage for peer_dev_majorand peer_dev_minor, paired with str_to_uint(). This triggers an instant integer overflow parsing error on entry: imap-hibernate: Error: conn unix:imap-hibernate (uid=125): Failed to parse client input: Invalid peer_dev_major value: 18446744073709551615 2. Strict Verification Bug on Unhibernation (Exit): When a client wakes up, imap_master_client_verify() in src/imap/imap-master-client.c checks if the current peer_st.st_dev matches the recorded master_input->peer_dev via the CMP_DEV_T macro. Under FreeBSD, since these are virtual network descriptors, the kernel's device mapping evaluation causes a mismatch check failure, triggering a false positive BUG panic loop during the restore assertion. The Complete 64-bit IPC Solution To resolve this fundamentally without masking data or dropping validation, we need to expand the internal serialization parsers to uint64_t using str_to_uint64() on both ends of the IPC boundary, and add an explicit exemption for FreeBSD's NODEV (-1) sockets during verification. Here are the patches required for a complete solution: 1. Inside src/imap-hibernate/imap-hibernate-client.c --- src/imap-hibernate/imap-hibernate-client.c.orig 2026-06-25 19:49:30 UTC +++ src/imap-hibernate/imap-hibernate-client.c @@ -51,8 +51,7 @@ imap_hibernate_client_parse_input(const char *const *a const char **error_r) { const char *key, *value; - unsigned int peer_dev_major = 0, peer_dev_minor = 0; - + uint64_t peer_dev_major = 0, peer_dev_minor = 0; i_zero(state_r); if (args[0] == NULL) { *error_r = "Missing username in input"; @@ -98,13 +97,13 @@ imap_hibernate_client_parse_input(const char *const *a return -1; } } else if (strcmp(key, "peer_dev_major") == 0) { - if (str_to_uint(value, &peer_dev_major) < 0) { + if (str_to_uint64(value, &peer_dev_major) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_major value: %s", value); return -1; } } else if (strcmp(key, "peer_dev_minor") == 0) { - if (str_to_uint(value, &peer_dev_minor) < 0) { + if (str_to_uint64(value, &peer_dev_minor) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_minor value: %s", value); return -1; @@ -244,7 +243,7 @@ imap_hibernate_client_parse_input(const char *const *a return -1; } if (peer_dev_major != 0 || peer_dev_minor != 0) - state_r->peer_dev = makedev(peer_dev_major, peer_dev_minor); + state_r->peer_dev = makedev((unsigned long)peer_dev_major, (unsigned long)peer_dev_minor); return 0; } 2. Inside src/imap/imap-master-client.c --- src/imap/imap-master-client.c.orig 2026-05-12 10:48:57 UTC +++ src/imap/imap-master-client.c @@ -75,7 +75,7 @@ imap_master_client_parse_input(const char *const *args const char **error_r) { const char *key, *value; - unsigned int peer_dev_major = 0, peer_dev_minor = 0; + uint64_t peer_dev_major = 0, peer_dev_minor = 0; i_zero(input_r); i_zero(stats_r); @@ -128,13 +128,13 @@ imap_master_client_parse_input(const char *const *args return -1; } } else if (strcmp(key, "peer_dev_major") == 0) { - if (str_to_uint(value, &peer_dev_major) < 0) { + if (str_to_uint64(value, &peer_dev_major) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_major value: %s", value); return -1; } } else if (strcmp(key, "peer_dev_minor") == 0) { - if (str_to_uint(value, &peer_dev_minor) < 0) { + if (str_to_uint64(value, &peer_dev_minor) < 0) { *error_r = t_strdup_printf( "Invalid peer_dev_minor value: %s", value); return -1; @@ -265,7 +265,7 @@ imap_master_client_parse_input(const char *const *args } if (peer_dev_major != 0 || peer_dev_minor != 0) { master_input_r->peer_dev = - makedev(peer_dev_major, peer_dev_minor); + makedev((unsigned long)peer_dev_major, (unsigned long)peer_dev_minor); } return 0; } @@ -410,7 +410,8 @@ static int imap_master_client_verify(const struct imap return -1; } if (peer_st.st_ino != master_input->peer_ino || - !CMP_DEV_T(peer_st.st_dev, master_input->peer_dev)) { + (!CMP_DEV_T(peer_st.st_dev, master_input->peer_dev) && + major(peer_st.st_dev) != (unsigned long)-1)) { *error_r = t_strdup_printf( "BUG: Expected peer device=%lu,%lu inode=%s doesn't match " "client fd's actual device=%lu,%lu inode=%s", Empirical Testing & Results To answer your question regarding whether incoming mail or flag modifications on SELECTED mailboxes are still properly noticed during background hibernation--yes, I have performed live production tests with these patches applied, and it works perfectly. o The Test Scenario: An active connection entered an authenticated session on the INBOX and executed an explicit IDLE sequence. It was successfully sent to sleep and offloaded to imap-hibernate (verified via doveadm who). o The Notification Event: A new message was transmitted to the mailbox externally via Gmail. o The Outcome: The internal event system seamlessly triggered the kernel notification layers. The imap-hibernateservice immediately picked up the modification event, successfully unhibernated the session, and seamlessly handed the socket descriptor back over to a standard imap worker process. The active client instantly pushed raw streaming updates to the live terminal without dropping connection parameters or printing any errors: Plaintext + idling * OK Still here * 4249 EXISTS * 1 RECENT * 4249 FETCH (FLAGS (\Recent $NotJunk NotJunk)) Long-term stability tests show that idle sessions remain safely tucked away under imap-hibernate for hours until client-side teardowns or standard network NAT lease expirations occur, at which point the process terminates gracefully with a routine Disconnected: Connection closed log record. I hope this native 64-bit conversion can be merged into standard upstream branches to officially unbreak the feature for FreeBSD server deployment. Best regards, Jordan On 25 Jun 2026, at 19:34, Aki Tuomi <aki.tuomi@open-xchange.com> wrote: Did you also test that incoming mail, or changes to SELECTed mailboxes is noticed while hibernated?