Skip to content

std.fs.cwd() returns a stateless descriptor that changes with subsequent setAsCwd() calls #22763

@rootbeer

Description

@rootbeer

Zig Version

0.14.0-dev.3029+bebd8d7ff

Steps to Reproduce and Observed Behavior

I'm fixing some of Zig's Posix file tests, and they change the current working directory (CWD), so to protect subsequent tests I tried starting the test with:

    // Restore default CWD at end of test.
    const orig_cwd = try std.fs.cwd();
    defer orig_cwd.setAsCwd() catch unreachable;

I read this code as using defer to restore the current working directory to the original value after the main part of the test has completed.

However, this doesn't work (on Linux**) because the CWD returned is a stateless file descriptor that means "current working directory" (Posix's AT_FDCWD). So the code in the defer ends up being a no-op.

To capture a stateful "cwd" descriptor I can open ., and now the deferred restore works as I expect (but now I also have a real file descriptor that I have to close at some point):

    var orig_cwd = try std.fs.cwd().openDir(".", .{});
    defer orig_cwd.setAsCwd() catch unreachable;

This problem came up tangentially in #9688 and #17616 and #15592. While I can see how the current semantics of cwd() are slick and efficient (its basically the entrypoint for all relative-path file operations), this does seem like very surprising behavior to someone expecting the Dir returned from std.fs.cwd() to act like other stateful file descriptor-backed handles. If the current semantics are correct as-is, then at least the doc needs an update on cwd(). And probably on setAsCwd() to say it will transparently "update" results from earlier cwd() calls. Or remove setAsCwd()?

Here's a full test to reproduce the problem:

const std = @import("std");

test "first" {
    const orig_cwd = std.fs.cwd(); // save off CWD handle

    var orig_path_buf: [std.fs.max_path_bytes]u8 = undefined;
    const orig_path = try std.posix.getcwd(orig_path_buf[0..]);

    const pdir = try std.fs.cwd().openDir("..", .{});
    try pdir.setAsCwd(); // change CWD to something new

    // ... in a real test do something here...

    try orig_cwd.setAsCwd(); // "restore" the CWD to the original

    var after_path_buf: [std.fs.max_path_bytes]u8 = undefined;
    const after_path = try std.posix.getcwd(after_path_buf[0..]);

    try std.testing.expectEqualSlices(u8, orig_path, after_path);
}

** Windows uses windows.peb().ProcessParameters.CurrentDirectory.Handle for fs.cwd(), and I'm not sure if that has the same semantics as AT_FDCWD on Linux or if its stateful.

Expected Behavior

The test above should pass. Or the doc on cwd() should call out that its always whatever the CWD is, and is not a stable handle to the current CWD.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behavior

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions