17
17
import errno
18
18
import os
19
19
import sys
20
- from collections import namedtuple
21
20
from stat import (
22
21
S_ISDIR ,
23
22
)
43
42
is_root ,
44
43
PERM_READ ,
45
44
PERM_WRITE ,
45
+ _OpenModes ,
46
46
)
47
47
48
48
if TYPE_CHECKING :
49
49
from pyfakefs .fake_filesystem import FakeFilesystem
50
50
51
51
52
- _OpenModes = namedtuple (
53
- "_OpenModes" ,
54
- "must_exist can_read can_write truncate append must_not_exist" ,
55
- )
56
-
57
52
_OPEN_MODE_MAP = {
58
53
# mode name:(file must exist, can read, can write,
59
54
# truncate, append, must not exist)
@@ -148,6 +143,13 @@ def call(
148
143
raise ValueError ("binary mode doesn't take an encoding argument" )
149
144
150
145
newline , open_modes = self ._handle_file_mode (mode , newline , open_modes )
146
+ opened_as_fd = isinstance (file_ , int )
147
+ if opened_as_fd and not helpers .IS_PYPY :
148
+ fd : int = cast (int , file_ )
149
+ # cannot open the same file descriptor twice, except in PyPy
150
+ for f in self .filesystem .get_open_files (fd ):
151
+ if isinstance (f , FakeFileWrapper ) and f .opened_as_fd :
152
+ raise OSError (errno .EBADF , "Bad file descriptor" )
151
153
152
154
# the pathlib opener is defined in a Path instance that may not be
153
155
# patched under some circumstances; as it just calls standard open(),
@@ -157,7 +159,9 @@ def call(
157
159
# here as if directly passed
158
160
file_ = opener (file_ , self ._open_flags_from_open_modes (open_modes ))
159
161
160
- file_object , file_path , filedes , real_path = self ._handle_file_arg (file_ )
162
+ file_object , file_path , filedes , real_path , can_write = self ._handle_file_arg (
163
+ file_
164
+ )
161
165
if file_object is None and file_path is None :
162
166
# file must be a fake pipe wrapper, find it...
163
167
if (
@@ -176,7 +180,7 @@ def call(
176
180
existing_wrapper .can_write ,
177
181
mode ,
178
182
)
179
- file_des = self .filesystem ._add_open_file (wrapper )
183
+ file_des = self .filesystem .add_open_file (wrapper )
180
184
wrapper .filedes = file_des
181
185
return wrapper
182
186
@@ -197,7 +201,11 @@ def call(
197
201
198
202
assert real_path is not None
199
203
file_object = self ._init_file_object (
200
- file_object , file_path , open_modes , real_path
204
+ file_object ,
205
+ file_path ,
206
+ open_modes ,
207
+ real_path ,
208
+ check_file_permission = not opened_as_fd ,
201
209
)
202
210
203
211
if S_ISDIR (file_object .st_mode ):
@@ -218,7 +226,7 @@ def call(
218
226
fakefile = FakeFileWrapper (
219
227
file_object ,
220
228
file_path ,
221
- update = open_modes .can_write ,
229
+ update = open_modes .can_write and can_write ,
222
230
read = open_modes .can_read ,
223
231
append = open_modes .append ,
224
232
delete_on_close = self ._delete_on_close ,
@@ -230,6 +238,7 @@ def call(
230
238
errors = errors ,
231
239
buffering = buffering ,
232
240
raw_io = self .raw_io ,
241
+ opened_as_fd = opened_as_fd ,
233
242
)
234
243
if filedes is not None :
235
244
fakefile .filedes = filedes
@@ -238,7 +247,7 @@ def call(
238
247
assert open_files_list is not None
239
248
open_files_list .append (fakefile )
240
249
else :
241
- fakefile .filedes = self .filesystem ._add_open_file (fakefile )
250
+ fakefile .filedes = self .filesystem .add_open_file (fakefile )
242
251
return fakefile
243
252
244
253
@staticmethod
@@ -267,16 +276,25 @@ def _init_file_object(
267
276
file_path : AnyStr ,
268
277
open_modes : _OpenModes ,
269
278
real_path : AnyString ,
279
+ check_file_permission : bool ,
270
280
) -> FakeFile :
271
281
if file_object :
272
- if not is_root () and (
273
- (open_modes .can_read and not file_object .has_permission (PERM_READ ))
274
- or (open_modes .can_write and not file_object .has_permission (PERM_WRITE ))
282
+ if (
283
+ check_file_permission
284
+ and not is_root ()
285
+ and (
286
+ (open_modes .can_read and not file_object .has_permission (PERM_READ ))
287
+ or (
288
+ open_modes .can_write
289
+ and not file_object .has_permission (PERM_WRITE )
290
+ )
291
+ )
275
292
):
276
293
self .filesystem .raise_os_error (errno .EACCES , file_path )
277
294
if open_modes .can_write :
278
295
if open_modes .truncate :
279
296
file_object .set_contents ("" )
297
+ file_object
280
298
else :
281
299
if open_modes .must_exist :
282
300
self .filesystem .raise_os_error (errno .ENOENT , file_path )
@@ -304,16 +322,21 @@ def _init_file_object(
304
322
305
323
def _handle_file_arg (
306
324
self , file_ : Union [AnyStr , int ]
307
- ) -> Tuple [Optional [FakeFile ], Optional [AnyStr ], Optional [int ], Optional [AnyStr ]]:
325
+ ) -> Tuple [
326
+ Optional [FakeFile ], Optional [AnyStr ], Optional [int ], Optional [AnyStr ], bool
327
+ ]:
308
328
file_object = None
309
329
if isinstance (file_ , int ):
310
330
# opening a file descriptor
311
331
filedes : int = file_
312
332
wrapper = self .filesystem .get_open_file (filedes )
333
+ can_write = True
313
334
if isinstance (wrapper , FakePipeWrapper ):
314
- return None , None , filedes , None
335
+ return None , None , filedes , None , can_write
315
336
if isinstance (wrapper , FakeFileWrapper ):
316
337
self ._delete_on_close = wrapper .delete_on_close
338
+ can_write = wrapper .allow_update
339
+
317
340
file_object = cast (
318
341
FakeFile , self .filesystem .get_open_file (filedes ).get_object ()
319
342
)
@@ -324,6 +347,7 @@ def _handle_file_arg(
324
347
cast (AnyStr , path ), # pytype: disable=invalid-annotation
325
348
filedes ,
326
349
cast (AnyStr , path ), # pytype: disable=invalid-annotation
350
+ can_write ,
327
351
)
328
352
329
353
# open a file file by path
@@ -337,7 +361,7 @@ def _handle_file_arg(
337
361
file_object = self .filesystem .get_object_from_normpath (
338
362
real_path , check_read_perm = False
339
363
)
340
- return file_object , file_path , None , real_path
364
+ return file_object , file_path , None , real_path , True
341
365
342
366
def _handle_file_mode (
343
367
self ,
0 commit comments