Skip to content

Commit 2885ab0

Browse files
committed
feat: add support for 32 bit time_t in windows
This commit adds support symmetric to that offered in Linux for using 32-bit `time_t` in Windows. This PR also removes some of the `skip()` functions in Windows, as it renames some symbols to properly reflect whether they are 32-bit or 64-bit compliant, and adds deprecation notices to functions that were are not supported by Microsoft anymore (as per their security guidelines.) There's a few functions that are altogether missing and yet do appear in the Windows CRT docs, but that's work for another PR.
1 parent 551afd1 commit 2885ab0

File tree

10 files changed

+127
-155
lines changed

10 files changed

+127
-155
lines changed

.github/workflows/ci.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ jobs:
128128
# env: { ARCH_BITS: 32, ARCH: i686 }
129129
- target: i686-pc-windows-msvc
130130
os: windows-2025
131+
env: { ARCH_BITS: 32, ARCH: i686 }
132+
- target: i686-pc-windows-msvc
133+
os: windows-2025
134+
env: { ARCH_BITS: 32, ARCH: i686, RUST_LIBC_UNSTABLE_WINDOWS_TIME32: 1 }
135+
artifact-tag: windows_time32
131136
- target: i686-unknown-linux-gnu
132137
- target: x86_64-pc-windows-gnu
133138
os: windows-2025

build.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
use std::env::VarError;
2-
use std::process::{
3-
Command,
4-
Output,
5-
};
6-
use std::{
7-
env,
8-
str,
9-
};
2+
use std::process::{Command, Output};
3+
use std::{env, str};
104

115
// List of cfgs this build script is allowed to set. The list is needed to support check-cfg, as we
126
// need to know all the possible cfgs that this script will set. If you need to set another cfg
@@ -31,6 +25,8 @@ const ALLOWED_CFGS: &[&str] = &[
3125
// Corresponds to `_REDIR_TIME64` in musl
3226
"musl32_time64",
3327
"vxworks_lt_25_09",
28+
// Corresponds to `_USE_32BIT_TIME_T` in Windows CRT
29+
"windows_use_time32",
3430
];
3531

3632
// Extra values to allow for check-cfg.
@@ -123,6 +119,11 @@ fn main() {
123119
}
124120
}
125121

