Repository: denoland/deno
Pull request: fix(node): implement process.umask properly

## Summary
- `process.umask()` was a stub that always returned `0o22` — now it actually gets/sets the process umask via `op_fs_umask`
- Validates input with `parseFileMode` (supports numeric and octal string masks)
- Masks off bits above `0o777`
- Throws `ERR_WORKER_UNSUPPORTED_OPERATION` when setting from a worker thread
- Enabled `test-process-umask.js` and `test-process-umask-mask.js` node compat tests

Fixes #30255

## Test plan
- [x] `cargo test --test node_compat parallel -- --filter test-process-umask.js` passes
- [x] `cargo test --test node_compat parallel -- --filter test-process-umask-mask.js` passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changed files:
filename: ext/fs/std_fs.rs
patch: @@ -43,12 +43,23 @@ impl FileSystem for RealFs {
     std::env::set_current_dir(path).map_err(Into::into)
   }
 
-  #[cfg(not(unix))]
-  fn umask(&self, _mask: Option<u32>) -> FsResult<u32> {
-    // TODO implement umask for Windows
-    // see https://github.com/nodejs/node/blob/master/src/node_process_methods.cc
-    // and https://docs.microsoft.com/fr-fr/cpp/c-runtime-library/reference/umask?view=vs-2019
-    Err(FsError::NotSupported)
+  #[cfg(windows)]
+  fn umask(&self, mask: Option<u32>) -> FsResult<u32> {
+    unsafe extern "C" {
+      fn _umask(mask: std::ffi::c_int) -> std::ffi::c_int;
+    }
+    // SAFETY: `_umask` is a Windows CRT function that sets the file mode
+    // creation mask and returns the previous value.
+    unsafe {
+      let old = if let Some(mask) = mask {
+        _umask(mask as std::ffi::c_int)
+      } else {
+        let prev = _umask(0);
+        _umask(prev);
+        prev
+      };
+      Ok(old as u32)
+    }
   }
 
   #[cfg(unix)]

filename: ext/node/polyfills/process.ts
patch: @@ -8,6 +8,7 @@ import { core, internals, primordials } from "ext:core/mod.js";
 import { initializeDebugEnv } from "ext:deno_node/internal/util/debuglog.ts";
 import { format } from "ext:deno_node/internal/util/inspect.mjs";
 import {
+  op_fs_umask,
   op_getegid,
   op_geteuid,
   op_node_load_env_file,
@@ -26,6 +27,7 @@ import Module, { getBuiltinModule } from "node:module";
 import { report } from "ext:deno_node/internal/process/report.ts";
 import { onWarning } from "ext:deno_node/internal/process/warning.ts";
 import {
+  parseFileMode,
   validateNumber,
   validateObject,
   validateString,
@@ -37,6 +39,7 @@ import {
   ERR_INVALID_ARG_VALUE_RANGE,
   ERR_OUT_OF_RANGE,
   ERR_UNKNOWN_SIGNAL,
+  ERR_WORKER_UNSUPPORTED_OPERATION,
   errnoException,
   NodeTypeError,
 } from "ext:deno_node/internal/errors.ts";
@@ -159,13 +162,19 @@ export const exit = (code?: number | string) => {
 };
 
 /** https://nodejs.org/api/process.html#processumaskmask */
-export const umask = () => {
-  // Always return the system default umask value.
-  // We don't use Deno.umask here because it has a race
-  // condition bug.
-  // See https://github.com/denoland/deno_std/issues/1893#issuecomment-1032897779
-  return 0o22;
-};
+export function umask(mask?: number | string): number {
+  if (mask !== undefined) {
+    if (internals.__isWorkerThread) {
+      throw new ERR_WORKER_UNSUPPORTED_OPERATION("Setting process.umask()");
+    }
+    mask = parseFileMode(mask, "mask");
+    return op_fs_umask(mask & 0o777);
+  }
+  // Note: reading the umask without setting has an inherent race condition
+  // (two syscalls: set to 0 then restore). Node.js has the same issue and
+  // has deprecated process.umask() with no arguments.
+  return op_fs_umask(null);
+}
 
 export const abort = () => {
   op_process_abort();
@@ -878,13 +887,7 @@ process.binding = (name: BindingName) => {
 };
 
 /** https://nodejs.org/api/process.html#processumaskmask */
-process.umask = () => {
-  // Always return the system default umask value.
-  // We don't use Deno.umask here because it has a race
-  // condition bug.
-  // See https://github.com/denoland/deno_std/issues/1893#issuecomment-1032897779
-  return 0o22;
-};
+process.umask = umask;
 
 /** This method is removed on Windows */
 process.getgid = getgid;

filename: tests/node_compat/config.jsonc
patch: @@ -1088,6 +1088,8 @@
     "parallel/test-process-ppid.js": {},
     "parallel/test-process-really-exit.js": {},
     "parallel/test-process-release.js": {},
+    "parallel/test-process-umask-mask.js": {},
+    "parallel/test-process-umask.js": {},
     "parallel/test-process-uptime.js": {},
     "parallel/test-process-warning.js": {},
     "parallel/test-promise-handled-rejection-no-warning.js": {},