Skip to content

Commit

Permalink
system: op-mode: T3334: allow delayed getty restart when configuring …
Browse files Browse the repository at this point in the history
…serial ports

* Relocated service control to op-mode command "restart serial-console"
* Checking for logged-in serial sessions that may be affected by getty reconfig
* Warning the user when changes are committed and serial sessions are active,
  otherwise restart services as normal. No prompts issued during commit,
  all config gen/commit steps still occur except for the service restarts
  (everything remains consistent)
* To apply committed changes, user will need to run "restart serial-console"
  to complete the process or reboot the whole router
  • Loading branch information
talmakion committed Jun 21, 2024
1 parent 9428146 commit 40dbd61
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 7 deletions.
13 changes: 13 additions & 0 deletions op-mode-definitions/restart-serial.xml.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<interfaceDefinition>
<node name="restart">
<children>
<node name="serial-console">
<properties>
<help>Restart serial console service</help>
</properties>
<command>${vyos_op_scripts_dir}/restart_serial.py</command>
</node>
</children>
</node>
</interfaceDefinition>
9 changes: 2 additions & 7 deletions src/conf_mode/system_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ def generate(console):
for root, dirs, files in os.walk(base_dir):
for basename in files:
if 'serial-getty' in basename:
call(f'systemctl stop {basename}')
os.unlink(os.path.join(root, basename))

if not console or 'device' not in console:
Expand Down Expand Up @@ -129,12 +128,8 @@ def apply(console):
# Configure screen blank powersaving on VGA console
call('/usr/bin/setterm -blank 15 -powersave powerdown -powerdown 60 -term linux </dev/tty1 >/dev/tty1 2>&1')

# Start getty process on configured serial interfaces
for device in console['device']:
# Only start console if it exists on the running system. If a user
# detaches a USB serial console and reboots - it should not fail!
if os.path.exists(f'/dev/{device}'):
call(f'systemctl restart serial-getty@{device}.service')
# Service control moved to op_mode/restart_serial.py to unify checks and prompts.
call('/usr/libexec/vyos/op_mode/restart_serial.py --quiet')

return None

Expand Down
84 changes: 84 additions & 0 deletions src/op_mode/restart_serial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
#
# Copyright (C) 2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import sys, os, re, json

from vyos.base import Warning
from vyos.utils.io import ask_yes_no
from vyos.utils.process import cmd, rc_cmd

GLOB_GETTY_UNITS = 'serial-getty@*.service'
RE_GETTY_DEVICES = re.compile(r'.+@(.+).service$')

SD_UNIT_PATH = '/run/systemd/system'
UTMP_PATH = '/run/utmp'

def get_serial_units():
# Since we cannot depend on the current config for decommissioned ports,
# we just grab everything that systemd knows about.
_, tmp = rc_cmd(f'sudo systemctl list-units {GLOB_GETTY_UNITS} --all --output json --no-pager')
getty_units = json.loads(tmp)
for sdunit in getty_units:
m = RE_GETTY_DEVICES.search(sdunit['unit'])
if m is None:
Warning(f'Serial console unit name "{sdunit["unit"]}" is malformed and cannot be checked for activity!')
else:
sdunit['device'] = m.group(1)

return getty_units

def get_connected_ports(units):
connected = []

ports = [ x['device'] for x in units if 'device' in x ]
for line in cmd(f'utmpdump {UTMP_PATH}').splitlines():
row = line.split('] [')
user_name = row[3].strip()
user_term = row[4].strip()
if user_name and user_name != 'LOGIN' and user_term in ports:
connected.append(user_term)

return connected

if __name__ == '__main__':
cmd('sudo systemctl daemon-reload')

no_prompt = '--quiet' in sys.argv # don't need a full argparse for this.
units = get_serial_units()
connected = get_connected_ports(units)

if connected:
print('There are user sessions connected via serial console that will be terminated\n' \
'when serial console settings are changed.\n') # extra newline is deliberate.

if no_prompt:
# This flag is used by conf_mode/system_console.py to reset things, if there's
# a problem, the user should issue a manual restart for serial-getty.
print('Please ensure all settings are committed and saved before issuing a\n' \
'"restart serial-console" command to apply new configuration.')
sys.exit(0)

if not ask_yes_no('Any uncommitted changes from these sessions will be lost and in-progress actions\n' \
'may be left in an inconsistent state. Continue?'):
sys.exit(1)

for unit in units:
unit_name = unit['unit']
if os.path.exists(os.path.join(SD_UNIT_PATH, unit_name)):
cmd(f'sudo systemctl restart {unit_name}')
else:
# Deleted stubs don't need to be restarted, just shut them down.
cmd(f'sudo systemctl stop {unit_name}')

0 comments on commit 40dbd61

Please sign in to comment.