Skip to content

Conversation

@xbjfk
Copy link
Contributor

@xbjfk xbjfk commented May 26, 2025

Description

This change includes time64 support for applicable architectures (x86, arm, mips and powerpc). This is based on the previous PRs to this repo as well as the musl changelog from 1.1.24 -> 1.2. It can be enabled with the environment variable
RUST_LIBC_UNSTABLE_MUSL_TIME64 only when musl_v1_2_3 is enabled and the architecture is supported.

A lot of structures, especially ones with mixed endian became excessively complicated, so I used cfg_if to separate them. It looks like you can only nest s! {} in cfg_if! {}, but not vice versa.

As a note, I'm considering removing musl_not_time64, and just keeping the old logic of allowing deprecated for function definitions involving time_t as it introduces necessary complexity.

When libc 1.0 is released, I believe the best path would be to remove the musl_v1_2_3 feature, making it unconditionally enabled, keeping musl_time64 which will expand to (musl && time64_arch).

Tested through QEMU for all architectures.

Sources

Sources are located on each commit, in the form of upstream commits

Checklist

  • Relevant tests in libc-test/semver have been updated
  • No placeholder or unstable values like *LAST or *MAX are
    included (see rust-lang/libc#3131)
  • Tested locally (cd libc-test && cargo test --target mytarget);
    especially relevant for platforms that may not be checked in CI

@rustbot label +stable-nominated

@rustbot rustbot added A-CI Area: CI-related items O-arm O-linux O-linux-like O-mips O-musl O-unix O-x86 S-waiting-on-review stable-nominated This PR should be considered for cherry-pick to libc's stable release branch labels May 26, 2025
@xbjfk xbjfk changed the title musl: time64 musl: 64-bit time support May 26, 2025
@xbjfk xbjfk mentioned this pull request May 26, 2025
5 tasks
@xbjfk
Copy link
Contributor Author

xbjfk commented May 26, 2025

Hmm, seems like the style checker isn't happy with mixing s! {} and cfg_if! {}, and putting cfg_if! {} inside an s! {} block does not seem to work either. I'm not sure what the best course of action is regarding this.

@xbjfk
Copy link
Contributor Author

xbjfk commented May 30, 2025

Since #4433 seems close to merging, I can rebase on top of it once merged.

@xbjfk xbjfk force-pushed the musl-time64 branch 6 times, most recently from 8b32bb3 to e91f182 Compare June 3, 2025 08:26
@xbjfk
Copy link
Contributor Author

xbjfk commented Jun 3, 2025

Rebased on top of the GNU changes :)

@tgross35
Copy link
Contributor

tgross35 commented Jun 3, 2025

Thanks! Sorry it's taken me a while, I'll try to look at this very soon

Copy link
Contributor

@tgross35 tgross35 left a comment

Choose a reason for hiding this comment

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

Thanks for working on this! Left a few comments here.

For the style check, are the changes it is suggesting possible? If not, that will probably have to be updated unfortunately (hopefully we will be able to reorganize things at some point so it's less annoying...)

@xbjfk
Copy link
Contributor Author

xbjfk commented Jun 15, 2025

Thanks for the review! I'll get around to implementing the changes hopefully in the next few days. I'll try figure out how to pass the style lints - but it might need a new file.

After this merges and when 1.0 is closer to release feel free to ping me on GitHub and I can help fully complete the transition for these changes in the 1.0 branch on the musl side :).

#[cfg(not(all(target_arch = "x86_64", target_pointer_width = "32")))]
pub tv_nsec: c_long,
#[cfg(all(musl_time64, target_endian = "little"))]
__pad0: u32,

Choose a reason for hiding this comment

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

Playing with this PR, this makes constructing timespec structs directly as was done in std fail:

error: cannot construct `timespec` with struct literal syntax due to private fields
   --> library/std/src/sys/pal/unix/thread.rs:251:30
    |
