Skip to content

Instantly share code, notes, and snippets.

@elibroftw
Last active April 24, 2023 14:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elibroftw/36d18406d6be775e2c52af9d19571c42 to your computer and use it in GitHub Desktop.
Save elibroftw/36d18406d6be775e2c52af9d19571c42 to your computer and use it in GitHub Desktop.
A CLI to increase productivity for the CS 350 Operating Systems course. This should sit two levels above the os161-X.Y directory. Script is robust enough to work with the naming convention recommended by the course as well as my own.
#!/usr/bin/python3
import argparse
import subprocess
from subprocess import DEVNULL
import os
import glob
from pathlib import Path
import time
import shutil
import sys
# https://student.cs.uwaterloo.ca/~cs350/common/WorkingWith161.html
script_root = Path(os.path.dirname(__file__)).absolute()
try:
os161_dir = Path(glob.glob(glob.escape(script_root) + '/*os161*')[0]).absolute()
except IndexError:
print('ERROR: could not find a os161 project directory')
sys.exit(1)
try:
os161_src = Path(glob.glob(glob.escape(os161_dir) + '/os161*')[0]).absolute()
except IndexError:
print('ERROR: could not find a os161 source code directory')
sys.exit(2)
userspace_dir = script_root / 'userspace'
if not userspace_dir.exists():
userspace_dir_bak = script_root / 'cs350-student'
if not userspace_dir_bak.exists():
print('Could not find a userspace directory such as /userspace or /cs350-student')
sys.exit(3)
userspace_dir = userspace_dir_bak
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands', help='additional help', dest='subcommand')
submit_parser = subparsers.add_parser('submit', help='submit code for an assignment')
submit_parser.add_argument('num', help='assignment number to submit')
grade_parser = subparsers.add_parser('grade', help='get grade for an assignment')
grade_parser.add_argument('num', help='assignment number to fetch grade for')
new_parser = subparsers.add_parser('new', help='configure new assignment')
new_parser.add_argument('num', help='assignment number to configure kernel for', type=int)
build_parser = subparsers.add_parser('build', help='build assignment')
build_parser.add_argument('num', help='assignment to build and install', type=int)
configure_parser = subparsers.add_parser('configure', help='configure the OS on a new machine')
user_parser = subparsers.add_parser('user', help='build user-level programs')
run_parser = subparsers.add_parser('run', help='run the simulator for the default kernel or provided kernel')
run_parser.add_argument('num', help='assignment number for kernel to simulate', default=-1, type=int, nargs='?')
run_parser.add_argument('--command', '-c', help='command to run immediately on the kernel', default='')
debug_parser = subparsers.add_parser('debug', help="subcommands for debugging OS/161 (hopefully you won't need to use this)")
debug_subparsers = debug_parser.add_subparsers(title='debug subcommands', help='start and then attach', dest='debug_subcommand')
debug_start_parser = debug_subparsers.add_parser('start', help="1. start OS/161 default kernel in debug mode")
debug_start_parser.add_argument('num', help='assignment number for kernel to debug', default=-1, type=int, nargs='?')
debug_attach_parser = debug_subparsers.add_parser('attach', help="2. attach [break_point lines] gdb to running OS/161")
debug_attach_parser.add_argument('num', help='assignment number for kernel to debug', type=int)
debug_attach_parser.add_argument('--breakpoints', nargs='*', default=[], help='breakpoints to set in gdb (space seperated)')
args = parser.parse_args()
if args.subcommand == 'submit':
p_args = ['cs350_submit', os161_src, f'ASST{args.num}']
print(' '.join((f'"{arg}' if ' ' in str(arg) else str(arg) for arg in p_args)))
p = subprocess.Popen(p_args, stdin=DEVNULL)
p.wait()
try:
assignment_dir = (glob.glob(glob.escape(userspace_dir) + f'/a*{args.num}') + glob.glob(glob.escape(userspace_dir) + f'/A*{args.num}'))[0]
except IndexError:
print(f'ERROR: could not find assignment directory for assignment {args.num} matching A*{args.num} or a*{args.num}')
sys.exit(4)
p_args = ['cs350_submit', assignment_dir, f'ASSTUSER{args.num}']
print(' '.join((f'"{arg}' if ' ' in str(arg) else str(arg) for arg in p_args)))
p = subprocess.Popen(p_args, stdin=DEVNULL)
p.wait()
elif args.subcommand == 'new':
os.makedirs(script_root / 'userspace' / f'ASSTUSER{args.num}', exist_ok=True)
os.chdir(os161_src / 'kern' / 'conf')
p = subprocess.Popen(['./config', f'ASST{args.num}'], stdin=DEVNULL)
p.wait()
print(f'run subcommand `build {args.num}`')
elif args.subcommand == 'build':
os.chdir(os161_src / 'kern' / 'compile' / f'ASST{args.num}')
for command in ('bmake depend', 'bmake', 'bmake install'):
p = subprocess.Popen(command.split(), stdin=DEVNULL)
exit_code = p.wait()
if exit_code != 0:
sys.exit(exit_code)
print('use the `run` subcommand to run OS/161')
elif args.subcommand == 'user':
os.chdir(os161_src)
for command in ('bmake', 'bmake install'):
p = subprocess.Popen(command.split(), stdin=DEVNULL)
exit_code = p.wait()
if exit_code != 0:
sys.exit(exit_code)
elif args.subcommand == 'run':
kernel = 'kernel'
if args.num > -1:
kernel = f'kernel-ASST{args.num}'
os.chdir(os161_src / 'kern' / 'compile' / f'ASST{args.num}')
p = subprocess.Popen(['bmake', 'install'], stdin=DEVNULL)
p.wait()
os.chdir(os161_src.parent / 'root')
if args.command == 'testa2':
args.command = 'sy3;sy2;uw1;q'
p = subprocess.Popen(['sys161', kernel, args.command])
p.wait()
elif args.subcommand == 'debug':
os.chdir(os161_dir / 'root')
if args.debug_subcommand == 'start':
kernel = 'kernel' if args.num < 0 else f'kernel-ASST{args.num}'
p = subprocess.Popen(['sys161', '-w', kernel])
p.wait()
print('use the `debug attach` subcommand in a second terminal')
elif args.debug_subcommand == 'attach':
kernel = f'kernel-ASST{args.num}'
with open(os161_dir / 'root' / '.gdbinit', 'w', encoding='utf-8') as f:
f.write(f'dir ../{os161_src.name}/kern/compile/ASST{args.num}\n')
f.write('target remote unix:.sockets/gdb\n')
for breakpoint in args.breakpoints:
f.write(f'break {breakpoint}\n')
# f.write('c\n')
p = subprocess.Popen(['cs350-gdb', kernel])
p.wait()
else:
debug_parser.print_help()
debug_start_parser.print_usage()
debug_attach_parser.print_usage()
elif args.subcommand == 'configure':
os.chdir(os161_src)
p = subprocess.Popen(['./configure', f'--ostree={os161_dir}/root', '--toolprefix=cs350-'], stdin=DEVNULL)
p.wait()
print('use subcommand new to create new assignments')
os.makedirs(os161_dir / 'root', exist_ok=True)
shutil.copyfile('/u/cs350/sys161/sys161.conf', os161_dir / 'root' / 'sys161.conf')
elif args.subcommand == 'grade':
p_args = ['cs350-grade', f'ASST{args.num}']
print(' '.join((f'"{arg}' if ' ' in str(arg) else str(arg) for arg in p_args)))
p = subprocess.Popen(p_args, stdin=DEVNULL)
p.wait()
p_args = ['cs350-grade', f'ASSTUSER{args.num}']
print(' '.join((f'"{arg}' if ' ' in str(arg) else str(arg) for arg in p_args)))
p = subprocess.Popen(p_args, stdin=DEVNULL)
p.wait()
else:
parser.print_help()
@elibroftw
Copy link
Author

elibroftw commented Apr 24, 2023

File hierarchy

  • cs350 (CWD)
    • os161
    • userspace
    • cs350_cli.py

setup new machine

./cs350_cli.py configure

Calls configure script and copies over the sys161.conf file. You should duplicate the cpu and memory line in the config and set CPUs to 4 and virtual memory to 4MB (commented out). Use this line when testing beefier programs.

configure new assignment

./cs350_cli.py new 1

build and run the kernel for an assignment

./cs350_cli.py build 1
./cs350_cli.py run [uint]

gdb debug requires running in two different terminals

./cs350_cli.py debug 2
./cs350_cli.py debug attach 2 --breakpoints file.c:30 file2.c:50

submit and see grade later

./cs350_cli.py submit 0

internally calls:
cs350_submit os161/os161-1.99/kern/compile/ASST0 ASST0
cs350_submit userspace/ASSTUSER0 ASSTUSER0

./cs350_cli.py grade 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment