Skip to content

Commit 975aeba

Browse files
committed
BF: set_readonly helper to set or reset file to/from readonly mode
1 parent c11a92c commit 975aeba

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

bin/heudiconv

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ from random import sample
5353
MIN_VERSIONS = {
5454
'datalad': '0.7'
5555
}
56+
57+
# Some variables for reuse
58+
import stat
59+
60+
ALL_CAN_WRITE = (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
61+
ALL_CAN_READ = (stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
62+
assert ALL_CAN_READ >> 1 == ALL_CAN_WRITE # Assumption in the code
63+
5664
PY3 = sys.version_info[0] >= 3
5765

5866
import logging
@@ -305,6 +313,35 @@ def find_files(regex, topdir=curdir, exclude=None, exclude_vcs=True, dirs=False)
305313
find_files.__doc__ %= (_VCS_REGEX,)
306314

307315

316+
def set_readonly(path, read_only=True):
317+
"""Make file read only or writeable while preserving "access levels"
318+
319+
So if file was not readable by "others" it should remain not readable
320+
by others
321+
322+
Parameters
323+
----------
324+
path : str
325+
read_only : bool, optional
326+
If True (default) - would make it read-only. If False, would make it
327+
writeable for levels where it is readable
328+
"""
329+
# get current permissions
330+
perms = stat.S_IMODE(os.lstat(path).st_mode)
331+
# set new permissions
332+
if read_only:
333+
new_perms = perms & (~ALL_CAN_WRITE)
334+
else:
335+
# need to set only for those which had read bit set
336+
# read bit is <<1 away from write bit
337+
whocanread = perms & ALL_CAN_READ
338+
thosecanwrite = whocanread >> 1
339+
new_perms = perms | thosecanwrite
340+
# apply and return those target permissions
341+
os.chmod(path, new_perms)
342+
return new_perms
343+
344+
308345
def group_dicoms_into_seqinfos(
309346
files, file_filter=None, dcmfilter=None, grouping='studyUID'
310347
):

tests/test_main.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import csv
22
import os
33
import pytest
4+
import stat
45
import sys
56

67
from mock import patch
@@ -194,4 +195,23 @@ def test__find_subj_ses():
194195
assert heudiconv._find_subj_ses('950_bids_test4/sub-phantom1sid1/fmap/sub-phantom1sid1_acq-3mm_phasediff.json') == ('phantom1sid1', None)
195196
assert heudiconv._find_subj_ses('sub-s1/ses-s1/fmap/sub-s1_ses-s1_acq-3mm_phasediff.json') == ('s1', 's1')
196197
assert heudiconv._find_subj_ses('sub-s1/ses-s1/fmap/sub-s1_ses-s1_acq-3mm_phasediff.json') == ('s1', 's1')
197-
assert heudiconv._find_subj_ses('fmap/sub-01-fmap_acq-3mm_acq-3mm_phasediff.nii.gz') == ('01', None)
198+
assert heudiconv._find_subj_ses('fmap/sub-01-fmap_acq-3mm_acq-3mm_phasediff.nii.gz') == ('01', None)
199+
200+
201+
def test_make_readonly(tmpdir):
202+
# we could test it all without torturing a poor file, but for going all
203+
# the way, let's do it on a file
204+
path = tmpdir.join('f')
205+
pathname = str(path)
206+
with open(pathname, 'w'):
207+
pass
208+
for orig, ro, rw in [
209+
(0o600, 0o400, 0o600), # fully returned
210+
(0o624, 0o404, 0o606), # it will not get write bit where it is not readable
211+
(0o1777, 0o1555, 0o1777), # and other bits should be preserved
212+
]:
213+
os.chmod(pathname, orig)
214+
assert heudiconv.set_readonly(pathname) == ro
215+
assert stat.S_IMODE(os.lstat(pathname).st_mode) == ro
216+
# and it should go back if we set it back to non-read_only
217+
assert heudiconv.set_readonly(pathname, read_only=False) == rw

0 commit comments

Comments
 (0)