251 |                 let mut ts = libc::timespec {
    |                              ^^^^^^^^^^^^^^
    |
    = note: ...and other private field `__pad0` that was not provided

I'm not quite sure what to suggest here -- making these pub doesn't really make sense, and even if we did I believe we'd need something like ..Default::default() to make it work so users of libc::timespec (including the compiler) will need to be fixed?... :/

At this build step there are only 4 (as of rust-lang/rust@255aa22) so this is probably not too bad, but here goes my hopes to see this backported in 0.2 :p
(for reference:

  • library/std/src/sys/pal/unix/thread.rs:251:30
  • library/std/src/sys/pal/unix/time.rs:10:5
  • library/std/src/sys/pal/unix/time.rs:199:14
  • library/std/src/sys/fs/unix.rs:1507:24
    )

Copy link

@martinetd martinetd Jun 23, 2025

Choose a reason for hiding this comment

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

FWIW, just reporting it's working as expected here by working around this issue by making the fields pub here and patching std as follow:

rust-lang/rust diff
diff --git a/Cargo.lock b/Cargo.lock
index 1a619096d34c..6f91779999af 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2046,8 +2046,6 @@ checksum = "9fa0e2a1fcbe2f6be6c42e342259976206b383122fc152e872795338b5a3f3a7"
 [[package]]
 name = "libc"
 version = "0.2.174"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
 
 [[package]]
 name = "libdbus-sys"
diff --git a/Cargo.toml b/Cargo.toml
index c4d2a06f4cb1..12757101281e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -89,3 +89,5 @@ codegen-units = 1
 # FIXME: LTO cannot be enabled for binaries in a workspace
 # <https://github.com/rust-lang/cargo/issues/9330>
 # lto = true
+[patch.crates-io.libc]
+path = "/mnt/rust/libc"
diff --git a/library/Cargo.lock b/library/Cargo.lock
index 34012d6943b7..b112d7ab9ac6 100644
--- a/library/Cargo.lock
+++ b/library/Cargo.lock
@@ -142,8 +142,6 @@ dependencies = [
 [[package]]
 name = "libc"
 version = "0.2.174"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
 dependencies = [
  "rustc-std-workspace-core",
 ]
diff --git a/library/Cargo.toml b/library/Cargo.toml
index 35480b9319d7..955ae17cca35 100644
--- a/library/Cargo.toml
+++ b/library/Cargo.toml
@@ -51,3 +51,5 @@ rustc-std-workspace-core = { path = 'rustc-std-workspace-core' }
 rustc-std-workspace-alloc = { path = 'rustc-std-workspace-alloc' }
 rustc-std-workspace-std = { path = 'rustc-std-workspace-std' }
 compiler_builtins = { path = "compiler-builtins/compiler-builtins" }
+[patch.crates-io.libc]
+path = "/mnt/rust/libc"
diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs
index dc278274f00f..db1fc8a527f0 100644
--- a/library/std/src/sys/fs/unix.rs
+++ b/library/std/src/sys/fs/unix.rs
@@ -1504,7 +1504,12 @@ pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
                 io::ErrorKind::InvalidInput,
                 "timestamp is too small to set as a file time",
             )),
