Skip to content

Commit 2a91f0f

Browse files
committed
Skip chmod on non-regular files like /dev/null in streaming output
1 parent 538b1de commit 2a91f0f

4 files changed

Lines changed: 43 additions & 2 deletions

File tree

awscli/customizations/s3events.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"""Add S3 specific event streaming output arg."""
1414

1515
import os
16+
import stat
1617

1718
from awscli.arguments import CustomArgument
1819

@@ -124,7 +125,9 @@ def save_file(self, parsed, **kwargs):
124125
fd = os.open(
125126
self._output_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600
126127
)
127-
os.chmod(self._output_file, 0o600)
128+
if stat.S_ISREG(os.fstat(fd).st_mode):
129+
# Only chmod regular files; skip devices like /dev/null
130+
os.chmod(self._output_file, 0o600)
128131
with os.fdopen(fd, 'wb') as fp:
129132
for event in event_stream:
130133
if 'Records' in event:

awscli/customizations/streamingoutputarg.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
import os
14+
import stat
1415

1516
from botocore.model import Shape
1617

@@ -109,7 +110,9 @@ def save_file(self, parsed, **kwargs):
109110
fd = os.open(
110111
self._output_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600
111112
)
112-
os.chmod(self._output_file, 0o600)
113+
if stat.S_ISREG(os.fstat(fd).st_mode):
114+
# Only chmod regular files; skip devices like /dev/null
115+
os.chmod(self._output_file, 0o600)
113116
with os.fdopen(fd, 'wb') as fp:
114117
data = body.read(buffer_size)
115118
while data:

tests/functional/s3api/test_select_object_content.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,27 @@ def test_output_file_permissions(self):
108108
# Mask file type bits to isolate permission bits (rwxrwxrwx)
109109
self.assertEqual(os.stat(filename).st_mode & 0o777, 0o600)
110110

111+
@skip_if_windows('chmod is not supported on Windows')
112+
def test_output_does_not_chmod_non_regular_files(self):
113+
cmdline = self.prefix + [
114+
'--bucket',
115+
'mybucket',
116+
'--key',
117+
'mykey',
118+
'--expression',
119+
'SELECT * FROM S3Object',
120+
'--expression-type',
121+
'SQL',
122+
'--input-serialization',
123+
'{"CSV": {}}',
124+
'--output-serialization',
125+
'{"CSV": {}}',
126+
'/dev/null',
127+
]
128+
original_mode = os.stat('/dev/null').st_mode & 0o777
129+
self.assert_params_for_cmd(cmdline, ignore_params=True)
130+
self.assertEqual(os.stat('/dev/null').st_mode & 0o777, original_mode)
131+
111132
def test_errors_are_propagated(self):
112133
self.http_response.status_code = 400
113134
self.parsed_response = {

tests/functional/test_streaming_output.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,17 @@ def test_streaming_output_file_permissions(self):
6262
self.assert_params_for_cmd(cmdline % outpath, ignore_params=True)
6363
# Mask file type bits to isolate permission bits (rwxrwxrwx)
6464
self.assertEqual(os.stat(outpath).st_mode & 0o777, 0o600)
65+
66+
@skip_if_windows('chmod is not supported on Windows')
67+
def test_streaming_output_does_not_chmod_non_regular_files(self):
68+
cmdline = (
69+
'kinesis-video-media get-media --stream-name test-stream '
70+
'--start-selector StartSelectorType=EARLIEST %s'
71+
)
72+
self.parsed_response = {
73+
'ContentType': 'video/webm',
74+
'Payload': BytesIO(b'testbody'),
75+
}
76+
original_mode = os.stat('/dev/null').st_mode & 0o777
77+
self.assert_params_for_cmd(cmdline % '/dev/null', ignore_params=True)
78+
self.assertEqual(os.stat('/dev/null').st_mode & 0o777, original_mode)

0 commit comments

Comments
 (0)