Skip to content

Commit 6298505

Browse files
authored
Merge pull request #102 from dartmouth-pbs/bf-chmod
BF: do not chmod files using hardcoded mode number
2 parents e6fdd93 + 5cc4fd3 commit 6298505

File tree

3 files changed

+71
-10
lines changed

3 files changed

+71
-10
lines changed

bin/heudiconv

Lines changed: 45 additions & 8 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
@@ -306,6 +314,35 @@ def find_files(regex, topdir=curdir, exclude=None, exclude_vcs=True, dirs=False)
306314
find_files.__doc__ %= (_VCS_REGEX,)
307315

308316

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

9641001
if custom_callable is not None:
9651002
custom_callable(*item)
@@ -1143,9 +1180,9 @@ def tuneup_bids_json_files(json_files):
11431180
lgr.error("Failed to open magnitude file: %s", exc)
11441181

11451182
# might have been made R/O already
1146-
os.chmod(json_phasediffname, 0o0664)
1183+
set_readonly(json_phasediffname, False)
11471184
json.dump(json_, open(json_phasediffname, 'w'), indent=2)
1148-
os.chmod(json_phasediffname, 0o0444)
1185+
set_readonly(json_phasediffname)
11491186

11501187
# phasediff one should contain two PhaseDiff's
11511188
# -- one for original amplitude and the other already replicating what is there
@@ -1219,15 +1256,15 @@ in that one though
12191256
if global_options['overwrite'] and os.path.lexists(scaninfo):
12201257
# TODO: handle annexed file case
12211258
if not os.path.islink(scaninfo):
1222-
os.chmod(scaninfo, 0o0660)
1259+
set_readonly(scaninfo, False)
12231260
res = embedfunc.run()
1224-
os.chmod(scaninfo, 0o0444)
1261+
set_readonly(scaninfo)
12251262
if with_prov:
12261263
g = res.provenance.rdf()
12271264
g.parse(prov_file,
12281265
format='turtle')
12291266
g.serialize(prov_file, format='turtle')
1230-
os.chmod(prov_file, 0o0440)
1267+
set_readonly(prov_file)
12311268
except Exception as exc:
12321269
lgr.error("Embedding failed: %s", str(exc))
12331270
os.chdir(cwd)
@@ -1244,10 +1281,10 @@ def treat_infofile(filename):
12441281
j_slim = slim_down_info(j)
12451282
j_pretty = json_dumps_pretty(j_slim, indent=2, sort_keys=True)
12461283

1247-
os.chmod(filename, 0o0664)
1284+
set_readonly(filename, False)
12481285
with open(filename, 'wt') as fp:
12491286
fp.write(j_pretty)
1250-
os.chmod(filename, 0o0444)
1287+
set_readonly(filename)
12511288

12521289

12531290
def convert_dicoms(sid,

tests/test_heuristics.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
import pytest
1313

14-
from datalad.api import Dataset
14+
try:
15+
from datalad.api import Dataset
16+
except ImportError: # pragma: no cover
17+
Dataset = None
1518

1619

1720
def test_smoke_converall(tmpdir):
@@ -28,6 +31,7 @@ def test_smoke_converall(tmpdir):
2831
"-d tests/data/{subject}/* -s 01-fmap_acq-3mm" # "old" way specifying subject
2932
# should produce the same results
3033
])
34+
@pytest.mark.skipif(Dataset is None, reason="no datalad")
3135
def test_dbic_bids_largely_smoke(tmpdir, heuristic, invocation):
3236
is_bids = True if heuristic == 'dbic_bids' else False
3337
arg = "-f heuristics/%s.py -c dcm2niix -o %s" % (heuristic, tmpdir)

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)