|
21 | 21 | from .exceptions import CollectionNotFound, Exit, ParseError, UnexpectedExit |
22 | 22 | from .parser import Argument, Parser, ParserContext |
23 | 23 | from .terminals import pty_size |
24 | | -from .util import debug, enable_logging, helpline |
| 24 | +from .util import debug, enable_logging, helpline, isatty |
25 | 25 |
|
26 | 26 | if TYPE_CHECKING: |
27 | 27 | from .loader import Loader |
@@ -186,6 +186,10 @@ def task_args(self) -> List["Argument"]: |
186 | 186 | indent_width = 4 |
187 | 187 | indent = " " * indent_width |
188 | 188 | col_padding = 3 |
| 189 | + root_warning = ( |
| 190 | + "WARNING: Running Invoke as root may create root-owned files and " |
| 191 | + "cause later I/O or permission errors. Re-run as a non-root user." |
| 192 | + ) |
189 | 193 |
|
190 | 194 | def __init__( |
191 | 195 | self, |
@@ -373,6 +377,7 @@ def run(self, argv: Optional[List[str]] = None, exit: bool = True) -> None: |
373 | 377 | .. versionadded:: 1.0 |
374 | 378 | """ |
375 | 379 | try: |
| 380 | + self.warn_if_running_as_root(is_testing=not exit) |
376 | 381 | # Create an initial config, which will hold defaults & values from |
377 | 382 | # most config file locations (all but runtime.) Used to inform |
378 | 383 | # loading & parsing behavior. |
@@ -421,6 +426,22 @@ def run(self, argv: Optional[List[str]] = None, exit: bool = True) -> None: |
421 | 426 | except KeyboardInterrupt: |
422 | 427 | sys.exit(1) # Same behavior as Python itself outside of REPL |
423 | 428 |
|
| 429 | + def warn_if_running_as_root(self, is_testing: bool = False) -> None: |
| 430 | + """ |
| 431 | + Emit a warning when Invoke is executed as the root user. |
| 432 | + """ |
| 433 | + if is_testing or not isatty(sys.stderr) or not self.running_as_root(): |
| 434 | + return |
| 435 | + print(self.root_warning, file=sys.stderr) |
| 436 | + |
| 437 | + def running_as_root(self) -> bool: |
| 438 | + """ |
| 439 | + Return ``True`` when the current process is running as root. |
| 440 | + """ |
| 441 | + if hasattr(os, "geteuid"): |
| 442 | + return os.geteuid() == 0 |
| 443 | + return getpass.getuser() == "root" |
| 444 | + |
424 | 445 | def parse_core(self, argv: Optional[List[str]]) -> None: |
425 | 446 | debug("argv given to Program.run: {!r}".format(argv)) |
426 | 447 | self.normalize_argv(argv) |
|
0 commit comments