-            None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }),
+            None => Ok(libc::timespec {
+                tv_sec: 0,
+                tv_nsec: libc::UTIME_OMIT as _,
+                #[cfg(target_pointer_width = "32")]
+                __pad0: 0,
+            }),
         };
         cfg_if::cfg_if! {
             if #[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx"))] {
diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs
index d8b189413f4a..20002a4caf16 100644
--- a/library/std/src/sys/pal/unix/thread.rs
+++ b/library/std/src/sys/pal/unix/thread.rs
@@ -251,6 +251,8 @@ pub fn sleep(dur: Duration) {
                 let mut ts = libc::timespec {
                     tv_sec: cmp::min(libc::time_t::MAX as u64, secs) as libc::time_t,
                     tv_nsec: nsecs,
+                    #[cfg(target_pointer_width = "32")]
+                    __pad0: 0,
                 };
                 secs -= ts.tv_sec as u64;
                 let ts_ptr = &raw mut ts;
diff --git a/library/std/src/sys/pal/unix/time.rs b/library/std/src/sys/pal/unix/time.rs
index 0074d7674741..262f2d70fb18 100644
--- a/library/std/src/sys/pal/unix/time.rs
+++ b/library/std/src/sys/pal/unix/time.rs
@@ -6,8 +6,12 @@
 const NSEC_PER_SEC: u64 = 1_000_000_000;
 pub const UNIX_EPOCH: SystemTime = SystemTime { t: Timespec::zero() };
 #[allow(dead_code)] // Used for pthread condvar timeouts
-pub const TIMESPEC_MAX: libc::timespec =
-    libc::timespec { tv_sec: <libc::time_t>::MAX, tv_nsec: 1_000_000_000 - 1 };
+pub const TIMESPEC_MAX: libc::timespec = libc::timespec {
+    tv_sec: <libc::time_t>::MAX,
+    tv_nsec: 1_000_000_000 - 1,
+    #[cfg(target_pointer_width = "32")]
+    __pad0: 0,
+};
 
 // This additional constant is only used when calling
 // `libc::pthread_cond_timedwait`.
@@ -199,6 +203,8 @@ pub fn to_timespec(&self) -> Option<libc::timespec> {
         Some(libc::timespec {
             tv_sec: self.tv_sec.try_into().ok()?,
             tv_nsec: self.tv_nsec.as_inner().try_into().ok()?,
+            #[cfg(target_pointer_width = "32")]
+            __pad0: 0,
         })
     }

made our axum-based server run properly after changing the date past 2038.

(I'd be curious if there's a better way to test this though, perhaps with -Zbuild-std ?, but this is good enough for high level testing)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, yeah - I'm not sure what the best path forward for this is. I believe the GNU changes will also result in a similar issue. I guess I will make the migration process to 1.0 more painful. I think I can make another PR to rust in that case. I think consumers will need to use ..Default::default(). Another problem I can think of is people naively not including that when compiling for 64bit locally breaking things for time64 users.

Choose a reason for hiding this comment

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

Another problem I can think of is people naively not including that when compiling for 64bit locally breaking things for time64 users.

Yeah, if we go the default way I think we'll want to make ..Default::default() mandatory -- I 've just tried and it's possible with a zero-length field like PhantomData (...except I guess we can't use that in libc, so we'll need another way to express it)
For the transition we could add the default trait so someone could add ..Default::default() now even if not required (... but that triggers a clippy warning...)

(Oh, and there's also deconstruction by pattern matching that'll need a , .....)

At this point if we're breaking API though there might be a better way, implicit cast through a well defined struct with the current fields? ... or maybe there's even a way to specify the physical representation of the fields without adding explicit padding? (but if there is I sure don't know 😁 )

Anyway, yeah, this will probably make the transition to 1.0 a bit painful so it's probably worth spending a bit of time now to figure out the path of least resistance...

Copy link
Contributor Author

@xbjfk xbjfk Jun 26, 2025

Choose a reason for hiding this comment

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

I'm not certain, but wouldn't PhantomData be fine if it is a member? I know ZST for FFI is generally not allowed - but the C code does not directly access the zero sized type? I just tested it and can see that rust does not report improper_ctypes if a struct contains PhantomData is used as an extern "C" argument - it does report it however if the only member of the struct is PhantomData.

We should probably also start some migration documentation of 1.0 changes, and how to fix them.

Copy link
Contributor Author

@xbjfk xbjfk Jun 27, 2025

Choose a reason for hiding this comment

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

I thought about this - I think I will open another PR that will not be stable-nominated specifically for this.

Choose a reason for hiding this comment

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

My first reaction was that it makes sense to split the thorny part out for more discussion/refining, but thinking back it's probably wrong to use time64 variant functions with a mismatched type (as in will appear to work but fail subtly because ns are not where the callee thinks they are), so I think this has to stay together?
Even if the cost is that this never makes it to stable, I don't see how we could make a "partial time64 support" work properly.

I'll try to play some with struct shaping next week to see if we can do better than relying on default; I've never heard of anything that we could use to shape the struct without affecting the API but it can't hurt to spend a bit more time on it...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I just realized - we don't need to use PhantomData hacks at all - we can just mark it as #[non_exhaustive] for all other architectures - this will have essentially the same effect, forcing people to use ..Default::default() and such. Also, I think the idea is to fully enable time64 support and remove 32 bit time support, at least on musl.

You are right regarding the types - I meant to split making timespec non_exhaustive globally out.

Copy link
Contributor Author

@xbjfk xbjfk Jun 28, 2025

Choose a reason for hiding this comment

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

I created #4508 for visibility - let me know if you find a better alternative!

Copy link
Contributor

Choose a reason for hiding this comment

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

In 1.0 we'll be making everything non_exhaustive, we more or less want everyone to go via MaybeUninit::zeroed and write individual fields like you have to do with C. Don't worry about it here while the flags are unstable.

See #4080 for a bit more.

@xbjfk xbjfk force-pushed the musl-time64 branch 2 times, most recently from 9b5ca40 to f669296 Compare June 23, 2025 11:51
@xbjfk
Copy link
Contributor Author

xbjfk commented Oct 5, 2025

Hey @tgross35, sorry to ping/nag you - but I would be grateful if you could take a look at this :)

Copy link
Contributor

@tgross35 tgross35 left a comment

Choose a reason for hiding this comment

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

Hey @tgross35, sorry to ping/nag you - but I would be grateful if you could take a look at this :)

Thank you for the ping, I'm sorry it took so long! Left some feedback that I think should be near final, of course this needs a rebase to see what happens in CI.

Comment on lines 30 to 41
__reserved1: c_int,
//#[cfg(musl32_time64)]
__reserved2: [c_long; 4],
//#[cfg(not(musl32_time64))]
//__reserved2: [crate::timespec; 2],
__reserved3: c_int,
Copy link
Contributor

Choose a reason for hiding this comment

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

To match the source, add a struct __c_anon_sched_param__reserved2 { time_t, long } for this field and keep the configs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem with this (and the reason I kept these commented) is that this will be a breaking change as it will remove access to the sched_ss_* fields - but this sounds good if it stays in the 1.0 branch

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I follow; why would changing the type of these private fields affect access of public fields under a different cfg?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, sorry - I meant in the larger context as this struct is under the #[cfg(musl32_time64)] cfg_if - without the config, we have to keep sched_ss_* for semver compatibility. But, now that I think of it, I'm not sure why I gated this struct under musl32_time64 and not musl_v1_2_3.. I will change it and add re-add the configs

Comment on lines 4829 to 4831
(old_musl && struct_ == "utmpx" && field == "ut_session")
(old_musl && struct_ == "utmpx" && field == "ut_session") ||
// FIXME(linux): this is changed to separate sec/usec fields when time64 is enabled
(struct_ == "input_event" && field == "time")
Copy link
Contributor

Choose a reason for hiding this comment

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

This is musl-specific right? If so, it can get musl && as part of the condition.

@tgross35
Copy link
Contributor

Cc @Gelbpunkt @neuschaefer since you've done a ton of work on musl support.

@rustbot

This comment has been minimized.

@xbjfk
Copy link
Contributor Author

xbjfk commented Oct 27, 2025

Thanks for the detailed review! Just rebased - including updating for the new style checker and ci script. Will go through the reviews now :)