122+
let windows_use_time32 = env::var("RUST_LIBC_UNSTABLE_WINDOWS_TIME32").is_ok();
123+
println!("cargo:rerun-if-env-changed=RUST_LIBC_UNSTABLE_WINDOWS_TIME32");
124+
if windows_use_time32 {
125+
set_cfg("windows_use_time32");
126+
}
126127
let linux_time_bits64 = env::var("RUST_LIBC_UNSTABLE_LINUX_TIME_BITS64").is_ok();
127128
println!("cargo:rerun-if-env-changed=RUST_LIBC_UNSTABLE_LINUX_TIME_BITS64");
128129
if linux_time_bits64 {

ci/install-rust.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ if [ -n "${INSTALL_RUST_SRC:-}" ]; then
3838
fi
3939

4040
if [ "$os" = "windows" ]; then
41-
if [ "${ARCH_BITS:-}" = "i686" ]; then
41+
if [ "${ARCH:-}" = "i686" ]; then
4242
echo "Install MinGW32"
4343
choco install mingw --x86 --force
4444
fi

ctest-test/src/t1.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
#![allow(dead_code)]
22

3-
use std::ffi::{
4-
c_char,
5-
c_double,
6-
c_int,
7-
c_long,
8-
c_uint,
9-
c_void,
10-
};
3+
use std::ffi::{c_char, c_double, c_int, c_long, c_uint, c_void};
114

125
pub type T1Foo = i32;
13-
pub const T1S: *const c_char = b"foo\0".as_ptr().cast();
6+
pub const T1S: *const c_char = c"foo".as_ptr().cast();
147

158
pub const T1N: i32 = 5;
169

ctest-test/src/t2.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
#![allow(non_camel_case_types)]
22

3-
use std::ffi::{
4-
c_char,
5-
c_int,
6-
};
3+
use std::ffi::{c_char, c_int};
74

85
pub type T2Foo = u32;
96
pub type T2Bar = u32;
@@ -33,7 +30,7 @@ pub union T2Union {
3330
pub const T2C: i32 = 5;
3431

3532
i! {
36-
pub const T2S: *const c_char = b"b\0".as_ptr().cast();
33+
pub const T2S: *const c_char = c"b".as_ptr().cast();
3734
}
3835

3936
extern "C" {

ctest/src/ffi_items.rs

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,14 @@
22
33
use std::ops::Deref;
44

5-
use syn::punctuated::Punctuated;
6-
use syn::visit::Visit;
7-
8-
use crate::{
9-
Abi,
10-
BoxStr,
11-
Const,
12-
Field,
13-
Fn,
14-
Parameter,
15-
Static,
16-
Struct,
17-
Type,
18-
Union,
5+
use syn::{
6+
Attribute, Expr, ExprLit, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemStatic,
7+
ItemConst, ItemForeignMod, ItemStruct, ItemType, ItemUnion, Lit, Meta, MetaNameValue, Pat,
8+
ReturnType, punctuated::Punctuated, visit::Visit,
199
};
2010

11+
use crate::{Abi, BoxStr, Const, Field, Fn, Parameter, Static, Struct, Type, Union};
12+
2113
/// Represents a collected set of top-level Rust items relevant to FFI generation or analysis.
2214
///
2315
/// Includes foreign functions/statics, type aliases, structs, unions, and constants.
@@ -105,7 +97,7 @@ fn collect_fields(fields: &Punctuated<syn::Field, syn::Token![,]>) -> Vec<Field>
10597
.collect()
10698
}
10799

108-
fn extract_single_link_name(attrs: &[syn::Attribute]) -> Option<BoxStr> {
100+
fn extract_single_link_name(attrs: &[Attribute]) -> Option<BoxStr> {
109101
let mut link_name_iter = attrs
110102
.iter()
111103
.filter(|attr| attr.path().is_ident("link_name"));
@@ -115,17 +107,17 @@ fn extract_single_link_name(attrs: &[syn::Attribute]) -> Option<BoxStr> {
115107
panic!("multiple `#[link_name = ...]` attributes found: {attr:?}");
116108
}
117109

118-
if let syn::Meta::NameValue(nv) = &link_name.meta
119-
&& let syn::Expr::Lit(expr_lit) = &nv.value
120-
&& let syn::Lit::Str(lit_str) = &expr_lit.lit
110+
if let Meta::NameValue(MetaNameValue { value, .. }) = &link_name.meta
111+
&& let Expr::Lit(ExprLit { lit, .. }) = value
112+
&& let Lit::Str(lit_str) = lit
121113
{
122114
return Some(lit_str.value().into_boxed_str());
123115
}
124116

125117
panic!("unrecognized `link_name` syntax: {link_name:?}");
126118
}
127119

128-
fn visit_foreign_item_fn(table: &mut FfiItems, i: &syn::ForeignItemFn, abi: &Abi) {
120+
fn visit_foreign_item_fn(table: &mut FfiItems, i: &ForeignItemFn, abi: &Abi) {
129121
let public = is_visible(&i.vis);
130122
let abi = abi.clone();
131123
let ident = i.sig.ident.to_string().into_boxed_str();
@@ -134,23 +126,22 @@ fn visit_foreign_item_fn(table: &mut FfiItems, i: &syn::ForeignItemFn, abi: &Abi
134126
.inputs
135127
.iter()
136128
.map(|arg| match arg {
137-
syn::FnArg::Typed(arg) => Parameter {
138-
ident: match arg.pat.deref() {
139-
syn::Pat::Ident(i) => i.ident.to_string().into_boxed_str(),
140-
_ => {
141-
unimplemented!("Foreign functions are unlikely to have any other pattern.")
142-
}
129+
FnArg::Typed(arg) => Parameter {
130+
ident: if let Pat::Ident(i) = arg.pat.deref() {
131+
i.ident.to_string().into_boxed_str()
132+
} else {
133+
unimplemented!("Foreign functions are unlikely to have any other pattern.");
143134
},
144135
ty: arg.ty.deref().clone(),
145136
},
146-
syn::FnArg::Receiver(_) => {
137+
FnArg::Receiver(_) => {
147138
unreachable!("Foreign functions can't have self/receiver parameters.")
148139
}
149140
})
150-
.collect::<Vec<_>>();
141+
.collect();
151142
let return_type = match &i.sig.output {
152-
syn::ReturnType::Default => None,
153-
syn::ReturnType::Type(_, ty) => Some(ty.deref().clone()),
143+
ReturnType::Default => None,
144+
ReturnType::Type(_, ty) => Some(ty.deref().clone()),
154145
};
155146
let link_name = extract_single_link_name(&i.attrs);
156147

@@ -164,7 +155,7 @@ fn visit_foreign_item_fn(table: &mut FfiItems, i: &syn::ForeignItemFn, abi: &Abi
164155
});
165156
}
166157

167-
fn visit_foreign_item_static(table: &mut FfiItems, i: &syn::ForeignItemStatic, abi: &Abi) {
158+
fn visit_foreign_item_static(table: &mut FfiItems, i: &ForeignItemStatic, abi: &Abi) {
168159
let public = is_visible(&i.vis);
169160
let abi = abi.clone();
170161
let ident = i.ident.to_string().into_boxed_str();
@@ -181,21 +172,21 @@ fn visit_foreign_item_static(table: &mut FfiItems, i: &syn::ForeignItemStatic, a
181172
}
182173

183174
impl<'ast> Visit<'ast> for FfiItems {
184-
fn visit_item_type(&mut self, i: &'ast syn::ItemType) {
175+
fn visit_item_type(&mut self, i: &'ast ItemType) {
185176
let public = is_visible(&i.vis);
186177
let ty = i.ty.deref().clone();
187178
let ident = i.ident.to_string().into_boxed_str();
188179

189180
self.aliases.push(Type { public, ident, ty });
190181
}
191182

192-
fn visit_item_struct(&mut self, i: &'ast syn::ItemStruct) {
183+
fn visit_item_struct(&mut self, i: &'ast ItemStruct) {
193184
let public = is_visible(&i.vis);
194185
let ident = i.ident.to_string().into_boxed_str();
195186
let fields = match &i.fields {
196-
syn::Fields::Named(fields) => collect_fields(&fields.named),
197-
syn::Fields::Unnamed(fields) => collect_fields(&fields.unnamed),
198-
syn::Fields::Unit => Vec::new(),
187+
Fields::Named(fields) => collect_fields(&fields.named),
188+
Fields::Unnamed(_) => Vec::new(),
189+
Fields::Unit => Vec::new(),
199190
};
200191

201192
self.structs.push(Struct {
@@ -205,7 +196,7 @@ impl<'ast> Visit<'ast> for FfiItems {
205196
});
206197
}
207198

208-
fn visit_item_union(&mut self, i: &'ast syn::ItemUnion) {
199+
fn visit_item_union(&mut self, i: &'ast ItemUnion) {
209200
let public = is_visible(&i.vis);
210201
let ident = i.ident.to_string().into_boxed_str();
211202
let fields = collect_fields(&i.fields.named);
@@ -217,15 +208,15 @@ impl<'ast> Visit<'ast> for FfiItems {
217208
});
218209
}
219210

220-
fn visit_item_const(&mut self, i: &'ast syn::ItemConst) {
211+
fn visit_item_const(&mut self, i: &'ast ItemConst) {
221212
let public = is_visible(&i.vis);
222213
let ident = i.ident.to_string().into_boxed_str();
223214
let ty = i.ty.deref().clone();
224215

225216
self.constants.push(Const { public, ident, ty });
226217
}
227218

228-
fn visit_item_foreign_mod(&mut self, i: &'ast syn::ItemForeignMod) {
219+
fn visit_item_foreign_mod(&mut self, i: &'ast ItemForeignMod) {
229220
// Because we need to store the ABI we can't directly visit the foreign
230221
// functions/statics.
231222

@@ -235,12 +226,12 @@ impl<'ast> Visit<'ast> for FfiItems {
235226
.name
236227
.clone()
237228
.map(|s| Abi::from(s.value().as_str()))
238-
.unwrap_or_else(|| Abi::C);
229+
.unwrap_or(Abi::C);
239230

240231
for item in &i.items {
241232
match item {
242-
syn::ForeignItem::Fn(function) => visit_foreign_item_fn(self, function, &abi),
243-
syn::ForeignItem::Static(static_variable) => {
233+
ForeignItem::Fn(function) => visit_foreign_item_fn(self, function, &abi),
234+
ForeignItem::Static(static_variable) => {
244235
visit_foreign_item_static(self, static_variable, &abi)
245236
}
246237
_ => (),

libc-test/build.rs

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
11
#![deny(warnings)]
22
#![allow(clippy::match_like_matches_macro)]
33

4-
use std::fs::File;
5-
use std::io::{
6-
BufRead,
7-
BufReader,
8-
BufWriter,
9-
Write,
10-
};
11-
use std::path::{
12-
Path,
13-
PathBuf,
14-
};
154
use std::{
165
env,
17-
io,
6+
fs::File,
7+
io::{self, BufRead, BufReader, BufWriter, Write},
8+
path::{Path, PathBuf},
189
};
1910

2011
fn do_cc() {
@@ -76,7 +67,7 @@ fn do_ctest() {
7667
t if t.contains("windows") => test_windows(t),
7768
t if t.contains("vxworks") => test_vxworks(t),
7869
t if t.contains("nto-qnx") => test_neutrino(t),
79-
t if t.contains("aix") => return test_aix(t),
70+
t if t.contains("aix") => test_aix(t),
8071
t => panic!("unknown target {t}"),
8172
}
8273
}
@@ -735,7 +726,6 @@ fn test_cygwin(target: &str) {
735726
fn test_windows(target: &str) {
736727
assert!(target.contains("windows"));
737728
let gnu = target.contains("gnu");
738-
let i686 = target.contains("i686");
739729

740730
let mut cfg = ctest_cfg();
741731

@@ -744,6 +734,11 @@ fn test_windows(target: &str) {
744734
}
745735
cfg.define("_WIN32_WINNT", Some("0x8000"));
746736

737+
if env::var("RUST_LIBC_UNSTABLE_WINDOWS_TIME32").is_ok() {
738+
cfg.define("_USE_32BIT_TIME_T", None);
739+
cfg.cfg("windows_use_time32", None);
740+
}
741+
747742
headers!(
748743
cfg,
749744
"direct.h",
@@ -772,23 +767,18 @@ fn test_windows(target: &str) {
772767
// Just pass all these through, no need for a "struct" prefix
773768
"FILE" | "DIR" | "Dl_info" => ty.to_string().into(),
774769
t if t.ends_with("_t") => t.to_string().into(),
775-
// Windows uppercase structs don't have `struct` in fr.into()ont:
770+
// Windows uppercase structs don't have `struct` in front:
776771
t if ty.chars().next().unwrap().is_uppercase() => t.to_string().into(),
777-
"stat" => "struct __stat64".to_string().into(),
778-
"utimbuf" => "struct __utimbuf64".to_string().into(),
779772
_ => None,
780773
}
781774
});
782-
cfg.rename_type(move |ty| {
783-
match ty {
784-
// FIXME(windows): these don't exist:
785-
"time64_t" => "__time64_t".to_string().into(),
786-
"ssize_t" => "SSIZE_T".to_string().into(),
787775

788-
"sighandler_t" if !gnu => "_crt_signal_t".to_string().into(),
789-
"sighandler_t" if gnu => "__p_sig_fn_t".to_string().into(),
790-
_ => None,
791-
}
776+
cfg.rename_type(move |ty| match ty {
777+
"time64_t" => "__time64_t".to_string().into(),
778+
"ssize_t" => "SSIZE_T".to_string().into(),
779+
"sighandler_t" if !gnu => "_crt_signal_t".to_string().into(),
780+
"sighandler_t" if gnu => "__p_sig_fn_t".to_string().into(),
781+
_ => None,
792782
});
793783

794784
cfg.rename_fn(move |func| {
@@ -800,19 +790,9 @@ fn test_windows(target: &str) {
800790
cfg.skip_alias(move |alias| match alias.ident() {
801791
"SSIZE_T" if !gnu => true,
802792
"ssize_t" if !gnu => true,
803-
// FIXME(windows): The size and alignment of this type are incorrect
804-
"time_t" if gnu && i686 => true,
805793
_ => false,
806794
});
807795

808-
cfg.skip_struct(move |struct_| {
809-
match struct_.ident() {
810-
// FIXME(windows): The size and alignment of this struct are incorrect
811-
"timespec" if gnu && i686 => true,
812-
_ => false,
813-
}
814-
});
815-
816796
cfg.skip_const(move |constant| {
817797
match constant.ident() {
818798
// FIXME(windows): API error:
@@ -1380,7 +1360,6 @@ fn test_netbsd(target: &str) {
13801360
});
13811361

13821362
cfg.skip_fn(move |func| {
1383-
#[expect(clippy::wildcard_in_or_patterns)]
13841363
match func.ident() {
13851364
// FIXME(netbsd): Look into setting `_POSIX_C_SOURCE` to enable this
13861365
"qsort_r" => true,
@@ -3085,11 +3064,8 @@ fn test_emscripten(target: &str) {
30853064
});
30863065

30873066
cfg.skip_alias(|ty| {
3088-
match ty.ident() {
3089-
// LFS64 types have been removed in Emscripten 3.1.44
3090-
// https://github.com/emscripten-core/emscripten/pull/19812
3091-
ty => ty.ends_with("64") || ty.ends_with("64_t"),
3092-
}
3067+
let ty = ty.ident();
3068+
ty.ends_with("64") || ty.ends_with("64_t")
30933069
});
30943070

30953071
cfg.skip_struct(move |struct_| {

0 commit comments

Comments
 (0)