multiSSH3 6.35__tar.gz → 6.36__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {multissh3-6.35/multiSSH3.egg-info → multissh3-6.36}/PKG-INFO +3 -3
- {multissh3-6.35 → multissh3-6.36}/README.md +2 -2
- {multissh3-6.35 → multissh3-6.36}/mssh_complete.py +23 -4
- {multissh3-6.35 → multissh3-6.36/multiSSH3.egg-info}/PKG-INFO +3 -3
- {multissh3-6.35 → multissh3-6.36}/multiSSH3.py +27 -5
- {multissh3-6.35 → multissh3-6.36}/tests/test_harness_smoke.py +13 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_runtime_dir.py +28 -0
- {multissh3-6.35 → multissh3-6.36}/LICENSE +0 -0
- {multissh3-6.35 → multissh3-6.36}/MANIFEST.in +0 -0
- {multissh3-6.35 → multissh3-6.36}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-6.35 → multissh3-6.36}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-6.35 → multissh3-6.36}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-6.35 → multissh3-6.36}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-6.35 → multissh3-6.36}/pytest.ini +0 -0
- {multissh3-6.35 → multissh3-6.36}/setup.cfg +0 -0
- {multissh3-6.35 → multissh3-6.36}/setup.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/bench_complete.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/complete_shell_harness.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/conftest.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/data/run_command_golden.json +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/shells/Dockerfile.bash5.1 +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/shells/Dockerfile.bash5.3 +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/shells/Dockerfile.centos7 +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/shells/Dockerfile.current +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/shells/Dockerfile.legacy +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/shells/Dockerfile.modern +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/shells/run_matrix.sh +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_cli_branches.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_cli_option_words.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_cli_processing.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_closed_output.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_color_helpers.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_command_terminal.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_compact_hostnames.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_blesh.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_cli.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_context.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_diagnose.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_editor.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_engine.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_eval.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_hosts.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_hoststate.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_index.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_keys.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_lex.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_mssh_helpers.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_password_spellings.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_picker.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_probe.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_shells.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_start.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_store.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_suggest.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_terminal.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_untrusted.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_complete_zsh_options.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_config_cli.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_config_validate.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_config_values.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_control_master_config.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_copy_id.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_curses_branches.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_curses_event_loop.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_curses_tui.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_curses_wide_chars.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_expand_hostnames.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_file_trust.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_helpers_extra.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_history_replay.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_host.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_host_validity.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_hosts_file.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_identities_only.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_ipmi_lockout_wait.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_ipmi_logins.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_key_path_home.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_login_drop_retry.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_main_lifecycle.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_narrow_terminal.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_no_hosts.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_option_no_effect.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_options_after_command.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_output_merge.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_output_robustness.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_packaging.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_password_handling.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_passwords_out_of_ps.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_run_command.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_run_command_branches.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_run_command_characterization.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_run_command_exit.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_run_on_hosts.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_run_on_hosts_modes.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_runtime_branch_matrix.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_shared_lines.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_smoke_optional.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_ssh_error_text.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_ssh_timeouts.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_sshpass_exit.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_start_run_on_hosts.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_stopped_commands.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_stream_reader.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_streamed_output.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_tui_ssh_notices.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_unavailable_hosts.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_underscore_settings.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_waiting.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/test_wide_wrap.py +0 -0
- {multissh3-6.35 → multissh3-6.36}/tests/vt_grid.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multiSSH3
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.36
|
|
4
4
|
Summary: Run commands on multiple hosts via SSH
|
|
5
5
|
Home-page: https://github.com/yufei-pan/multiSSH3
|
|
6
6
|
Author: Yufei Pan
|
|
@@ -826,7 +826,7 @@ mssh -f ./data/ allServers /tmp/data/
|
|
|
826
826
|
- By default mssh set this to true to speed up operations on large host lists with some unreachable hosts.
|
|
827
827
|
- Unreachable hosts will be tried again when their timeout expires.
|
|
828
828
|
- A skipped host is not run and counts as a failed host: it shows return code 255 ( what ssh returns when it cannot connect ) with a line saying it was skipped, and the summary lists it after `failed_hosts:` as `Skipped N hosts unreachable in a recent run, without trying them: ...`. So mssh does not exit 0 or print `Success` when it skipped a host.
|
|
829
|
-
- mssh stores the current run unreachable hosts in memory and if `skip_unreachable` is true, it will store them in `unavailable_hosts.csv` in mssh's private runtime directory: `$XDG_RUNTIME_DIR/multiSSH3`, else `/run/user/<uid>/multiSSH3`, `/dev/shm/multiSSH3-<uid>`, or a `multiSSH3-<uid>` folder in the system temp folder (the first that only you can write to). If none can be used, they are only kept for the current run.
|
|
829
|
+
- mssh stores the current run unreachable hosts in memory and if `skip_unreachable` is true, it will store them in `unavailable_hosts.csv` in mssh's private runtime directory: `$XDG_RUNTIME_DIR/multiSSH3`, else `/run/user/<uid>/multiSSH3`, `/dev/shm/multiSSH3-<uid>`, or a `multiSSH3-<uid>` folder in the system temp folder (the first that only you can write to, or could make). mssh reads them back from that one only, not from a later place an older session left them in. If none can be used, they are only kept for the current run.
|
|
830
830
|
- To force mssh to not use unreachable hosts from previous runs, you can use `-a, -nsu, --no_skip_unreachable` to set `skip_unreachable` to false.
|
|
831
831
|
|
|
832
832
|
### `-a, -nsu, --no_skip_unreachable` | not _`DEFAULT_SKIP_UNREACHABLE`_
|
|
@@ -974,7 +974,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
|
|
|
974
974
|
|
|
975
975
|
Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
|
|
976
976
|
|
|
977
|
-
Below is a sample help message output from multiSSH3 6.
|
|
977
|
+
Below is a sample help message output from multiSSH3 6.36 @ 2026-10-16 (no config file, 120 columns; the lines after the options list the programs found on that machine)
|
|
978
978
|
|
|
979
979
|
```bash
|
|
980
980
|
$ mssh -h
|
|
@@ -800,7 +800,7 @@ mssh -f ./data/ allServers /tmp/data/
|
|
|
800
800
|
- By default mssh set this to true to speed up operations on large host lists with some unreachable hosts.
|
|
801
801
|
- Unreachable hosts will be tried again when their timeout expires.
|
|
802
802
|
- A skipped host is not run and counts as a failed host: it shows return code 255 ( what ssh returns when it cannot connect ) with a line saying it was skipped, and the summary lists it after `failed_hosts:` as `Skipped N hosts unreachable in a recent run, without trying them: ...`. So mssh does not exit 0 or print `Success` when it skipped a host.
|
|
803
|
-
- mssh stores the current run unreachable hosts in memory and if `skip_unreachable` is true, it will store them in `unavailable_hosts.csv` in mssh's private runtime directory: `$XDG_RUNTIME_DIR/multiSSH3`, else `/run/user/<uid>/multiSSH3`, `/dev/shm/multiSSH3-<uid>`, or a `multiSSH3-<uid>` folder in the system temp folder (the first that only you can write to). If none can be used, they are only kept for the current run.
|
|
803
|
+
- mssh stores the current run unreachable hosts in memory and if `skip_unreachable` is true, it will store them in `unavailable_hosts.csv` in mssh's private runtime directory: `$XDG_RUNTIME_DIR/multiSSH3`, else `/run/user/<uid>/multiSSH3`, `/dev/shm/multiSSH3-<uid>`, or a `multiSSH3-<uid>` folder in the system temp folder (the first that only you can write to, or could make). mssh reads them back from that one only, not from a later place an older session left them in. If none can be used, they are only kept for the current run.
|
|
804
804
|
- To force mssh to not use unreachable hosts from previous runs, you can use `-a, -nsu, --no_skip_unreachable` to set `skip_unreachable` to false.
|
|
805
805
|
|
|
806
806
|
### `-a, -nsu, --no_skip_unreachable` | not _`DEFAULT_SKIP_UNREACHABLE`_
|
|
@@ -948,7 +948,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
|
|
|
948
948
|
|
|
949
949
|
Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
|
|
950
950
|
|
|
951
|
-
Below is a sample help message output from multiSSH3 6.
|
|
951
|
+
Below is a sample help message output from multiSSH3 6.36 @ 2026-10-16 (no config file, 120 columns; the lines after the options list the programs found on that machine)
|
|
952
952
|
|
|
953
953
|
```bash
|
|
954
954
|
$ mssh -h
|
|
@@ -16,7 +16,7 @@ import sys
|
|
|
16
16
|
import time
|
|
17
17
|
from bisect import bisect_left
|
|
18
18
|
|
|
19
|
-
version = '0.
|
|
19
|
+
version = '0.9'
|
|
20
20
|
__version__ = version
|
|
21
21
|
PROTOCOL = 'MSSHC1'
|
|
22
22
|
MSSH_NAMES = ('mssh', 'mssh3', 'multissh', 'multissh3', 'multiSSH3')
|
|
@@ -1450,6 +1450,21 @@ def _private_dir(path, create=True):
|
|
|
1450
1450
|
return False
|
|
1451
1451
|
return stat.S_ISDIR(st.st_mode) and st.st_uid == os.getuid() and not st.st_mode & 0o077
|
|
1452
1452
|
|
|
1453
|
+
def _could_be_private_dir(path):
|
|
1454
|
+
'''Whether _private_dir(path, create=True) would take path, without changing anything (as multiSSH3's).'''
|
|
1455
|
+
import stat
|
|
1456
|
+
try:
|
|
1457
|
+
st = os.lstat(path)
|
|
1458
|
+
except FileNotFoundError:
|
|
1459
|
+
return os.access(os.path.dirname(path), os.W_OK | os.X_OK)
|
|
1460
|
+
except OSError:
|
|
1461
|
+
return False
|
|
1462
|
+
if not stat.S_ISDIR(st.st_mode):
|
|
1463
|
+
return False
|
|
1464
|
+
if not hasattr(os, 'getuid'):
|
|
1465
|
+
return True
|
|
1466
|
+
return st.st_uid == os.getuid() and not st.st_mode & 0o022
|
|
1467
|
+
|
|
1453
1468
|
def _owned_and_not_writable_by_others(path):
|
|
1454
1469
|
if not hasattr(os, 'getuid'):
|
|
1455
1470
|
return True
|
|
@@ -1468,7 +1483,8 @@ def cache_dir(create=True):
|
|
|
1468
1483
|
mssh's private runtime directory, where unavailable_hosts.csv and the index live, or None.
|
|
1469
1484
|
|
|
1470
1485
|
Exactly as multiSSH3.runtime_dir picks it: $XDG_RUNTIME_DIR/multiSSH3 when that runtime dir
|
|
1471
|
-
is ours, /run/user/<uid>/multiSSH3, /dev/shm/multiSSH3-<uid>, then <tempdir>/multiSSH3-<uid
|
|
1486
|
+
is ours, /run/user/<uid>/multiSSH3, /dev/shm/multiSSH3-<uid>, then <tempdir>/multiSSH3-<uid>;
|
|
1487
|
+
without create, the one a write would use if it is there already, else None.
|
|
1472
1488
|
'''
|
|
1473
1489
|
import tempfile
|
|
1474
1490
|
if hasattr(os, 'getuid'):
|
|
@@ -1488,8 +1504,11 @@ def cache_dir(create=True):
|
|
|
1488
1504
|
candidates.append(os.path.join(_SHM_DIR, 'multiSSH3-{}'.format(user_id)))
|
|
1489
1505
|
candidates.append(os.path.join(tempfile.gettempdir(), 'multiSSH3-{}'.format(user_id)))
|
|
1490
1506
|
for directory in candidates:
|
|
1491
|
-
if
|
|
1492
|
-
|
|
1507
|
+
if create:
|
|
1508
|
+
if _private_dir(directory):
|
|
1509
|
+
return directory
|
|
1510
|
+
elif _could_be_private_dir(directory):
|
|
1511
|
+
return directory if _private_dir(directory, create=False) else None
|
|
1493
1512
|
return None
|
|
1494
1513
|
|
|
1495
1514
|
def _debug(message):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multiSSH3
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.36
|
|
4
4
|
Summary: Run commands on multiple hosts via SSH
|
|
5
5
|
Home-page: https://github.com/yufei-pan/multiSSH3
|
|
6
6
|
Author: Yufei Pan
|
|
@@ -826,7 +826,7 @@ mssh -f ./data/ allServers /tmp/data/
|
|
|
826
826
|
- By default mssh set this to true to speed up operations on large host lists with some unreachable hosts.
|
|
827
827
|
- Unreachable hosts will be tried again when their timeout expires.
|
|
828
828
|
- A skipped host is not run and counts as a failed host: it shows return code 255 ( what ssh returns when it cannot connect ) with a line saying it was skipped, and the summary lists it after `failed_hosts:` as `Skipped N hosts unreachable in a recent run, without trying them: ...`. So mssh does not exit 0 or print `Success` when it skipped a host.
|
|
829
|
-
- mssh stores the current run unreachable hosts in memory and if `skip_unreachable` is true, it will store them in `unavailable_hosts.csv` in mssh's private runtime directory: `$XDG_RUNTIME_DIR/multiSSH3`, else `/run/user/<uid>/multiSSH3`, `/dev/shm/multiSSH3-<uid>`, or a `multiSSH3-<uid>` folder in the system temp folder (the first that only you can write to). If none can be used, they are only kept for the current run.
|
|
829
|
+
- mssh stores the current run unreachable hosts in memory and if `skip_unreachable` is true, it will store them in `unavailable_hosts.csv` in mssh's private runtime directory: `$XDG_RUNTIME_DIR/multiSSH3`, else `/run/user/<uid>/multiSSH3`, `/dev/shm/multiSSH3-<uid>`, or a `multiSSH3-<uid>` folder in the system temp folder (the first that only you can write to, or could make). mssh reads them back from that one only, not from a later place an older session left them in. If none can be used, they are only kept for the current run.
|
|
830
830
|
- To force mssh to not use unreachable hosts from previous runs, you can use `-a, -nsu, --no_skip_unreachable` to set `skip_unreachable` to false.
|
|
831
831
|
|
|
832
832
|
### `-a, -nsu, --no_skip_unreachable` | not _`DEFAULT_SKIP_UNREACHABLE`_
|
|
@@ -974,7 +974,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
|
|
|
974
974
|
|
|
975
975
|
Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
|
|
976
976
|
|
|
977
|
-
Below is a sample help message output from multiSSH3 6.
|
|
977
|
+
Below is a sample help message output from multiSSH3 6.36 @ 2026-10-16 (no config file, 120 columns; the lines after the options list the programs found on that machine)
|
|
978
978
|
|
|
979
979
|
```bash
|
|
980
980
|
$ mssh -h
|
|
@@ -41,7 +41,7 @@ from itertools import count, product
|
|
|
41
41
|
from pprint import pformat
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
version = '6.
|
|
44
|
+
version = '6.36'
|
|
45
45
|
VERSION = version
|
|
46
46
|
__version__ = version
|
|
47
47
|
COMMIT_DATE = '2026-10-16'
|
|
@@ -6588,6 +6588,24 @@ def _private_dir(path, create=True):
|
|
|
6588
6588
|
return False
|
|
6589
6589
|
return stat.S_ISDIR(st.st_mode) and st.st_uid == os.getuid() and not st.st_mode & 0o077
|
|
6590
6590
|
|
|
6591
|
+
def _could_be_private_dir(path):
|
|
6592
|
+
'''
|
|
6593
|
+
Whether _private_dir(path, create=True) would take path, without changing anything: a directory
|
|
6594
|
+
ours and only ours to write to (it makes one readable by others 0700), or none yet where we can
|
|
6595
|
+
make it.
|
|
6596
|
+
'''
|
|
6597
|
+
try:
|
|
6598
|
+
st = os.lstat(path)
|
|
6599
|
+
except FileNotFoundError:
|
|
6600
|
+
return os.access(os.path.dirname(path), os.W_OK | os.X_OK)
|
|
6601
|
+
except OSError:
|
|
6602
|
+
return False
|
|
6603
|
+
if not stat.S_ISDIR(st.st_mode):
|
|
6604
|
+
return False
|
|
6605
|
+
if not hasattr(os, 'getuid'):
|
|
6606
|
+
return True
|
|
6607
|
+
return st.st_uid == os.getuid() and not st.st_mode & 0o022
|
|
6608
|
+
|
|
6591
6609
|
def _owned_and_not_writable_by_others(path):
|
|
6592
6610
|
'''Whether path exists, is owned by us and only we can write to it.'''
|
|
6593
6611
|
if not hasattr(os, 'getuid'):
|
|
@@ -6606,8 +6624,9 @@ def runtime_dir(create=True):
|
|
|
6606
6624
|
'''
|
|
6607
6625
|
mssh's private directory for state that must not outlive a reboot, or None to run without it.
|
|
6608
6626
|
|
|
6609
|
-
create: set up the directory (for writing). Without it
|
|
6610
|
-
reading changes nothing on disk
|
|
6627
|
+
create: set up the directory (for writing). Without it, the directory a write would use if it
|
|
6628
|
+
is there already, else None: reading changes nothing on disk, and never reads a later place (an
|
|
6629
|
+
older session's state, or this machine's own when $XDG_RUNTIME_DIR is set to a new directory).
|
|
6611
6630
|
|
|
6612
6631
|
The first of these that is a directory only we control (_private_dir):
|
|
6613
6632
|
1. $XDG_RUNTIME_DIR/multiSSH3, when $XDG_RUNTIME_DIR is ours and only we can write to it
|
|
@@ -6631,8 +6650,11 @@ def runtime_dir(create=True):
|
|
|
6631
6650
|
candidates.append(os.path.join(_SHM_DIR, 'multiSSH3-{}'.format(user_id)))
|
|
6632
6651
|
candidates.append(os.path.join(tempfile.gettempdir(), 'multiSSH3-{}'.format(user_id)))
|
|
6633
6652
|
for directory in candidates:
|
|
6634
|
-
if
|
|
6635
|
-
|
|
6653
|
+
if create:
|
|
6654
|
+
if _private_dir(directory):
|
|
6655
|
+
return directory
|
|
6656
|
+
elif _could_be_private_dir(directory):
|
|
6657
|
+
return directory if _private_dir(directory, create=False) else None
|
|
6636
6658
|
return None
|
|
6637
6659
|
|
|
6638
6660
|
def _unavailable_hosts_file_path(create=True):
|
|
@@ -123,6 +123,19 @@ def test_the_tests_never_use_the_real_runtime_dir(monkeypatch, tmp_path_factory,
|
|
|
123
123
|
assert multiSSH3._unavailable_hosts_file_path().startswith(base + os.sep)
|
|
124
124
|
|
|
125
125
|
|
|
126
|
+
def test_mssh_started_by_a_test_never_reads_the_real_runtime_dir(tmp_path):
|
|
127
|
+
# a test running mssh in a process of its own gives it a new $XDG_RUNTIME_DIR, and reading the
|
|
128
|
+
# unreachable hosts fell back to /run/user/<uid>/multiSSH3: hosts this machine's own runs found
|
|
129
|
+
# unreachable were skipped there
|
|
130
|
+
runtime = tmp_path / "run"
|
|
131
|
+
runtime.mkdir(mode=0o700)
|
|
132
|
+
child = subprocess.run([sys.executable, "-c", "import multiSSH3; print(multiSSH3._unavailable_hosts_file_path(create=False))"],
|
|
133
|
+
stdout=subprocess.PIPE, universal_newlines=True, check=True, cwd=os.path.dirname(os.path.abspath(multiSSH3.__file__)),
|
|
134
|
+
env=dict(os.environ, XDG_RUNTIME_DIR=str(runtime)))
|
|
135
|
+
|
|
136
|
+
assert child.stdout.strip() == "None"
|
|
137
|
+
|
|
138
|
+
|
|
126
139
|
def test_the_tests_never_see_this_machine_s_host_names():
|
|
127
140
|
# its env files (/etc/profile.d/hosts.sh) and hosts file
|
|
128
141
|
assert multiSSH3.readEnvFromFile() == {}
|
|
@@ -76,6 +76,34 @@ def test_a_place_someone_else_controls_is_skipped(places, monkeypatch, planted):
|
|
|
76
76
|
places['shm'].mkdir(mode=0o700)
|
|
77
77
|
places['shm'].chmod(0o777)
|
|
78
78
|
assert multiSSH3.runtime_dir() == mc.cache_dir() == str(places['temp'])
|
|
79
|
+
assert multiSSH3.runtime_dir(create=False) == mc.cache_dir(create=False) == str(places['temp'])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@posix_only
|
|
83
|
+
@pytest.mark.parametrize('holding', ['run_user', 'shm', 'temp'])
|
|
84
|
+
def test_reading_looks_only_where_a_write_goes(places, holding):
|
|
85
|
+
# a later place's state (an older session's, or this machine's own while a test or a script sets
|
|
86
|
+
# $XDG_RUNTIME_DIR to a new directory) is not read while the place a write goes to is not made yet
|
|
87
|
+
places[holding].mkdir(mode=0o700)
|
|
88
|
+
(places[holding] / 'unavailable_hosts.csv').write_text('#boot_id,x\n')
|
|
89
|
+
|
|
90
|
+
assert multiSSH3.runtime_dir(create=False) is None and mc.cache_dir(create=False) is None
|
|
91
|
+
assert multiSSH3._unavailable_hosts_file_path(create=False) is None
|
|
92
|
+
assert not places['xdg'].exists()
|
|
93
|
+
# once made, it is read
|
|
94
|
+
assert multiSSH3.runtime_dir() == str(places['xdg'])
|
|
95
|
+
assert multiSSH3.runtime_dir(create=False) == mc.cache_dir(create=False) == str(places['xdg'])
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@posix_only
|
|
99
|
+
def test_reading_does_not_pass_over_a_place_a_write_would_tighten(places):
|
|
100
|
+
# ours, but readable by others: a write makes it 0700 and uses it
|
|
101
|
+
places['xdg'].mkdir(mode=0o700)
|
|
102
|
+
places['xdg'].chmod(0o755)
|
|
103
|
+
places['run_user'].mkdir(mode=0o700)
|
|
104
|
+
|
|
105
|
+
assert multiSSH3.runtime_dir(create=False) is None and mc.cache_dir(create=False) is None
|
|
106
|
+
assert multiSSH3.runtime_dir() == mc.cache_dir() == str(places['xdg'])
|
|
79
107
|
|
|
80
108
|
|
|
81
109
|
@posix_only
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|