|
| 1 | +// Flags: --expose-internals |
1 | 2 | // Copyright Joyent, Inc. and other Node contributors. |
2 | 3 | // |
3 | 4 | // Permission is hereby granted, free of charge, to any person obtaining a |
|
22 | 23 | 'use strict'; |
23 | 24 | const common = require('../common'); |
24 | 25 | const assert = require('assert'); |
| 26 | +const { internalBinding } = require('internal/test/binding'); |
| 27 | +const child_process = require('child_process'); |
25 | 28 | const fs = require('fs'); |
26 | 29 | const path = require('path'); |
27 | 30 | const { isMainThread } = require('worker_threads'); |
@@ -184,6 +187,63 @@ function nextdir() { |
184 | 187 | })); |
185 | 188 | } |
186 | 189 |
|
| 190 | +// `mkdirp` when folder was readonly (EROFS) - mocked via internalBinding. |
| 191 | +// Verifies that EROFS is propagated correctly without requiring root/sudo. |
| 192 | +{ |
| 193 | + const fsBinding = internalBinding('fs'); |
| 194 | + const originalMkdir = fsBinding.mkdir; |
| 195 | + fsBinding.mkdir = function(p) { |
| 196 | + const err = new Error(`EROFS: read-only file system, mkdir '${p}'`); |
| 197 | + err.code = 'EROFS'; |
| 198 | + err.syscall = 'mkdir'; |
| 199 | + err.path = p; |
| 200 | + throw err; |
| 201 | + }; |
| 202 | + try { |
| 203 | + const pathname = path.join(tmpdir.path, nextdir(), nextdir()); |
| 204 | + assert.throws( |
| 205 | + () => { fs.mkdirSync(pathname, { recursive: true }); }, |
| 206 | + { code: 'EROFS', message: /EROFS:.*mkdir/, name: 'Error', syscall: 'mkdir' } |
| 207 | + ); |
| 208 | + } finally { |
| 209 | + fsBinding.mkdir = originalMkdir; |
| 210 | + } |
| 211 | +} |
| 212 | + |
| 213 | +// `mkdirp` when folder was readonly. |
| 214 | +if (common.isLinux) { |
| 215 | + const roTmpfsPath = path.join(tmpdir.path, 'ro-tmpfs'); |
| 216 | + fs.mkdirSync(roTmpfsPath); |
| 217 | + |
| 218 | + const { status, stderr } = child_process.spawnSync( |
| 219 | + 'sudo', ['-n', 'mount', '-t', 'tmpfs', '-o', 'ro', 'tmpfs', roTmpfsPath], |
| 220 | + { stdio: 'pipe', encoding: 'utf8' } |
| 221 | + ); |
| 222 | + |
| 223 | + if (status !== 0) { |
| 224 | + console.warn( |
| 225 | + 'Cannot test EROFS: passwordless sudo required to mount read-only tmpfs. ' + |
| 226 | + `Mount failed with status ${status}: ${stderr}` |
| 227 | + ); |
| 228 | + } else { |
| 229 | + try { |
| 230 | + const pathname = path.join(roTmpfsPath, nextdir()); |
| 231 | + assert.throws( |
| 232 | + () => { fs.mkdirSync(pathname, { recursive: true }); }, |
| 233 | + { |
| 234 | + code: 'EROFS', |
| 235 | + message: /EROFS:.*mkdir/, |
| 236 | + name: 'Error', |
| 237 | + syscall: 'mkdir', |
| 238 | + } |
| 239 | + ); |
| 240 | + } finally { |
| 241 | + child_process.spawnSync('sudo', ['-n', 'umount', roTmpfsPath]); |
| 242 | + } |
| 243 | + } |
| 244 | + fs.rmdirSync(roTmpfsPath); |
| 245 | +} |
| 246 | + |
187 | 247 | // `mkdirp` when path is a file. |
188 | 248 | { |
189 | 249 | const pathname = tmpdir.resolve(nextdir(), nextdir()); |
|
0 commit comments