@tgross35
Copy link
Contributor

tgross35 commented Oct 27, 2025

Feel free to just add a commit deleting the handle_s_macro_no_attrs check, I don't have a good idea how to fix it to not complain in cfg_if

@rustbot
Copy link
Collaborator

rustbot commented Oct 30, 2025

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@xbjfk xbjfk force-pushed the musl-time64 branch 2 times, most recently from adeeb76 to 88d2e64 Compare October 30, 2025 11:24
xbjfk added 4 commits October 30, 2025 11:35
The structures are the same - and this follows the upstream definition
On musl at least, blkcnt64_t == blkcnt_t == i64 and
ino_t == ino64_t == u64
This feature is enabled with independently from musl_v1_2_3 to support
time64.

Defining this feature makes this roughly equivalent to upstream
commit bminor/musl@f12bd8e.
xbjfk added 8 commits October 30, 2025 11:50
This corresponds to upstream commit bminor/musl@1febd21 (most symbols)
and bminor/musl@22daaea (only dlsym)
A bunch of properties were removed upstream and set to reserved.

This matches upstream commit bminor/musl@827aa8f and bminor/musl@2d69fcf
Change time_t type to i64
Change struct stat, msqid_ds and shmid_ds to reflect

This commit follows upstream bminor/musl@3814333 and bminor/musl@d6dcfe4
It also implements a fix from bminor/musl@0fbd7d6
This is primarily based on a small part of bminor/musl@3814333.

This also integrates bminor/musl@3c02bac, which update MSG_STAT,
SEM_STAT, SEM_STAT_ANY. These are based on the value of IPC_STAT,
however we can just use `cfg` as it is effectively the same.
This is because the struct is different depending on whether time64 is
enabled.
Namely, this allows `target_endian` as well as adds a constant array
where certain configs are explicitly allowed (e.g. musl32_time64).
Unfortunately does not seem to handle cfg_if well
@xbjfk
Copy link
Contributor Author

