Skip to content

Commit c30f150

Browse files
committed
fix os.fchmod
1 parent d6d5b66 commit c30f150

File tree

1 file changed

+72
-4
lines changed
  • crates/vm/src/stdlib

1 file changed

+72
-4
lines changed

crates/vm/src/stdlib/nt.rs

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,9 @@ pub(crate) mod module {
191191
}
192192

193193
#[derive(FromArgs)]
194-
struct ChmodArgs {
194+
struct ChmodArgs<'a> {
195195
#[pyarg(any)]
196-
path: OsPath,
196+
path: OsPathOrFd<'a>,
197197
#[pyarg(any)]
198198
mode: u32,
199199
#[pyarg(flatten)]
@@ -202,17 +202,85 @@ pub(crate) mod module {
202202
follow_symlinks: OptionalArg<bool>,
203203
}
204204

205+
const S_IWRITE: u32 = 128;
206+
207+
fn fchmod_impl(fd: i32, mode: u32, vm: &VirtualMachine) -> PyResult<()> {
208+
use windows_sys::Win32::Storage::FileSystem::{
209+
FILE_BASIC_INFO, FileBasicInfo, GetFileInformationByHandleEx,
210+
SetFileInformationByHandle,
211+
};
212+
213+
// Get Windows HANDLE from fd
214+
let borrowed = unsafe { crt_fd::Borrowed::borrow_raw(fd) };
215+
let handle = crt_fd::as_handle(borrowed).map_err(|e| e.to_pyexception(vm))?;
216+
let hfile = handle.as_raw_handle() as Foundation::HANDLE;
217+
218+
// Get current file info
219+
let mut info: FILE_BASIC_INFO = unsafe { std::mem::zeroed() };
220+
let ret = unsafe {
221+
GetFileInformationByHandleEx(
222+
hfile,
223+
FileBasicInfo,
224+
&mut info as *mut _ as *mut _,
225+
std::mem::size_of::<FILE_BASIC_INFO>() as u32,
226+
)
227+
};
228+
if ret == 0 {
229+
return Err(vm.new_last_os_error());
230+
}
231+
232+
// Modify readonly attribute based on S_IWRITE bit
233+
if mode & S_IWRITE != 0 {
234+
info.FileAttributes &= !FileSystem::FILE_ATTRIBUTE_READONLY;
235+
} else {
236+
info.FileAttributes |= FileSystem::FILE_ATTRIBUTE_READONLY;
237+
}
238+
239+
// Set the new attributes
240+
let ret = unsafe {
241+
SetFileInformationByHandle(
242+
hfile,
243+
FileBasicInfo,
244+
&info as *const _ as *const _,
245+
std::mem::size_of::<FILE_BASIC_INFO>() as u32,
246+
)
247+
};
248+
if ret == 0 {
249+
return Err(vm.new_last_os_error());
250+
}
251+
252+
Ok(())
253+
}
254+
205255
#[pyfunction]
206-
fn chmod(args: ChmodArgs, vm: &VirtualMachine) -> PyResult<()> {
256+
fn fchmod(fd: i32, mode: u32, vm: &VirtualMachine) -> PyResult<()> {
257+
fchmod_impl(fd, mode, vm)
258+
}
259+
260+
#[pyfunction]
261+
fn chmod(args: ChmodArgs<'_>, vm: &VirtualMachine) -> PyResult<()> {
207262
let ChmodArgs {
208263
path,
209264
mode,
210265
dir_fd,
211266
follow_symlinks,
212267
} = args;
213-
const S_IWRITE: u32 = 128;
214268
let [] = dir_fd.0;
215269

270+
// If path is a file descriptor, use fchmod
271+
if let OsPathOrFd::Fd(fd) = path {
272+
if follow_symlinks.into_option().is_some() {
273+
return Err(vm.new_value_error(
274+
"chmod: follow_symlinks is not supported with fd argument".to_owned(),
275+
));
276+
}
277+
return fchmod_impl(fd.as_raw(), mode, vm);
278+
}
279+
280+
let OsPathOrFd::Path(path) = path else {
281+
unreachable!()
282+
};
283+
216284
// On Windows, os.chmod behavior differs based on whether follow_symlinks is explicitly provided:
217285
// - Not provided (default): use SetFileAttributesW on the path directly (doesn't follow symlinks)
218286
// - Explicitly True: resolve symlink first, then apply permissions to target

0 commit comments

Comments
 (0)