Compare commits

...

7 Commits
util ... fs

Author SHA1 Message Date
ridethepig
4368fbf44c make grade 100/100 2023-02-05 17:35:55 +00:00
ridethepig
f187a07854 bigfile ok 2023-02-05 15:23:55 +00:00
Frans Kaashoek
888b75593b Increase timeouts 2022-11-28 20:58:24 -05:00
Frans Kaashoek
c084dbbffb fs lab 2022-11-14 07:30:00 -05:00
Frans Kaashoek
4b46c0c6eb Use O_RDONLY instead of 0 2022-09-11 13:51:11 -04:00
Robert Morris
463ae0abc3 handle negative arguments to sleep() a little better 2022-09-09 09:17:37 -04:00
Frans Kaashoek
f5b93ef12f Update acks
61810
2022-08-25 14:20:52 -04:00
25 changed files with 1333 additions and 43 deletions

9
.gitignore vendored
View File

@ -15,3 +15,12 @@ mkfs
kernel/kernel
user/usys.S
.gdbinit
myapi.key
*-handin.tar.gz
xv6.out*
.vagrant/
submissions/
ph
barrier
/lab-*.json
.DS_Store

260
Makefile
View File

@ -1,14 +1,16 @@
# To compile and run with a lab solution, set the lab name in conf/lab.mk
# (e.g., LAB=util). Run make grade to test solution with the lab's
# grade script (e.g., grade-lab-util).
-include conf/lab.mk
K=kernel
U=user
OBJS = \
$K/entry.o \
$K/start.o \
$K/console.o \
$K/printf.o \
$K/uart.o \
$K/kalloc.o \
$K/spinlock.o \
$K/string.o \
$K/main.o \
$K/vm.o \
@ -30,6 +32,34 @@ OBJS = \
$K/plic.o \
$K/virtio_disk.o
OBJS_KCSAN = \
$K/start.o \
$K/console.o \
$K/printf.o \
$K/uart.o \
$K/spinlock.o
ifdef KCSAN
OBJS_KCSAN += \
$K/kcsan.o
endif
ifeq ($(LAB),$(filter $(LAB), lock))
OBJS += \
$K/stats.o\
$K/sprintf.o
endif
ifeq ($(LAB),net)
OBJS += \
$K/e1000.o \
$K/net.o \
$K/sysnet.o \
$K/pci.o
endif
# riscv64-unknown-elf- or riscv64-linux-gnu-
# perhaps in /opt/riscv/bin
#TOOLPREFIX =
@ -57,12 +87,28 @@ OBJCOPY = $(TOOLPREFIX)objcopy
OBJDUMP = $(TOOLPREFIX)objdump
CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb -gdwarf-2
ifdef LAB
LABUPPER = $(shell echo $(LAB) | tr a-z A-Z)
XCFLAGS += -DSOL_$(LABUPPER) -DLAB_$(LABUPPER)
endif
CFLAGS += $(XCFLAGS)
CFLAGS += -MD
CFLAGS += -mcmodel=medany
CFLAGS += -ffreestanding -fno-common -nostdlib -mno-relax
CFLAGS += -I.
CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
ifeq ($(LAB),net)
CFLAGS += -DNET_TESTS_PORT=$(SERVERPORT)
endif
ifdef KCSAN
CFLAGS += -DKCSAN
KCSANFLAG = -fsanitize=thread -fno-inline
endif
# Disable PIE when possible (for Ubuntu 16.10 toolchain)
ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]no-pie'),)
CFLAGS += -fno-pie -no-pie
@ -73,11 +119,17 @@ endif
LDFLAGS = -z max-page-size=4096
$K/kernel: $(OBJS) $K/kernel.ld $U/initcode
$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS)
$K/kernel: $(OBJS) $(OBJS_KCSAN) $K/kernel.ld $U/initcode
$(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS) $(OBJS_KCSAN)
$(OBJDUMP) -S $K/kernel > $K/kernel.asm
$(OBJDUMP) -t $K/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $K/kernel.sym
$(OBJS): EXTRAFLAG := $(KCSANFLAG)
$K/%.o: $K/%.c
$(CC) $(CFLAGS) $(EXTRAFLAG) -c -o $@ $<
$U/initcode: $U/initcode.S
$(CC) $(CFLAGS) -march=rv64g -nostdinc -I. -Ikernel -c $U/initcode.S -o $U/initcode.o
$(LD) $(LDFLAGS) -N -e start -Ttext 0 -o $U/initcode.out $U/initcode.o
@ -89,6 +141,10 @@ tags: $(OBJS) _init
ULIB = $U/ulib.o $U/usys.o $U/printf.o $U/umalloc.o
ifeq ($(LAB),$(filter $(LAB), lock))
ULIB += $U/statistics.o
endif
_%: %.o $(ULIB)
$(LD) $(LDFLAGS) -T $U/user.ld -o $@ $^
$(OBJDUMP) -S $@ > $*.asm
@ -107,7 +163,7 @@ $U/_forktest: $U/forktest.o $(ULIB)
$(OBJDUMP) -S $U/_forktest > $U/forktest.asm
mkfs/mkfs: mkfs/mkfs.c $K/fs.h $K/param.h
gcc -Werror -Wall -I. -o mkfs/mkfs mkfs/mkfs.c
gcc $(XCFLAGS) -Werror -Wall -I. -o mkfs/mkfs mkfs/mkfs.c
# Prevent deletion of intermediate files, e.g. cat.o, after first build, so
# that disk image changes after first build are persistent until clean. More
@ -132,9 +188,81 @@ UPROGS=\
$U/_grind\
$U/_wc\
$U/_zombie\
$U/_symlinktest\
fs.img: mkfs/mkfs README $(UPROGS)
mkfs/mkfs fs.img README $(UPROGS)
ifeq ($(LAB),$(filter $(LAB), lock))
UPROGS += \
$U/_stats
endif
ifeq ($(LAB),traps)
UPROGS += \
$U/_call\
$U/_bttest
endif
ifeq ($(LAB),lazy)
UPROGS += \
$U/_lazytests
endif
ifeq ($(LAB),cow)
UPROGS += \
$U/_cowtest
endif
ifeq ($(LAB),thread)
UPROGS += \
$U/_uthread
$U/uthread_switch.o : $U/uthread_switch.S
$(CC) $(CFLAGS) -c -o $U/uthread_switch.o $U/uthread_switch.S
$U/_uthread: $U/uthread.o $U/uthread_switch.o $(ULIB)
$(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $U/_uthread $U/uthread.o $U/uthread_switch.o $(ULIB)
$(OBJDUMP) -S $U/_uthread > $U/uthread.asm
ph: notxv6/ph.c
gcc -o ph -g -O2 $(XCFLAGS) notxv6/ph.c -pthread
barrier: notxv6/barrier.c
gcc -o barrier -g -O2 $(XCFLAGS) notxv6/barrier.c -pthread
endif
ifeq ($(LAB),pgtbl)
UPROGS += \
$U/_pgtbltest
endif
ifeq ($(LAB),lock)
UPROGS += \
$U/_kalloctest\
$U/_bcachetest
endif
ifeq ($(LAB),fs)
UPROGS += \
$U/_bigfile
endif
ifeq ($(LAB),net)
UPROGS += \
$U/_nettests
endif
UEXTRA=
ifeq ($(LAB),util)
UEXTRA += user/xargstest.sh
endif
fs.img: mkfs/mkfs README $(UEXTRA) $(UPROGS)
mkfs/mkfs fs.img README $(UEXTRA) $(UPROGS)
-include kernel/*.d user/*.d
@ -144,7 +272,8 @@ clean:
$U/initcode $U/initcode.out $K/kernel fs.img \
mkfs/mkfs .gdbinit \
$U/usys.S \
$(UPROGS)
$(UPROGS) \
ph barrier
# try to generate a unique GDB port
GDBPORT = $(shell expr `id -u` % 5000 + 25000)
@ -155,12 +284,22 @@ QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \
ifndef CPUS
CPUS := 3
endif
ifeq ($(LAB),fs)
CPUS := 1
endif
FWDPORT = $(shell expr `id -u` % 5000 + 25999)
QEMUOPTS = -machine virt -bios none -kernel $K/kernel -m 128M -smp $(CPUS) -nographic
QEMUOPTS += -global virtio-mmio.force-legacy=false
QEMUOPTS += -drive file=fs.img,if=none,format=raw,id=x0
QEMUOPTS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
ifeq ($(LAB),net)
QEMUOPTS += -netdev user,id=net0,hostfwd=udp::$(FWDPORT)-:2000 -object filter-dump,id=net0,netdev=net0,file=packets.pcap
QEMUOPTS += -device e1000,netdev=net0,bus=pcie.0
endif
qemu: $K/kernel fs.img
$(QEMU) $(QEMUOPTS)
@ -171,3 +310,102 @@ qemu-gdb: $K/kernel .gdbinit fs.img
@echo "*** Now run 'gdb' in another window." 1>&2
$(QEMU) $(QEMUOPTS) -S $(QEMUGDB)
ifeq ($(LAB),net)
# try to generate a unique port for the echo server
SERVERPORT = $(shell expr `id -u` % 5000 + 25099)
server:
python3 server.py $(SERVERPORT)
ping:
python3 ping.py $(FWDPORT)
endif
##
## FOR testing lab grading script
##
ifneq ($(V),@)
GRADEFLAGS += -v
endif
print-gdbport:
@echo $(GDBPORT)
grade:
@echo $(MAKE) clean
@$(MAKE) clean || \
(echo "'make clean' failed. HINT: Do you have another running instance of xv6?" && exit 1)
./grade-lab-$(LAB) $(GRADEFLAGS)
##
## FOR web handin
##
WEBSUB := https://6828.scripts.mit.edu/2022/handin.py
handin: tarball-pref myapi.key
@SUF=$(LAB); \
curl -f -F file=@lab-$$SUF-handin.tar.gz -F key=\<myapi.key $(WEBSUB)/upload \
> /dev/null || { \
echo ; \
echo Submit seems to have failed.; \
echo Please go to $(WEBSUB)/ and upload the tarball manually.; }
handin-check:
@if ! test -d .git; then \
echo No .git directory, is this a git repository?; \
false; \
fi
@if test "$$(git symbolic-ref HEAD)" != refs/heads/$(LAB); then \
git branch; \
read -p "You are not on the $(LAB) branch. Hand-in the current branch? [y/N] " r; \
test "$$r" = y; \
fi
@if ! git diff-files --quiet || ! git diff-index --quiet --cached HEAD; then \
git status -s; \
echo; \
echo "You have uncomitted changes. Please commit or stash them."; \
false; \
fi
@if test -n "`git status -s`"; then \
git status -s; \
read -p "Untracked files will not be handed in. Continue? [y/N] " r; \
test "$$r" = y; \
fi
UPSTREAM := $(shell git remote -v | grep -m 1 "xv6-labs-2022" | awk '{split($$0,a," "); print a[1]}')
tarball: handin-check
git archive --format=tar HEAD | gzip > lab-$(LAB)-handin.tar.gz
tarball-pref: handin-check
@SUF=$(LAB); \
git archive --format=tar HEAD > lab-$$SUF-handin.tar; \
git diff $(UPSTREAM)/$(LAB) > /tmp/lab-$$SUF-diff.patch; \
tar -rf lab-$$SUF-handin.tar /tmp/lab-$$SUF-diff.patch; \
gzip -c lab-$$SUF-handin.tar > lab-$$SUF-handin.tar.gz; \
rm lab-$$SUF-handin.tar; \
rm /tmp/lab-$$SUF-diff.patch; \
myapi.key:
@echo Get an API key for yourself by visiting $(WEBSUB)/
@read -p "Please enter your API key: " k; \
if test `echo "$$k" |tr -d '\n' |wc -c` = 32 ; then \
TF=`mktemp -t tmp.XXXXXX`; \
if test "x$$TF" != "x" ; then \
echo "$$k" |tr -d '\n' > $$TF; \
mv -f $$TF $@; \
else \
echo mktemp failed; \
false; \
fi; \
else \
echo Bad API key: $$k; \
echo An API key should be 32 characters long.; \
false; \
fi;
.PHONY: handin tarball tarball-pref clean grade handin-check

36
README
View File

@ -6,7 +6,7 @@ ACKNOWLEDGMENTS
xv6 is inspired by John Lions's Commentary on UNIX 6th Edition (Peer
to Peer Communications; ISBN: 1-57398-013-7; 1st edition (June 14,
2000)). See also https://pdos.csail.mit.edu/6.828/, which provides
2000)). See also https://pdos.csail.mit.edu/6.1810/, which provides
pointers to on-line resources for v6.
The following people have made contributions: Russ Cox (context switching,
@ -14,29 +14,31 @@ locking), Cliff Frey (MP), Xiao Yu (MP), Nickolai Zeldovich, and Austin
Clements.
We are also grateful for the bug reports and patches contributed by
Takahiro Aoyagi, Silas Boyd-Wickizer, Anton Burtsev, Ian Chen, Dan
Cross, Cody Cutler, Mike CAT, Tej Chajed, Asami Doi, eyalz800, Nelson
Elhage, Saar Ettinger, Alice Ferrazzi, Nathaniel Filardo, flespark,
Peter Froehlich, Yakir Goaron, Shivam Handa, Matt Harvey, Bryan Henry,
jaichenhengjie, Jim Huang, Matúš Jókay, Alexander Kapshuk, Anders
Kaseorg, kehao95, Wolfgang Keller, Jungwoo Kim, Jonathan Kimmitt,
Eddie Kohler, Vadim Kolontsov, Austin Liew, l0stman, Pavan
Maddamsetti, Imbar Marinescu, Yandong Mao, Matan Shabtay, Hitoshi
Mitake, Carmi Merimovich, Mark Morrissey, mtasm, Joel Nider,
OptimisticSide, Greg Price, Jude Rich, Ayan Shafqat, Eldar Sehayek,
Yongming Shen, Fumiya Shigemitsu, Cam Tenny, tyfkda, Warren Toomey,
Stephen Tu, Rafael Ubal, Amane Uehara, Pablo Ventura, Xi Wang, Keiichi
Watanabe, Nicolas Wolovick, wxdao, Grant Wu, Jindong Zhang, Icenowy
Zheng, ZhUyU1997, and Zou Chang Wei.
Takahiro Aoyagi, Silas Boyd-Wickizer, Anton Burtsev, carlclone, Ian
Chen, Dan Cross, Cody Cutler, Mike CAT, Tej Chajed, Asami Doi,
eyalz800, Nelson Elhage, Saar Ettinger, Alice Ferrazzi, Nathaniel
Filardo, flespark, Peter Froehlich, Yakir Goaron, Shivam Handa, Matt
Harvey, Bryan Henry, jaichenhengjie, Jim Huang, Matúš Jókay, John
Jolly, Alexander Kapshuk, Anders Kaseorg, kehao95, Wolfgang Keller,
Jungwoo Kim, Jonathan Kimmitt, Eddie Kohler, Vadim Kolontsov, Austin
Liew, l0stman, Pavan Maddamsetti, Imbar Marinescu, Yandong Mao, Matan
Shabtay, Hitoshi Mitake, Carmi Merimovich, Mark Morrissey, mtasm, Joel
Nider, Hayato Ohhashi, OptimisticSide, Harry Porter, Greg Price, Jude
Rich, segfault, Ayan Shafqat, Eldar Sehayek, Yongming Shen, Fumiya
Shigemitsu, Cam Tenny, tyfkda, Warren Toomey, Stephen Tu, Rafael Ubal,
Amane Uehara, Pablo Ventura, Xi Wang, WaheedHafez, Keiichi Watanabe,
Nicolas Wolovick, wxdao, Grant Wu, Jindong Zhang, Icenowy Zheng,
ZhUyU1997, and Zou Chang Wei.
The code in the files that constitute xv6 is
Copyright 2006-2020 Frans Kaashoek, Robert Morris, and Russ Cox.
Copyright 2006-2022 Frans Kaashoek, Robert Morris, and Russ Cox.
ERROR REPORTS
Please send errors and suggestions to Frans Kaashoek and Robert Morris
(kaashoek,rtm@mit.edu). The main purpose of xv6 is as a teaching
operating system for MIT's 6.S081, so we are more interested in
operating system for MIT's 6.1810, so we are more interested in
simplifications and clarifications than new features.
BUILDING AND RUNNING XV6

1
conf/lab.mk Normal file
View File

@ -0,0 +1 @@
LAB=fs

41
grade-lab-fs Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
import re
from gradelib import *
r = Runner(save("xv6.out"))
@test(40, "running bigfile")
def test_bigfile():
r.run_qemu(shell_script([
'bigfile'
]), timeout=400)
r.match('^wrote 65803 blocks$')
r.match('^bigfile done; ok$')
@test(0, "running symlinktest")
def test_symlinktest():
r.run_qemu(shell_script([
'symlinktest'
]), timeout=20)
@test(20, "symlinktest: symlinks", parent=test_symlinktest)
def test_symlinktest_symlinks():
r.match("^test symlinks: ok$")
@test(20, "symlinktest: concurrent symlinks", parent=test_symlinktest)
def test_symlinktest_symlinks():
r.match("^test concurrent symlinks: ok$")
@test(19, "usertests")
def test_usertests():
r.run_qemu(shell_script([
'usertests -q'
]), timeout=600)
r.match('^ALL TESTS PASSED$')
@test(1, "time")
def test_time():
check_time()
run_tests()

611
gradelib.py Normal file
View File

@ -0,0 +1,611 @@
from __future__ import print_function
import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string
from subprocess import check_call, Popen
from optparse import OptionParser
__all__ = []
##################################################################
# Test structure
#
__all__ += ["test", "end_part", "run_tests", "get_current_test"]
TESTS = []
TOTAL = POSSIBLE = 0
PART_TOTAL = PART_POSSIBLE = 0
CURRENT_TEST = None
def test(points, title=None, parent=None):
"""Decorator for declaring test functions. If title is None, the
title of the test will be derived from the function name by
stripping the leading "test_" and replacing underscores with
spaces."""
def register_test(fn, title=title):
if not title:
assert fn.__name__.startswith("test_")
title = fn.__name__[5:].replace("_", " ")
if parent:
title = " " + title
def run_test():
global TOTAL, POSSIBLE, CURRENT_TEST
# Handle test dependencies
if run_test.complete:
return run_test.ok
run_test.complete = True
parent_failed = False
if parent:
parent_failed = not parent()
# Run the test
fail = None
start = time.time()
CURRENT_TEST = run_test
sys.stdout.write("== Test %s == " % title)
if parent:
sys.stdout.write("\n")
sys.stdout.flush()
try:
if parent_failed:
raise AssertionError('Parent failed: %s' % parent.__name__)
fn()
except AssertionError as e:
fail = str(e)
# Display and handle test result
POSSIBLE += points
if points:
print("%s: %s" % (title, \
(color("red", "FAIL") if fail else color("green", "OK"))), end=' ')
if time.time() - start > 0.1:
print("(%.1fs)" % (time.time() - start), end=' ')
print()
if fail:
print(" %s" % fail.replace("\n", "\n "))
else:
TOTAL += points
for callback in run_test.on_finish:
callback(fail)
CURRENT_TEST = None
run_test.ok = not fail
return run_test.ok
# Record test metadata on the test wrapper function
run_test.__name__ = fn.__name__
run_test.title = title
run_test.complete = False
run_test.ok = False
run_test.on_finish = []
TESTS.append(run_test)
return run_test
return register_test
def end_part(name):
def show_part():
global PART_TOTAL, PART_POSSIBLE
print("Part %s score: %d/%d" % \
(name, TOTAL - PART_TOTAL, POSSIBLE - PART_POSSIBLE))
print()
PART_TOTAL, PART_POSSIBLE = TOTAL, POSSIBLE
show_part.title = ""
TESTS.append(show_part)
def run_tests():
"""Set up for testing and run the registered test functions."""
# Handle command line
global options
parser = OptionParser(usage="usage: %prog [-v] [filters...]")
parser.add_option("-v", "--verbose", action="store_true",
help="print commands")
parser.add_option("--color", choices=["never", "always", "auto"],
default="auto", help="never, always, or auto")
(options, args) = parser.parse_args()
# Start with a full build to catch build errors
make()
# Clean the file system if there is one
reset_fs()
# Run tests
limit = list(map(str.lower, args))
try:
for test in TESTS:
if not limit or any(l in test.title.lower() for l in limit):
test()
if not limit:
print("Score: %d/%d" % (TOTAL, POSSIBLE))
except KeyboardInterrupt:
pass
if TOTAL < POSSIBLE:
sys.exit(1)
def get_current_test():
if not CURRENT_TEST:
raise RuntimeError("No test is running")
return CURRENT_TEST
##################################################################
# Assertions
#
__all__ += ["assert_equal", "assert_lines_match"]
def assert_equal(got, expect, msg=""):
if got == expect:
return
if msg:
msg += "\n"
raise AssertionError("%sgot:\n %s\nexpected:\n %s" %
(msg, str(got).replace("\n", "\n "),
str(expect).replace("\n", "\n ")))
def assert_lines_match(text, *regexps, **kw):
"""Assert that all of regexps match some line in text. If a 'no'
keyword argument is given, it must be a list of regexps that must
*not* match any line in text."""
def assert_lines_match_kw(no=[]):
return no
no = assert_lines_match_kw(**kw)
# Check text against regexps
lines = text.splitlines()
good = set()
bad = set()
for i, line in enumerate(lines):
if any(re.match(r, line) for r in regexps):
good.add(i)
regexps = [r for r in regexps if not re.match(r, line)]
if any(re.match(r, line) for r in no):
bad.add(i)
if not regexps and not bad:
return
# We failed; construct an informative failure message
show = set()
for lineno in good.union(bad):
for offset in range(-2, 3):
show.add(lineno + offset)
if regexps:
show.update(n for n in range(len(lines) - 5, len(lines)))
msg = []
last = -1
for lineno in sorted(show):
if 0 <= lineno < len(lines):
if lineno != last + 1:
msg.append("...")
last = lineno
msg.append("%s %s" % (color("red", "BAD ") if lineno in bad else
color("green", "GOOD") if lineno in good
else " ",
lines[lineno]))
if last != len(lines) - 1:
msg.append("...")
if bad:
msg.append("unexpected lines in output")
for r in regexps:
msg.append(color("red", "MISSING") + " '%s'" % r)
raise AssertionError("\n".join(msg))
##################################################################
# Utilities
#
__all__ += ["make", "maybe_unlink", "reset_fs", "color", "random_str", "check_time", "check_answers"]
MAKE_TIMESTAMP = 0
def pre_make():
"""Delay prior to running make to ensure file mtimes change."""
while int(time.time()) == MAKE_TIMESTAMP:
time.sleep(0.1)
def post_make():
"""Record the time after make completes so that the next run of
make can be delayed if needed."""
global MAKE_TIMESTAMP
MAKE_TIMESTAMP = int(time.time())
def make(*target):
pre_make()
if Popen(("make",) + target).wait():
sys.exit(1)
post_make()
def show_command(cmd):
from pipes import quote
print("\n$", " ".join(map(quote, cmd)))
def maybe_unlink(*paths):
for path in paths:
try:
os.unlink(path)
except EnvironmentError as e:
if e.errno != errno.ENOENT:
raise
COLORS = {"default": "\033[0m", "red": "\033[31m", "green": "\033[32m"}
def color(name, text):
if options.color == "always" or (options.color == "auto" and os.isatty(1)):
return COLORS[name] + text + COLORS["default"]
return text
def reset_fs():
if os.path.exists("obj/fs/clean-fs.img"):
shutil.copyfile("obj/fs/clean-fs.img", "obj/fs/fs.img")
def random_str(n=8):
letters = string.ascii_letters + string.digits
return ''.join(random.choice(letters) for _ in range(n))
def check_time():
try:
print("")
with open('time.txt') as f:
d = f.read().strip()
if not re.match(r'^\d+$', d):
raise AssertionError('time.txt does not contain a single integer (number of hours spent on the lab)')
except IOError:
raise AssertionError('Cannot read time.txt')
def check_answers(file, n=10):
try:
with open(file) as f:
d = f.read().strip()
if len(d) < n:
raise AssertionError('%s does not seem to contain enough text' % file)
except IOError:
raise AssertionError('Cannot read %s' % file)
##################################################################
# Controllers
#
__all__ += ["QEMU", "GDBClient"]
class QEMU(object):
_GDBPORT = None
def __init__(self, *make_args):
# Check that QEMU is not currently running
try:
GDBClient(self.get_gdb_port(), timeout=0).close()
except socket.error:
pass
else:
print("""\
GDB stub found on port %d.
QEMU appears to already be running. Please exit it if possible or use
'killall qemu' or 'killall qemu.real'.""" % self.get_gdb_port(), file=sys.stderr)
sys.exit(1)
if options.verbose:
show_command(("make",) + make_args)
cmd = ("make", "-s", "--no-print-directory") + make_args
self.proc = Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.PIPE)
# Accumulated output as a string
self.output = ""
# Accumulated output as a bytearray
self.outbytes = bytearray()
self.on_output = []
@staticmethod
def get_gdb_port():
if QEMU._GDBPORT is None:
p = Popen(["make", "-s", "--no-print-directory", "print-gdbport"],
stdout=subprocess.PIPE)
(out, _) = p.communicate()
if p.returncode:
raise RuntimeError(
"Failed to get gdbport: make exited with %d" %
p.returncode)
QEMU._GDBPORT = int(out)
return QEMU._GDBPORT
def fileno(self):
if self.proc:
return self.proc.stdout.fileno()
def handle_read(self):
buf = os.read(self.proc.stdout.fileno(), 4096)
self.outbytes.extend(buf)
self.output = self.outbytes.decode("utf-8", "replace")
for callback in self.on_output:
callback(buf)
if buf == b"":
self.wait()
return
def write(self, buf):
if isinstance(buf, str):
buf = buf.encode('utf-8')
self.proc.stdin.write(buf)
self.proc.stdin.flush()
def wait(self):
if self.proc:
self.proc.wait()
self.proc = None
def kill(self):
if self.proc:
self.proc.terminate()
class GDBClient(object):
def __init__(self, port, timeout=15):
start = time.time()
while True:
self.sock = socket.socket()
try:
self.sock.settimeout(1)
self.sock.connect(("localhost", port))
break
except socket.error:
if time.time() >= start + timeout:
raise
self.__buf = ""
def fileno(self):
if self.sock:
return self.sock.fileno()
def handle_read(self):
try:
data = self.sock.recv(4096).decode("ascii", "replace")
except socket.error:
data = ""
if data == "":
self.sock.close()
self.sock = None
return
self.__buf += data
while True:
m = re.search(r"\$([^#]*)#[0-9a-zA-Z]{2}", self.__buf)
if not m:
break
pkt = m.group(1)
self.__buf = self.__buf[m.end():]
if pkt.startswith("T05"):
# Breakpoint
raise TerminateTest
def __send(self, cmd):
packet = "$%s#%02x" % (cmd, sum(map(ord, cmd)) % 256)
self.sock.sendall(packet.encode("ascii"))
def __send_break(self):
self.sock.sendall(b"\x03")
def close(self):
if self.sock:
self.sock.close()
self.sock = None
def cont(self):
self.__send("c")
def breakpoint(self, addr):
self.__send("Z1,%x,1" % addr)
##################################################################
# QEMU test runner
#
__all__ += ["TerminateTest", "Runner"]
class TerminateTest(Exception):
pass
class Runner():
def __init__(self, *default_monitors):
self.__default_monitors = default_monitors
def run_qemu(self, *monitors, **kw):
"""Run a QEMU-based test. monitors should functions that will
be called with this Runner instance once QEMU and GDB are
started. Typically, they should register callbacks that throw
TerminateTest when stop events occur. The target_base
argument gives the make target to run. The make_args argument
should be a list of additional arguments to pass to make. The
timeout argument bounds how long to run before returning."""
def run_qemu_kw(target_base="qemu", make_args=[], timeout=30):
return target_base, make_args, timeout
target_base, make_args, timeout = run_qemu_kw(**kw)
# Start QEMU
pre_make()
self.qemu = QEMU(target_base + "-gdb", *make_args)
self.gdb = None
try:
# Wait for QEMU to start or make to fail. This will set
# self.gdb if QEMU starts.
self.qemu.on_output = [self.__monitor_start]
self.__react([self.qemu], timeout=90)
self.qemu.on_output = []
if self.gdb is None:
print("Failed to connect to QEMU; output:")
print(self.qemu.output)
sys.exit(1)
post_make()
# QEMU and GDB are up
self.reactors = [self.qemu, self.gdb]
# Start monitoring
for m in self.__default_monitors + monitors:
m(self)
# Run and react
self.gdb.cont()
self.__react(self.reactors, timeout)
finally:
# Shutdown QEMU
try:
if self.gdb is None:
sys.exit(1)
self.qemu.kill()
self.__react(self.reactors, 5)
self.gdb.close()
self.qemu.wait()
except:
print("""\
Failed to shutdown QEMU. You might need to 'killall qemu' or
'killall qemu.real'.
""")
raise
def __monitor_start(self, output):
if b"\n" in output:
try:
self.gdb = GDBClient(self.qemu.get_gdb_port(), timeout=2)
raise TerminateTest
except socket.error:
pass
if not len(output):
raise TerminateTest
def __react(self, reactors, timeout):
deadline = time.time() + timeout
try:
while True:
timeleft = deadline - time.time()
if timeleft < 0:
sys.stdout.write("Timeout! ")
sys.stdout.flush()
return
rset = [r for r in reactors if r.fileno() is not None]
if not rset:
return
rset, _, _ = select.select(rset, [], [], timeleft)
for reactor in rset:
reactor.handle_read()
except TerminateTest:
pass
def user_test(self, binary, *monitors, **kw):
"""Run a user test using the specified binary. Monitors and
keyword arguments are as for run_qemu. This runs on a disk
snapshot unless the keyword argument 'snapshot' is False."""
maybe_unlink("obj/kern/init.o", "obj/kern/kernel")
if kw.pop("snapshot", True):
kw.setdefault("make_args", []).append("QEMUEXTRA+=-snapshot")
self.run_qemu(target_base="run-%s" % binary, *monitors, **kw)
def match(self, *args, **kwargs):
"""Shortcut to call assert_lines_match on the most recent QEMU
output."""
assert_lines_match(self.qemu.output, *args, **kwargs)
##################################################################
# Monitors
#
__all__ += ["save", "stop_breakpoint", "call_on_line", "stop_on_line", "shell_script"]
def save(path):
"""Return a monitor that writes QEMU's output to path. If the
test fails, copy the output to path.test-name."""
def setup_save(runner):
f.seek(0)
f.truncate()
runner.qemu.on_output.append(f.write)
get_current_test().on_finish.append(save_on_finish)
def save_on_finish(fail):
f.flush()
save_path = path + "." + get_current_test().__name__[5:]
if fail:
shutil.copyfile(path, save_path)
print(" QEMU output saved to %s" % save_path)
elif os.path.exists(save_path):
os.unlink(save_path)
print(" (Old %s failure log removed)" % save_path)
f = open(path, "wb")
return setup_save
def stop_breakpoint(addr):
"""Returns a monitor that stops when addr is reached. addr may be
a number or the name of a symbol."""
def setup_breakpoint(runner):
if isinstance(addr, str):
addrs = [int(sym[:16], 16) for sym in open("kernel/kernel.sym")
if sym[17:].strip() == addr]
assert len(addrs), "Symbol %s not found" % addr
runner.gdb.breakpoint(addrs[0])
else:
runner.gdb.breakpoint(addr)
return setup_breakpoint
def call_on_line(regexp, callback):
"""Returns a monitor that calls 'callback' when QEMU prints a line
matching 'regexp'."""
def setup_call_on_line(runner):
buf = bytearray()
def handle_output(output):
buf.extend(output)
while b"\n" in buf:
line, buf[:] = buf.split(b"\n", 1)
line = line.decode("utf-8", "replace")
if re.match(regexp, line):
callback(line)
runner.qemu.on_output.append(handle_output)
return setup_call_on_line
def stop_on_line(regexp):
"""Returns a monitor that stops when QEMU prints a line matching
'regexp'."""
def stop(line):
raise TerminateTest
return call_on_line(regexp, stop)
def shell_script(script, terminate_match=None):
"""Returns a monitor that plays the script, and stops when the script is
done executing."""
def setup_call_on_line(runner):
class context:
n = 0
buf = bytearray()
def handle_output(output):
context.buf.extend(output)
if terminate_match is not None:
if re.match(terminate_match, context.buf.decode('utf-8', 'replace')):
raise TerminateTest
if b'$ ' in context.buf:
context.buf = bytearray()
if context.n < len(script):
runner.qemu.write(script[context.n])
runner.qemu.write('\n')
context.n += 1
else:
if terminate_match is None:
raise TerminateTest
runner.qemu.on_output.append(handle_output)
return setup_call_on_line

View File

@ -3,3 +3,4 @@
#define O_RDWR 0x002
#define O_CREATE 0x200
#define O_TRUNC 0x400
#define O_NOFOLLOW 0x800

View File

@ -26,7 +26,7 @@ struct inode {
short minor;
short nlink;
uint size;
uint addrs[NDIRECT+1];
uint addrs[NDIRECT+2];
};
// map major device number to device functions.

View File

@ -416,7 +416,43 @@ bmap(struct inode *ip, uint bn)
brelse(bp);
return addr;
}
bn -= NINDIRECT;
if (bn < NINDIRECT2) {
// Load doubly indirect block L1
if ((addr = ip->addrs[NDIRECT+1]) == 0){
addr = balloc(ip->dev);
if(addr == 0)
return 0;
ip->addrs[NDIRECT+1] = addr;
}
// load doubly indirect block L2
// we have NINDIRECT blocks for per L1 entry
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if ((addr = a[bn / NINDIRECT]) == 0) {
addr = balloc(ip->dev);
if (addr == 0) {
brelse(bp);
return 0;
}
a[bn / NINDIRECT] = addr;
log_write(bp);
}
brelse(bp);
// Load finally the target block
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if ((addr = a[bn % NINDIRECT]) == 0) {
addr = balloc(ip->dev);
if (addr) {
a[bn % NINDIRECT] = addr;
log_write(bp);
}
}
brelse(bp);
return addr;
}
printf("bn=%d\n", bn);
panic("bmap: out of range");
}
@ -425,9 +461,9 @@ bmap(struct inode *ip, uint bn)
void
itrunc(struct inode *ip)
{
int i, j;
struct buf *bp;
uint *a;
int i, j, k;
struct buf *bp, *bp1;
uint *a, *a1;
for(i = 0; i < NDIRECT; i++){
if(ip->addrs[i]){
@ -448,6 +484,26 @@ itrunc(struct inode *ip)
ip->addrs[NDIRECT] = 0;
}
if (ip->addrs[NDIRECT+1]) {
bp = bread(ip->dev, ip->addrs[NDIRECT+1]);
a = (uint*)bp->data;
for(j = 0; j < NINDIRECT; j++){
if(a[j]) {
bp1 = bread(ip->dev, a[j]);
a1 = (uint*)bp1->data;
for (k = 0; k < NINDIRECT; ++ k) {
if (a1[k]) {
bfree(ip->dev, a1[k]);
}
}
brelse(bp1);
bfree(ip->dev, a[j]);
}
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT+1]);
ip->addrs[NDIRECT+1] = 0;
}
ip->size = 0;
iupdate(ip);
}

View File

@ -24,9 +24,10 @@ struct superblock {
#define FSMAGIC 0x10203040
#define NDIRECT 12
#define NDIRECT 11
#define NINDIRECT (BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + NINDIRECT)
#define NINDIRECT2 (NINDIRECT * NINDIRECT)
#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT2)
// On-disk inode structure
struct dinode {
@ -35,7 +36,7 @@ struct dinode {
short minor; // Minor device number (T_DEVICE only)
short nlink; // Number of links to inode in file system
uint size; // Size of file (bytes)
uint addrs[NDIRECT+1]; // Data block addresses
uint addrs[NDIRECT+2]; // Data block addresses
};
// Inodes per block.

View File

@ -1,4 +1,8 @@
#define NPROC 64 // maximum number of processes
#ifdef LAB_FS
#define NPROC 10 // maximum number of processes
#else
#define NPROC 64 // maximum number of processes (speedsup bigfile)
#endif
#define NCPU 8 // maximum number of CPUs
#define NOFILE 16 // open files per process
#define NFILE 100 // open files per system
@ -9,5 +13,15 @@
#define MAXOPBLOCKS 10 // max # of blocks any FS op writes
#define LOGSIZE (MAXOPBLOCKS*3) // max data blocks in on-disk log
#define NBUF (MAXOPBLOCKS*3) // size of disk block cache
#define FSSIZE 2000 // size of file system in blocks
#ifdef LAB_FS
#define FSSIZE 200000 // size of file system in blocks
#else
#ifdef LAB_LOCK
#define FSSIZE 10000 // size of file system in blocks
#else
#define FSSIZE 2000 // size of file system in blocks
#endif
#endif
#define MAXPATH 128 // maximum file path name

View File

@ -1,7 +1,7 @@
#define T_DIR 1 // Directory
#define T_FILE 2 // File
#define T_DEVICE 3 // Device
#define T_SYMLINK 4
struct stat {
int dev; // File system's disk device
uint ino; // Inode number

View File

@ -101,6 +101,7 @@ extern uint64 sys_unlink(void);
extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_close(void);
extern uint64 sys_symlink(void);
// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
@ -126,6 +127,7 @@ static uint64 (*syscalls[])(void) = {
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_symlink] sys_symlink,
};
void

View File

@ -20,3 +20,4 @@
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
#define SYS_symlink 22

View File

@ -335,6 +335,36 @@ sys_open(void)
}
}
if (ip->type == T_SYMLINK && !(omode & O_NOFOLLOW)) {
int symlink_step = 0;
while (ip->type == T_SYMLINK) {
if (symlink_step++ > 10) {
// printf("\x1b[031mFailed\x1b[0m due to deep symlink\n");
iunlockput(ip); // on error, put inode 'cause only success open need to use the file after the syscall
end_op();
return -1;
}
int len_target = readi(ip, 0, (uint64)path, 0, MAXPATH);
iunlockput(ip); // no more need to use this inode
if (len_target == 0) {
printf("\x1b[031mFailed\x1b[0m to read symlink %s\n", path);
end_op();
return -1;
}
// get target's inode
if((ip = namei(path)) == 0){
// printf("\x1b[031mFailed\x1b[0m open symlink target %s\n", path);
end_op();
return -1;
}
ilock(ip);
if(ip->type == T_DIR && omode != O_RDONLY){
iunlockput(ip);
end_op();
return -1;
}
}
}
if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){
iunlockput(ip);
end_op();
@ -503,3 +533,32 @@ sys_pipe(void)
}
return 0;
}
uint64
sys_symlink(void)
{
char target[MAXPATH], path[MAXPATH];
int len_target, len_path;
struct inode* ip;
if ((len_target = argstr(0, target, MAXPATH)) < 0)
return -1;
if ((len_path = argstr(1, path, MAXPATH)) < 0)
return -1;
// printf("\x1b[033msys_symlink\x1b[0m %s -> %s\n", path, target);
begin_op();
ip = create(path, T_SYMLINK, 0, 0);
if (ip == 0) {
end_op();
// printf("\x1b[031mFailed\x1b[0m to create path %s\n", path);
return -1;
}
if (writei(ip, 0, (uint64)target, 0, len_target) != len_target) {
iunlockput(ip);
end_op();
printf("\x1b[031mFailed\x1b[0m to write symlink target %s to %s\n", target, path);
return -1;
}
iunlockput(ip); // no need to keep this inode ref, if not put, in-RAM inode pool will run out
end_op();
return 0;
}

View File

@ -55,6 +55,8 @@ sys_sleep(void)
uint ticks0;
argint(0, &n);
if(n < 0)
n = 0;
acquire(&tickslock);
ticks0 = ticks;
while(ticks - ticks0 < n){

1
time.txt Normal file
View File

@ -0,0 +1 @@
7

57
user/bigfile.c Normal file
View File

@ -0,0 +1,57 @@
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fcntl.h"
#include "kernel/fs.h"
int
main()
{
char buf[BSIZE];
int fd, i, blocks;
fd = open("big.file", O_CREATE | O_WRONLY);
if(fd < 0){
printf("bigfile: cannot open big.file for writing\n");
exit(-1);
}
blocks = 0;
while(1){
*(int*)buf = blocks;
int cc = write(fd, buf, sizeof(buf));
if(cc <= 0)
break;
blocks++;
if (blocks % 100 == 0)
printf(".");
}
printf("\nwrote %d blocks\n", blocks);
if(blocks != 65803) {
printf("bigfile: file is too small\n");
exit(-1);
}
close(fd);
fd = open("big.file", O_RDONLY);
if(fd < 0){
printf("bigfile: cannot re-open big.file for reading\n");
exit(-1);
}
for(i = 0; i < blocks; i++){
int cc = read(fd, buf, sizeof(buf));
if(cc <= 0){
printf("bigfile: read error at block %d\n", i);
exit(-1);
}
if(*(int*)buf != i){
printf("bigfile: read the wrong data (%d) for block %d\n",
*(int*)buf, i);
exit(-1);
}
}
printf("bigfile done; ok\n");
exit(0);
}

View File

@ -1,5 +1,6 @@
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fcntl.h"
#include "user/user.h"
char buf[512];
@ -32,7 +33,7 @@ main(int argc, char *argv[])
}
for(i = 1; i < argc; i++){
if((fd = open(argv[i], 0)) < 0){
if((fd = open(argv[i], O_RDONLY)) < 0){
fprintf(2, "cat: cannot open %s\n", argv[i]);
exit(1);
}

View File

@ -2,6 +2,7 @@
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fcntl.h"
#include "user/user.h"
char buf[1024];
@ -51,7 +52,7 @@ main(int argc, char *argv[])
}
for(i = 2; i < argc; i++){
if((fd = open(argv[i], 0)) < 0){
if((fd = open(argv[i], O_RDONLY)) < 0){
printf("grep: cannot open %s\n", argv[i]);
exit(1);
}

View File

@ -2,6 +2,7 @@
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/fcntl.h"
char*
fmtname(char *path)
@ -30,7 +31,7 @@ ls(char *path)
struct dirent de;
struct stat st;
if((fd = open(path, 0)) < 0){
if((fd = open(path, O_RDONLY)) < 0){
fprintf(2, "ls: cannot open %s\n", path);
return;
}

188
user/symlinktest.c Normal file
View File

@ -0,0 +1,188 @@
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/riscv.h"
#include "kernel/fcntl.h"
#include "kernel/spinlock.h"
#include "kernel/sleeplock.h"
#include "kernel/fs.h"
#include "kernel/file.h"
#include "user/user.h"
#define fail(msg) do {printf("FAILURE: " msg "\n"); failed = 1; goto done;} while (0);
static int failed = 0;
static void testsymlink(void);
static void concur(void);
static void cleanup(void);
int
main(int argc, char *argv[])
{
cleanup();
testsymlink();
concur();
exit(failed);
}
static void
cleanup(void)
{
unlink("/testsymlink/a");
unlink("/testsymlink/b");
unlink("/testsymlink/c");
unlink("/testsymlink/1");
unlink("/testsymlink/2");
unlink("/testsymlink/3");
unlink("/testsymlink/4");
unlink("/testsymlink/z");
unlink("/testsymlink/y");
unlink("/testsymlink");
}
// stat a symbolic link using O_NOFOLLOW
static int
stat_slink(char *pn, struct stat *st)
{
int fd = open(pn, O_RDONLY | O_NOFOLLOW);
if(fd < 0)
return -1;
if(fstat(fd, st) != 0)
return -1;
return 0;
}
static void
testsymlink(void)
{
int r, fd1 = -1, fd2 = -1;
char buf[4] = {'a', 'b', 'c', 'd'};
char c = 0, c2 = 0;
struct stat st;
printf("Start: test symlinks\n");
mkdir("/testsymlink");
fd1 = open("/testsymlink/a", O_CREATE | O_RDWR);
if(fd1 < 0) fail("failed to open a");
r = symlink("/testsymlink/a", "/testsymlink/b");
if(r < 0)
fail("symlink b -> a failed");
if(write(fd1, buf, sizeof(buf)) != 4)
fail("failed to write to a");
if (stat_slink("/testsymlink/b", &st) != 0)
fail("failed to stat b");
if(st.type != T_SYMLINK)
fail("b isn't a symlink");
fd2 = open("/testsymlink/b", O_RDWR);
if(fd2 < 0)
fail("failed to open b");
read(fd2, &c, 1);
if (c != 'a')
fail("failed to read bytes from b");
unlink("/testsymlink/a");
if(open("/testsymlink/b", O_RDWR) >= 0)
fail("Should not be able to open b after deleting a");
r = symlink("/testsymlink/b", "/testsymlink/a");
if(r < 0)
fail("symlink a -> b failed");
r = open("/testsymlink/b", O_RDWR);
if(r >= 0)
fail("Should not be able to open b (cycle b->a->b->..)\n");
r = symlink("/testsymlink/nonexistent", "/testsymlink/c");
if(r != 0)
fail("Symlinking to nonexistent file should succeed\n");
r = symlink("/testsymlink/2", "/testsymlink/1");
if(r) fail("Failed to link 1->2");
r = symlink("/testsymlink/3", "/testsymlink/2");
if(r) fail("Failed to link 2->3");
r = symlink("/testsymlink/4", "/testsymlink/3");
if(r) fail("Failed to link 3->4");
close(fd1);
close(fd2);
fd1 = open("/testsymlink/4", O_CREATE | O_RDWR);
if(fd1<0) fail("Failed to create 4\n");
fd2 = open("/testsymlink/1", O_RDWR);
if(fd2<0) fail("Failed to open 1\n");
c = '#';
r = write(fd2, &c, 1);
if(r!=1) fail("Failed to write to 1\n");
r = read(fd1, &c2, 1);
if(r!=1) fail("Failed to read from 4\n");
if(c!=c2)
fail("Value read from 4 differed from value written to 1\n");
printf("test symlinks: ok\n");
done:
close(fd1);
close(fd2);
}
static void
concur(void)
{
int pid, i;
int fd;
struct stat st;
int nchild = 2;
printf("Start: test concurrent symlinks\n");
fd = open("/testsymlink/z", O_CREATE | O_RDWR);
if(fd < 0) {
printf("FAILED: open failed");
exit(1);
}
close(fd);
for(int j = 0; j < nchild; j++) {
pid = fork();
if(pid < 0){
printf("FAILED: fork failed\n");
exit(1);
}
if(pid == 0) {
int m = 0;
unsigned int x = (pid ? 1 : 97);
for(i = 0; i < 100; i++){
x = x * 1103515245 + 12345;
if((x % 3) == 0) {
symlink("/testsymlink/z", "/testsymlink/y");
if (stat_slink("/testsymlink/y", &st) == 0) {
m++;
if(st.type != T_SYMLINK) {
printf("FAILED: not a symbolic link\n", st.type);
exit(1);
}
}
} else {
unlink("/testsymlink/y");
}
}
exit(0);
}
}
int r;
for(int j = 0; j < nchild; j++) {
wait(&r);
if(r != 0) {
printf("test concurrent symlinks: failed\n");
exit(1);
}
}
printf("test concurrent symlinks: ok\n");
}

View File

@ -22,6 +22,7 @@ int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
int symlink(char* target, char* path);
// ulib.c
int stat(const char*, struct stat*);

View File

@ -36,3 +36,4 @@ entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
entry("symlink");

View File

@ -1,5 +1,6 @@
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fcntl.h"
#include "user/user.h"
char buf[512];
@ -43,7 +44,7 @@ main(int argc, char *argv[])
}
for(i = 1; i < argc; i++){
if((fd = open(argv[i], 0)) < 0){
if((fd = open(argv[i], O_RDONLY)) < 0){
printf("wc: cannot open %s\n", argv[i]);
exit(1);
}