xbjfk commented Oct 30, 2025

Okay, I've addressed most comments - unfortunately, it seems like there is an issue with compiling for hexagon - but it looks like it still appears on main.

Comment on lines +15 to +60
cfg_if! {
if #[cfg(not(any(
target_env = "musl",
target_os = "emscripten",
target_env = "ohos"
)))] {
s! {
pub struct sched_param {
pub sched_priority: c_int,
}
}
} else if #[cfg(musl_v1_2_3)] {
s! {
struct __c_anon_sched_param__reserved2 {
__reserved1: crate::time_t,
__reserved2: c_long,
}

pub struct sched_param {
pub sched_priority: c_int,

__reserved1: c_int,
#[cfg(musl32_time64)]
__reserved2: [c_long; 4],
#[cfg(not(musl32_time64))]
__reserved2: [__c_anon_sched_param__reserved2; 2],
__reserved3: c_int,
}
}
} else {
s! {
pub struct sched_param {
pub sched_priority: c_int,

#[deprecated(since = "0.2.173", note = "This field has been removed upstream")]
pub sched_ss_low_priority: c_int,
#[deprecated(since = "0.2.173", note = "This field has been removed upstream")]
pub sched_ss_repl_period: crate::timespec,
#[deprecated(since = "0.2.173", note = "This field has been removed upstream")]
pub sched_ss_init_budget: crate::timespec,
#[deprecated(since = "0.2.173", note = "This field has been removed upstream")]
pub sched_ss_max_repl: c_int,
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Commit "musl: time64: update struct sched_param": to keep things a bit cleaner, move these musl definitions to the musl module. Then keep the existing struct sched_param in this module but add the deprecated notes and move it to a cfg_if block with the not(any(musl, emscripten, ohos)) bit.

Also could you put this cfg_if! after the main s! macro with the others? Around line 252 looking at this file currently.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually if you wouldn't mind, the musl-specific bits could move directly to the new source structure a src/new/musl/sched.rs. Add pub(crate) mod sched; to src/new/musl/mod.rs and reexport its contents in the musl block at the bottom of new/mod.rs

Comment on lines +32 to 43
#[cfg(musl32_time64)]
__st_atim_pad: u32,
pub st_mtime: crate::time_t,
pub st_mtime_nsec: c_long,
#[cfg(musl32_time64)]
__st_mtim_pad: u32,
pub st_ctime: crate::time_t,
pub st_ctime_nsec: c_long,
#[cfg(musl32_time64)]
__st_ctim_pad: u32,
#[cfg(not(musl32_time64))]
pub st_ino: crate::ino_t,
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment on lines +8 to +9
cfg_if! {
if #[cfg(target_endian = "big")] {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of using cfg-if on big/little and then configuring on time_t inside, could you configure for musl32_time64 on the outside and configure big/little on each of the fields? This closer matches source and makes it easier to delete the non-64-bit version when that time comes.

Also, org nit: please put any cfg_if! invocations containing s! macros after the non-cfg s! macro (this is done in most files)

pub const IPC_STAT: c_int = if cfg!(musl32_time64) { 0x102 } else { 2 };
pub const IPC_INFO: c_int = 3;
pub const MSG_STAT: c_int = 11;
pub const MSG_STAT: c_int = if cfg!(musl32_time64) { 0x10b } else { 11 };
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment on lines -2789 to +2792
pub const SEM_STAT: c_int = 18;
pub const SEM_STAT: c_int = if cfg!(musl32_time64) { 0x112 } else { 18 };
pub const SEM_INFO: c_int = 19;
pub const SEM_STAT_ANY: c_int = 20;
pub const SEM_STAT_ANY: c_int = if cfg!(musl32_time64) { 0x114 } else { 20 };
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment on lines +4562 to +4563
// FIXME(linux): this is changed to separate sec/usec fields when time64 is enabled
(struct_ == "input_event" && field == "time")
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't defined in musl, why is the skip needed as part of this series? Or if the definition is wrong, shouldn't it be changed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CI Area: CI-related items O-arm O-linux O-linux-like O-mips O-musl O-powerpc O-riscv O-unix O-x86 S-waiting-on-author stable-nominated This PR should be considered for cherry-pick to libc's stable release branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants