diff options
author | obrien <obrien@FreeBSD.org> | 2013-02-08 16:10:16 +0000 |
---|---|---|
committer | obrien <obrien@FreeBSD.org> | 2013-02-08 16:10:16 +0000 |
commit | 3028e3f8aba938dfd0bf9fda987b8a72140b8027 (patch) | |
tree | b2f038222ff8a70f687652441df00d2b564c8abe /usr.bin | |
parent | 952a6d5a7cd3d3f9007acfa06805262fc04a105f (diff) | |
parent | 1d08d5f677c1dfa810e381073590adbae19cc69f (diff) | |
download | FreeBSD-src-3028e3f8aba938dfd0bf9fda987b8a72140b8027.zip FreeBSD-src-3028e3f8aba938dfd0bf9fda987b8a72140b8027.tar.gz |
Sync with HEAD.
Diffstat (limited to 'usr.bin')
115 files changed, 11599 insertions, 587 deletions
diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 17aafa8..2923f74 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -123,6 +123,7 @@ SUBDIR= alias \ pagesize \ passwd \ paste \ + patch \ pathchk \ perror \ pr \ diff --git a/usr.bin/apply/apply.c b/usr.bin/apply/apply.c index f63ba31..c886660 100644 --- a/usr.bin/apply/apply.c +++ b/usr.bin/apply/apply.c @@ -55,7 +55,7 @@ __FBSDID("$FreeBSD$"); #define EXEC "exec " -static int exec_shell(const char *, char *, char *); +static int exec_shell(const char *, const char *, const char *); static void usage(void); int @@ -222,7 +222,7 @@ main(int argc, char *argv[]) * arguments. */ static int -exec_shell(const char *command, char *use_shell, char *use_name) +exec_shell(const char *command, const char *use_shell, const char *use_name) { pid_t pid; int omask, pstat; @@ -250,7 +250,7 @@ exec_shell(const char *command, char *use_shell, char *use_name) return(pid == -1 ? -1 : pstat); } -void +static void usage(void) { diff --git a/usr.bin/bc/bc.y b/usr.bin/bc/bc.y index f7425f2..a802f5a 100644 --- a/usr.bin/bc/bc.y +++ b/usr.bin/bc/bc.y @@ -1084,7 +1084,7 @@ escape(const char *str) } /* ARGSUSED */ -void +static void sigchld(int signo) { pid_t pid; diff --git a/usr.bin/bmake/Makefile b/usr.bin/bmake/Makefile index 44ce56f..6cac1ec 100644 --- a/usr.bin/bmake/Makefile +++ b/usr.bin/bmake/Makefile @@ -10,109 +10,109 @@ SRCTOP?= ${.CURDIR:H:H} # look here first for config.h CFLAGS+= -I${.CURDIR} -# $NetBSD: Makefile,v 1.56 2012/05/30 21:54:23 sjg Exp $ -# @(#)Makefile 5.2 (Berkeley) 12/28/90 - -# $Id: Makefile.in,v 1.174 2012/10/10 18:46:24 sjg Exp $ +# for after-import +CLEANDIRS+= FreeBSD +CLEANFILES+= bootstrap -PROG?= ${.CURDIR:T} -SRCS= arch.c buf.c compat.c cond.c dir.c for.c hash.c job.c main.c \ - make.c parse.c str.c suff.c targ.c trace.c var.c util.c -SRCS+= strlist.c -SRCS+= make_malloc.c -SRCS+= lstAppend.c lstAtEnd.c lstAtFront.c lstClose.c lstConcat.c \ - lstDatum.c lstDeQueue.c lstDestroy.c lstDupl.c lstEnQueue.c \ - lstFind.c lstFindFrom.c lstFirst.c lstForEach.c lstForEachFrom.c \ - lstInit.c lstInsert.c lstIsAtEnd.c lstIsEmpty.c lstLast.c \ - lstMember.c lstNext.c lstOpen.c lstRemove.c lstReplace.c lstSucc.c -SRCS += lstPrev.c - -# you can use this Makefile if you have an earlier version of bmake. -prefix= /usr -srcdir= ${SRCTOP}/contrib/bmake -CC?= gcc +# $Id: Makefile,v 1.5 2013/01/28 19:31:58 sjg Exp $ # Base version on src date -MAKE_VERSION= 20121010 -DEFAULT_SYS_PATH = .../share/mk:/usr/share/mk +MAKE_VERSION= 20130123 -CPPFLAGS+= -CFLAGS+= ${CPPFLAGS} -CFLAGS+= -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\" -CFLAGS+= -I. -I${srcdir} -DHAVE_CONFIG_H ${XDEFS} -DMAKE_NATIVE -CFLAGS+= ${CFLAGS_${.TARGET:T}} -CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}} -COPTS.main.c+= "-DMAKE_VERSION=\"${MAKE_VERSION}\"" -LDFLAGS= -LIBOBJS= ${LIBOBJDIR}stresep$U.o -LDADD= +PROG?= ${.CURDIR:T} + +SRCS= \ + arch.c \ + buf.c \ + compat.c \ + cond.c \ + dir.c \ + for.c \ + hash.c \ + job.c \ + main.c \ + make.c \ + make_malloc.c \ + meta.c \ + parse.c \ + str.c \ + strlist.c \ + suff.c \ + targ.c \ + trace.c \ + util.c \ + var.c + +# from lst.lib/ +SRCS+= \ + lstAppend.c \ + lstAtEnd.c \ + lstAtFront.c \ + lstClose.c \ + lstConcat.c \ + lstDatum.c \ + lstDeQueue.c \ + lstDestroy.c \ + lstDupl.c \ + lstEnQueue.c \ + lstFind.c \ + lstFindFrom.c \ + lstFirst.c \ + lstForEach.c \ + lstForEachFrom.c \ + lstInit.c \ + lstInsert.c \ + lstIsAtEnd.c \ + lstIsEmpty.c \ + lstLast.c \ + lstMember.c \ + lstNext.c \ + lstOpen.c \ + lstPrev.c \ + lstRemove.c \ + lstReplace.c \ + lstSucc.c + +# this file gets generated by configure +.sinclude "Makefile.config" .if !empty(LIBOBJS) SRCS+= ${LIBOBJS:T:.o=.c} .endif -USE_META = yes -.if ${USE_META} != "no" -SRCS+= meta.c +# just in case +prefix?= /usr +srcdir?= ${.CURDIR} + +DEFAULT_SYS_PATH?= .../share/mk:${prefix}/share/mk + CPPFLAGS+= -DUSE_META +CFLAGS+= ${CPPFLAGS} +CFLAGS+= -D_PATH_DEFSYSPATH=\"${DEFAULT_SYS_PATH}\" +CFLAGS+= -I. -I${srcdir} ${XDEFS} -DMAKE_NATIVE +CFLAGS+= ${COPTS.${.ALLSRC:M*.c:T:u}} +COPTS.main.c+= "-DMAKE_VERSION=\"${MAKE_VERSION}\"" + +# meta mode can be useful even without filemon FILEMON_H ?= /usr/include/dev/filemon/filemon.h .if exists(${FILEMON_H}) && ${FILEMON_H:T} == "filemon.h" COPTS.meta.c += -DHAVE_FILEMON_H -I${FILEMON_H:H} .endif -.endif .PATH: ${srcdir} .PATH: ${srcdir}/lst.lib -OS!= uname -s -ARCH!= uname -p 2>/dev/null || uname -m - -# list of OS's which are derrived from BSD4.4 -isBSD44= NetBSD FreeBSD OpenBSD DragonFly - -.if ${OS} == "NetBSD" -# Don't set these for anyone else since we don't know what the effect may be. -# On FreeBSD WARNS=2 sets a bunch of -W flags that make does not handle. -WFORMAT= 1 -WARNS=4 -.NOPATH: bmake.cat1 -.if make(install) && exists(${DESTDIR}/usr/share/doc) -SUBDIR= PSD.doc -.endif -.endif - -.if defined(.PARSEDIR) -# we cannot rely on anything but bmake to parse this correctly. -.if empty(isBSD44:M${OS}) -MANTARGET=cat -INSTALL?=${srcdir}/install-sh -.if (${MACHINE} == "sun386") -# even I don't have one of these anymore :-) -CFLAGS+= -DPORTAR -.elif (${MACHINE} != "sunos") -SRCS+= sigcompat.c -CFLAGS+= -DSIGNAL_FLAGS=SA_RESTART -.endif -.endif - .if make(obj) || make(clean) SUBDIR+= unit-tests .endif -.endif - -# many systems use gcc these days -CC_IS_GCC=yes -.if ${CC_IS_GCC} == "yes" -# problem with gcc3 -CFLAGS_var.o+= -Wno-cast-qual -.endif -CFLAGS_main.o+= "-DMACHINE=\"${MACHINE}\"" "-DMACHINE_ARCH=\"${MACHINE_ARCH}\"" -EXTRACT_MAN=no +MAN= ${PROG}.1 +MAN1= ${MAN} -MAN=${PROG}.1 .if (${PROG} != "make") +CLEANFILES+= my.history +.if make(${MAN}) || !exists(${srcdir}/${MAN}) my.history: ${MAKEFILE} @(echo ".Nm"; \ echo "is derived from NetBSD"; \ @@ -120,19 +120,16 @@ my.history: ${MAKEFILE} echo "It uses autoconf to facilitate portability to other platforms."; \ echo ".Pp") > $@ +.NOPATH: ${MAN} ${MAN}: make.1 my.history - @echo making ${PROG}.1 + @echo making $@ @sed -e 's/^.Nx/NetBSD/' -e '/^.Nm/s/make/${PROG}/' \ -e '/^.Sh HISTORY/rmy.history' \ -e '/^.Sh HISTORY/,$$s,^.Nm,make,' ${srcdir}/make.1 > $@ +all beforeinstall: ${MAN} +_mfromdir=. .endif - -.if !empty(isBSD44:M${OS}) -.if "${OS}" != "NetBSD" -MAN1=${MAN} -.endif -MANTARGET?=man .endif MANTARGET?= cat @@ -142,58 +139,26 @@ MANDEST?= ${MANDIR}/${MANTARGET}1 _mfromdir=${srcdir} .endif -.if exists(${srcdir}/../Makefile.inc) -.include "${srcdir}/../Makefile.inc" -.endif -.sinclude <bsd.prog.mk> -# sigh, FreeBSD at least includes bsd.subdir.mk via bsd.obj.mk -# so the inclusion below, results in complaints about re-defined -# targets. For NetBSD though we need to explicitly include it. -.if defined(.PARSEDIR) -.if defined(SUBDIR) && !target(${SUBDIR:[1]}) -.sinclude <bsd.subdir.mk> -.endif -.endif +.include <bsd.prog.mk> -CPPFLAGS+= -DMAKE_NATIVE +CPPFLAGS+= -DMAKE_NATIVE -DHAVE_CONFIG_H COPTS.var.c += -Wno-cast-qual COPTS.job.c += -Wno-format-nonliteral COPTS.parse.c += -Wno-format-nonliteral COPTS.var.c += -Wno-format-nonliteral # Force these +SHAREDIR= ${prefix}/share BINDIR= ${prefix}/bin -MANDIR= ${prefix}/man +MANDIR= ${SHAREDIR}/man + +.if !exists(.depend) +${OBJS}: config.h +.endif -arch.o: config.h # make sure that MAKE_VERSION gets updated. main.o: ${SRCS} ${MAKEFILE} -MK?=${prefix}/share/mk -MKSRC?=mk -INSTALL?=${srcdir}/install-sh - -beforeinstall: - test -d ${DESTDIR}${BINDIR} || ${INSTALL} -m 775 -d ${DESTDIR}${BINDIR} - test -d ${DESTDIR}${MANDEST} || ${INSTALL} -m 775 -d ${DESTDIR}${MANDEST} - -# latest version of *.mk includes an installer. -# you should not need to set USE_OS -install-mk: -.if exists(${MKSRC}/install-mk) - test -d ${DESTDIR}${MK} || ${INSTALL} -m 775 -d ${DESTDIR}${MK} - ${MKSRC}/install-mk -v -m 644 ${DESTDIR}${MK} ${USE_OS} -.else - @echo need to unpack mk.tar.gz under ${srcdir} or set MKSRC; false -.endif - -.ifdef TOOLDIR -# this is a native netbsd build, -# use libutil rather than the local emalloc etc. -CPPFLAGS+= -DUSE_EMALLOC -LDADD+=-lutil -DPADD+=${LIBUTIL} -.endif # A simple unit-test driver to help catch regressions accept test: @@ -205,7 +170,6 @@ MANDIR= /usr/share/man/man # make sure we get this CFLAGS+= ${COPTS.${.IMPSRC:T}} -CLEANFILES+= bootstrap after-import: ${SRCTOP}/contrib/bmake/bsd.after-import.mk cd ${.CURDIR} && ${.MAKE} -f ${SRCTOP}/contrib/bmake/bsd.after-import.mk diff --git a/usr.bin/bmake/Makefile.config b/usr.bin/bmake/Makefile.config new file mode 100644 index 0000000..e4c4d3d --- /dev/null +++ b/usr.bin/bmake/Makefile.config @@ -0,0 +1,21 @@ +# This is a generated file, do NOT edit! +# See contrib/bmake/bsd.after-import.mk +# +# $FreeBSD$ + +SRCTOP?= ${.CURDIR:H:H} + +# things set by configure + +prefix= /usr +srcdir= ${SRCTOP}/contrib/bmake +CC?= gcc +DEFAULT_SYS_PATH= .../share/mk:/usr/share/mk + +CPPFLAGS+= +CFLAGS+= ${CPPFLAGS} -DHAVE_CONFIG_H +LDFLAGS= +LIBOBJS= ${LIBOBJDIR}stresep$U.o +LDADD= +USE_META= yes +FILEMON_H= /usr/include/dev/filemon/filemon.h diff --git a/usr.bin/bmake/Makefile.inc b/usr.bin/bmake/Makefile.inc index 4b126c7..c8ca110 100644 --- a/usr.bin/bmake/Makefile.inc +++ b/usr.bin/bmake/Makefile.inc @@ -11,4 +11,8 @@ PROG= make .endif +.if ${MK_SHARED_TOOLCHAIN} == "no" +NO_SHARED?= YES +.endif + WARNS=3 diff --git a/usr.bin/bmake/config.h b/usr.bin/bmake/config.h index 64777b8..32026c7 100644 --- a/usr.bin/bmake/config.h +++ b/usr.bin/bmake/config.h @@ -1,6 +1,6 @@ +/* $FreeBSD$ */ /* config.h. Generated from config.h.in by configure. */ /* config.h.in. Generated from configure.in by autoheader. */ -/* $FreeBSD$ */ /* Define if building universal (internal helper macro) */ /* #undef AC_APPLE_UNIVERSAL_BUILD */ @@ -230,7 +230,7 @@ #define PACKAGE_NAME "bmake" /* Define to the full name and version of this package. */ -#define PACKAGE_STRING "bmake 20120620" +#define PACKAGE_STRING "bmake 20121212" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "bmake" @@ -239,7 +239,7 @@ #define PACKAGE_URL "" /* Define to the version of this package. */ -#define PACKAGE_VERSION "20120620" +#define PACKAGE_VERSION "20121212" /* Define as the return type of signal handlers (`int' or `void'). */ #define RETSIGTYPE void diff --git a/usr.bin/bmake/unit-tests/Makefile b/usr.bin/bmake/unit-tests/Makefile index c22dbc5..0b9db1e 100644 --- a/usr.bin/bmake/unit-tests/Makefile +++ b/usr.bin/bmake/unit-tests/Makefile @@ -5,9 +5,9 @@ SRCTOP?= ${.CURDIR:H:H:H} -# $Id: Makefile.in,v 1.38 2012/06/19 23:38:48 sjg Exp $ +# $Id: Makefile.in,v 1.40 2012/12/28 21:28:19 sjg Exp $ # -# $NetBSD: Makefile,v 1.34 2012/06/19 23:25:53 sjg Exp $ +# $NetBSD: Makefile,v 1.35 2012/11/09 19:08:28 sjg Exp $ # # Unit tests for make(1) # The main targets are: @@ -47,6 +47,7 @@ SUBFILES= \ modorder \ modts \ modword \ + order \ phony-end \ posix \ qequals \ @@ -59,6 +60,7 @@ SUBFILES= \ all: ${SUBFILES} flags.doterror= +flags.order=-j1 # the tests are actually done with sub-makes. .PHONY: ${SUBFILES} @@ -77,10 +79,12 @@ TOOL_TR?= tr TOOL_DIFF?= diff DIFF_FLAGS?= -u +.if defined(.PARSEDIR) # ensure consistent results from sort(1) LC_ALL= C LANG= C .export LANG LC_ALL +.endif # The driver. # We always pretend .MAKE was called 'make' diff --git a/usr.bin/calendar/calendars/calendar.freebsd b/usr.bin/calendar/calendars/calendar.freebsd index 5f517a6..f7f449b 100644 --- a/usr.bin/calendar/calendars/calendar.freebsd +++ b/usr.bin/calendar/calendars/calendar.freebsd @@ -165,6 +165,7 @@ 05/22 Clive Tong-I Lin <clive@FreeBSD.org> born in Changhua, Taiwan, Republic of China, 1978 05/22 Michael Bushkov <bushman@FreeBSD.org> born in Rostov-on-Don, Russian Federation, 1985 05/22 Rui Paulo <rpaulo@FreeBSD.org> born in Evora, Portugal, 1986 +05/22 David Naylor <dbn@FreeBSD.org> born in Johannesburg, South Africa, 1988 05/23 Munechika Sumikawa <sumikawa@FreeBSD.org> born in Osaka, Osaka, Japan, 1972 05/24 Duncan McLennan Barclay <dmlb@FreeBSD.org> born in London, Middlesex, United Kingdom, 1970 05/24 Oliver Lehmann <oliver@FreeBSD.org> born in Karlsburg, Germany, 1981 @@ -297,6 +298,7 @@ 09/30 Hiten M. Pandya <hmp@FreeBSD.org> born in Dar-es-Salaam, Tanzania, East Africa, 1986 09/30 Third quarter status reports are due on 10/15 10/02 Beat Gaetzi <beat@FreeBSD.org> born in Zurich, Switzerland, 1980 +10/02 Grzegorz Blach <gblach@FreeBSD.org> born in Starachowice, Poland, 1985 10/05 Hiroki Sato <hrs@FreeBSD.org> born in Yamagata, Japan, 1977 10/05 Chris Costello <chris@FreeBSD.org> born in Houston, Texas, United States, 1985 10/09 Stefan Walter <stefan@FreeBSD.org> born in Werne, Nordrhein-Westfalen, Germany, 1978 diff --git a/usr.bin/calendar/calendars/calendar.history b/usr.bin/calendar/calendars/calendar.history index de8fb00..a05a6ff 100644 --- a/usr.bin/calendar/calendars/calendar.history +++ b/usr.bin/calendar/calendars/calendar.history @@ -411,7 +411,7 @@ 11/09 Roosevelt establishes the Civil Works Administration, 1933 11/10 41 Women arrested in suffragette demonstrations near White House, 1917 11/10 Cpt. Wirz, commandant of Andersonville Prison hanged, 1865 -11/10 Henry Stanley asks David Livingston, "Dr. Livingston, I presume?", 1871 +11/10 Henry Stanley asks David Livingstone, "Dr. Livingstone, I presume?", 1871 11/11 Washington becomes the 42nd state, 1889 11/12 Dr. Sun Yat-sen's Birthday in Taiwan 11/12 USA first exports oil to Europe, 1861 diff --git a/usr.bin/calendar/parsedata.c b/usr.bin/calendar/parsedata.c index 63e4395..4ece663 100644 --- a/usr.bin/calendar/parsedata.c +++ b/usr.bin/calendar/parsedata.c @@ -940,15 +940,13 @@ indextooffset(char *s) { int i; struct fixs *n; + char *es; if (s[0] == '+' || s[0] == '-') { - char ss[9]; - for (i = -100; i < 100; i++) { - sprintf(ss, "%s%d", (i > 0) ? "+" : "", i); - if (strcmp(ss, s) == 0) - return (i); - } - return (0); + i = strtol (s, &es, 10); + if (*es != '\0') /* trailing junk */ + errx (1, "Invalid specifier format: %s\n", s); + return (i); } for (i = 0; i < 6; i++) { diff --git a/usr.bin/chpass/chpass.c b/usr.bin/chpass/chpass.c index 2504e68..9f9be53 100644 --- a/usr.bin/chpass/chpass.c +++ b/usr.bin/chpass/chpass.c @@ -241,8 +241,11 @@ main(int argc, char *argv[]) #ifdef YP case _PWF_NIS: ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_host); - if (ypclnt == NULL || - ypclnt_connect(ypclnt) == -1 || + if (ypclnt == NULL) { + warnx("ypclnt_new failed"); + exit(1); + } + if (ypclnt_connect(ypclnt) == -1 || ypclnt_passwd(ypclnt, pw, password) == -1) { warnx("%s", ypclnt->error); ypclnt_free(ypclnt); diff --git a/usr.bin/clang/clang-tblgen/Makefile b/usr.bin/clang/clang-tblgen/Makefile index bc949ec..9e8af45 100644 --- a/usr.bin/clang/clang-tblgen/Makefile +++ b/usr.bin/clang/clang-tblgen/Makefile @@ -6,6 +6,8 @@ NO_MAN= SRCDIR= tools/clang/utils/TableGen SRCS= ClangASTNodesEmitter.cpp \ ClangAttrEmitter.cpp \ + ClangCommentCommandInfoEmitter.cpp \ + ClangCommentHTMLTagsEmitter.cpp \ ClangDiagnosticsEmitter.cpp \ ClangSACheckersEmitter.cpp \ NeonEmitter.cpp \ diff --git a/usr.bin/clang/clang/Makefile b/usr.bin/clang/clang/Makefile index 7c8f0e4..18a768b 100644 --- a/usr.bin/clang/clang/Makefile +++ b/usr.bin/clang/clang/Makefile @@ -20,9 +20,11 @@ MLINKS= clang.1 clang++.1 \ .if ${MK_CLANG_IS_CC} != "no" LINKS+= ${BINDIR}/clang ${BINDIR}/cc \ ${BINDIR}/clang ${BINDIR}/c++ \ + ${BINDIR}/clang ${BINDIR}/CC \ ${BINDIR}/clang ${BINDIR}/cpp MLINKS+= clang.1 cc.1 \ clang.1 c++.1 \ + clang.1 CC.1 \ clang.1 cpp.1 .endif @@ -33,6 +35,19 @@ TGHDRS= CC1AsOptions \ DiagnosticLexKinds \ DiagnosticSemaKinds \ Options + +.if !defined(EARLY_BUILD) && defined(MK_CLANG_FULL) && ${MK_CLANG_FULL} != "no" +_clangstaticanalyzer= \ + clangstaticanalyzerfrontend \ + clangstaticanalyzercheckers \ + clangstaticanalyzercore +_clangarcmigrate= \ + clangarcmigrate +_clangrewriter= \ + clangrewritefrontend \ + clangrewritecore +.endif # !EARLY_BUILD && MK_CLANG_FULL + LIBDEPS=clangfrontendtool \ clangfrontend \ clangdriver \ @@ -40,12 +55,10 @@ LIBDEPS=clangfrontendtool \ clangcodegen \ clangparse \ clangsema \ - clangstaticanalyzerfrontend \ - clangstaticanalyzercheckers \ - clangstaticanalyzercore \ + ${_clangstaticanalyzer} \ clanganalysis \ - clangarcmigrate \ - clangrewrite \ + ${_clangarcmigrate} \ + ${_clangrewriter} \ clangedit \ clangast \ clanglex \ @@ -64,12 +77,12 @@ LIBDEPS=clangfrontendtool \ llvmarmdesc \ llvmarminfo \ llvmarminstprinter \ - llvmmipscodegen \ - llvmmipsdisassembler \ llvmmipsasmparser \ + llvmmipscodegen \ llvmmipsdesc \ - llvmmipsinfo \ llvmmipsinstprinter \ + llvmmipsdisassembler \ + llvmmipsinfo \ llvmpowerpccodegen \ llvmpowerpcdesc \ llvmpowerpcinfo \ diff --git a/usr.bin/clang/clang/clang.1 b/usr.bin/clang/clang/clang.1 index 214fe0e..1a37c6d 100644 --- a/usr.bin/clang/clang/clang.1 +++ b/usr.bin/clang/clang/clang.1 @@ -1,5 +1,5 @@ .\" $FreeBSD$ -.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16) +.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.20) .\" .\" Standard preamble: .\" ======================================================================== @@ -125,7 +125,7 @@ .\" ======================================================================== .\" .IX Title "CLANG 1" -.TH CLANG 1 "2012-08-09" "clang 3.2" "Clang Tools Documentation" +.TH CLANG 1 "2012-12-21" "clang 3.2" "Clang Tools Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -231,8 +231,8 @@ Treat subsequent input files as having type \fIlanguage\fR. .IP "\fB\-std\fR=\fIlanguage\fR" 4 .IX Item "-std=language" Specify the language standard to compile for. -.IP "\fB\-stdlib\fR=\fIlanguage\fR" 4 -.IX Item "-stdlib=language" +.IP "\fB\-stdlib\fR=\fIlibrary\fR" 4 +.IX Item "-stdlib=library" Specify the \*(C+ standard library to use; supported options are libstdc++ and libc++. .IP "\fB\-ansi\fR" 4 diff --git a/usr.bin/clang/llc/Makefile b/usr.bin/clang/llc/Makefile index b99f876..3606fef 100644 --- a/usr.bin/clang/llc/Makefile +++ b/usr.bin/clang/llc/Makefile @@ -13,12 +13,12 @@ LIBDEPS=llvmasmparser \ llvmarmdesc \ llvmarminfo \ llvmarminstprinter \ - llvmmipscodegen \ - llvmmipsdisassembler \ llvmmipsasmparser \ + llvmmipscodegen \ llvmmipsdesc \ - llvmmipsinfo \ llvmmipsinstprinter \ + llvmmipsdisassembler \ + llvmmipsinfo \ llvmpowerpccodegen \ llvmpowerpcdesc \ llvmpowerpcinfo \ diff --git a/usr.bin/clang/lli/Makefile b/usr.bin/clang/lli/Makefile index 6ce563b..d6ecab5 100644 --- a/usr.bin/clang/lli/Makefile +++ b/usr.bin/clang/lli/Makefile @@ -3,9 +3,13 @@ PROG_CXX=lli SRCDIR= tools/lli -SRCS= lli.cpp +SRCS= lli.cpp \ + RecordingMemoryManager.cpp \ + RemoteTarget.cpp -LIBDEPS=llvmasmparser \ +LIBDEPS=llvmx86asmparser \ + llvmx86disassembler \ + llvmasmparser \ llvmbitreader \ llvmx86codegen \ llvmx86desc \ diff --git a/usr.bin/clang/llvm-mc/Makefile b/usr.bin/clang/llvm-mc/Makefile index 1c08894..c2106e1 100644 --- a/usr.bin/clang/llvm-mc/Makefile +++ b/usr.bin/clang/llvm-mc/Makefile @@ -14,12 +14,12 @@ LIBDEPS=llvmmcdisassembler \ llvmarmdesc \ llvmarminfo \ llvmarminstprinter \ - llvmmipscodegen \ - llvmmipsdisassembler \ llvmmipsasmparser \ + llvmmipscodegen \ llvmmipsdesc \ - llvmmipsinfo \ llvmmipsinstprinter \ + llvmmipsdisassembler \ + llvmmipsinfo \ llvmpowerpccodegen \ llvmpowerpcdesc \ llvmpowerpcinfo \ diff --git a/usr.bin/clang/llvm-objdump/Makefile b/usr.bin/clang/llvm-objdump/Makefile index 351c06c..38d23ae 100644 --- a/usr.bin/clang/llvm-objdump/Makefile +++ b/usr.bin/clang/llvm-objdump/Makefile @@ -16,12 +16,12 @@ LIBDEPS=llvmmcdisassembler \ llvmarmdesc \ llvmarminfo \ llvmarminstprinter \ - llvmmipscodegen \ - llvmmipsdisassembler \ llvmmipsasmparser \ + llvmmipscodegen \ llvmmipsdesc \ - llvmmipsinfo \ llvmmipsinstprinter \ + llvmmipsdisassembler \ + llvmmipsinfo \ llvmpowerpccodegen \ llvmpowerpcdesc \ llvmpowerpcinfo \ diff --git a/usr.bin/clang/llvm-rtdyld/Makefile b/usr.bin/clang/llvm-rtdyld/Makefile index e3b5d87..e899d70 100644 --- a/usr.bin/clang/llvm-rtdyld/Makefile +++ b/usr.bin/clang/llvm-rtdyld/Makefile @@ -15,12 +15,12 @@ LIBDEPS=llvmjit \ llvmarmdesc \ llvmarminfo \ llvmarminstprinter \ - llvmmipscodegen \ - llvmmipsdisassembler \ llvmmipsasmparser \ + llvmmipscodegen \ llvmmipsdesc \ - llvmmipsinfo \ llvmmipsinstprinter \ + llvmmipsdisassembler \ + llvmmipsinfo \ llvmpowerpccodegen \ llvmpowerpcdesc \ llvmpowerpcinfo \ diff --git a/usr.bin/clang/opt/Makefile b/usr.bin/clang/opt/Makefile index 14403ce1..5afd58d 100644 --- a/usr.bin/clang/opt/Makefile +++ b/usr.bin/clang/opt/Makefile @@ -9,7 +9,34 @@ SRCS= AnalysisWrappers.cpp \ opt.cpp TGHDRS= Intrinsics -LIBDEPS=llvmipo \ +LIBDEPS=llvmarmdisassembler \ + llvmarmasmparser \ + llvmarmcodegen \ + llvmarmdesc \ + llvmarminfo \ + llvmarminstprinter \ + llvmmipsasmparser \ + llvmmipscodegen \ + llvmmipsdesc \ + llvmmipsinstprinter \ + llvmmipsdisassembler \ + llvmmipsinfo \ + llvmpowerpccodegen \ + llvmpowerpcdesc \ + llvmpowerpcinfo \ + llvmpowerpcinstprinter \ + llvmx86asmparser \ + llvmx86codegen \ + llvmselectiondag \ + llvmasmprinter \ + llvmmcparser \ + llvmcodegen \ + llvmx86disassembler \ + llvmx86desc \ + llvmx86info \ + llvmx86instprinter \ + llvmx86utils \ + llvmipo \ llvmvectorize \ llvmscalaropts \ llvminstcombine \ diff --git a/usr.bin/clang/tblgen/Makefile b/usr.bin/clang/tblgen/Makefile index c74ad83..90a5789 100644 --- a/usr.bin/clang/tblgen/Makefile +++ b/usr.bin/clang/tblgen/Makefile @@ -10,6 +10,7 @@ SRCS= AsmMatcherEmitter.cpp \ CodeEmitterGen.cpp \ CodeGenDAGPatterns.cpp \ CodeGenInstruction.cpp \ + CodeGenMapTable.cpp \ CodeGenRegisters.cpp \ CodeGenSchedule.cpp \ CodeGenTarget.cpp \ diff --git a/usr.bin/cut/cut.1 b/usr.bin/cut/cut.1 index d4e6876..5b1d052 100644 --- a/usr.bin/cut/cut.1 +++ b/usr.bin/cut/cut.1 @@ -31,7 +31,7 @@ .\" @(#)cut.1 8.1 (Berkeley) 6/6/93 .\" $FreeBSD$ .\" -.Dd December 21, 2006 +.Dd August 8, 2012 .Dt CUT 1 .Os .Sh NAME @@ -47,7 +47,7 @@ .Op Ar .Nm .Fl f Ar list -.Op Fl d Ar delim +.Op Fl w | Fl d Ar delim .Op Fl s .Op Ar .Sh DESCRIPTION @@ -119,6 +119,9 @@ that form the character are selected. .It Fl s Suppress lines with no field delimiter characters. Unless specified, lines with no delimiters are passed through unmodified. +.It Fl w +Use whitespace (spaces and tabs) as the delimiter. +Consecutive spaces and tabs count as one single field separator. .El .Sh ENVIRONMENT The diff --git a/usr.bin/cut/cut.c b/usr.bin/cut/cut.c index 0a3a2a9..e343e5a 100644 --- a/usr.bin/cut/cut.c +++ b/usr.bin/cut/cut.c @@ -58,6 +58,7 @@ static int dflag; static int fflag; static int nflag; static int sflag; +static int wflag; static size_t autostart, autostop, maxval; static char * positions; @@ -67,6 +68,7 @@ static int b_n_cut(FILE *, const char *); static int c_cut(FILE *, const char *); static int f_cut(FILE *, const char *); static void get_list(char *); +static int is_delim(wchar_t); static void needpos(size_t); static void usage(void); @@ -84,7 +86,7 @@ main(int argc, char *argv[]) dchar = '\t'; /* default delimiter is \t */ strcpy(dcharmb, "\t"); - while ((ch = getopt(argc, argv, "b:c:d:f:sn")) != -1) + while ((ch = getopt(argc, argv, "b:c:d:f:snw")) != -1) switch(ch) { case 'b': get_list(optarg); @@ -111,6 +113,9 @@ main(int argc, char *argv[]) case 'n': nflag = 1; break; + case 'w': + wflag = 1; + break; case '?': default: usage(); @@ -119,9 +124,9 @@ main(int argc, char *argv[]) argv += optind; if (fflag) { - if (bflag || cflag || nflag) + if (bflag || cflag || nflag || (wflag && dflag)) usage(); - } else if (!(bflag || cflag) || dflag || sflag) + } else if (!(bflag || cflag) || dflag || sflag || wflag) usage(); else if (!bflag && nflag) usage(); @@ -359,18 +364,30 @@ out: } static int +is_delim(wchar_t ch) +{ + if (wflag) { + if (ch == ' ' || ch == '\t') + return 1; + } else { + if (ch == dchar) + return 1; + } + return 0; +} + +static int f_cut(FILE *fp, const char *fname) { wchar_t ch; int field, i, isdelim; char *pos, *p; - wchar_t sep; int output; char *lbuf, *mlbuf; size_t clen, lbuflen, reallen; mlbuf = NULL; - for (sep = dchar; (lbuf = fgetln(fp, &lbuflen)) != NULL;) { + while ((lbuf = fgetln(fp, &lbuflen)) != NULL) { reallen = lbuflen; /* Assert EOL has a newline. */ if (*(lbuf + lbuflen - 1) != '\n') { @@ -394,7 +411,7 @@ f_cut(FILE *fp, const char *fname) if (clen == 0) clen = 1; /* this should work if newline is delimiter */ - if (ch == sep) + if (is_delim(ch)) isdelim = 1; if (ch == '\n') { if (!isdelim && !sflag) @@ -421,8 +438,13 @@ f_cut(FILE *fp, const char *fname) if (clen == 0) clen = 1; p += clen; - if (ch == '\n' || ch == sep) + if (ch == '\n' || is_delim(ch)) { + /* compress whitespace */ + if (wflag && ch != '\n') + while (is_delim(*p)) + p++; break; + } if (*pos) for (i = 0; i < (int)clen; i++) putchar(p[i - clen]); @@ -452,6 +474,6 @@ usage(void) (void)fprintf(stderr, "%s\n%s\n%s\n", "usage: cut -b list [-n] [file ...]", " cut -c list [file ...]", - " cut -f list [-s] [-d delim] [file ...]"); + " cut -f list [-s] [-w | -d delim] [file ...]"); exit(1); } diff --git a/usr.bin/dc/bcode.c b/usr.bin/dc/bcode.c index 022cf87..f17a0da 100644 --- a/usr.bin/dc/bcode.c +++ b/usr.bin/dc/bcode.c @@ -29,8 +29,6 @@ __FBSDID("$FreeBSD$"); #include "extern.h" -BIGNUM zero; - #define __inline #define MAX_ARRAY_INDEX 2048 @@ -250,8 +248,12 @@ init_bmachine(bool extended_registers) if (bmachine.readstack == NULL) err(1, NULL); bmachine.obase = bmachine.ibase = 10; - BN_init(&zero); - bn_check(BN_zero(&zero)); +} + +u_int +bmachine_scale(void) +{ + return (bmachine.scale); } /* Reset the things needed before processing a (new) file */ @@ -407,7 +409,7 @@ split_number(const struct number *n, BIGNUM *i, BIGNUM *f) } } -__inline void +void normalize(struct number *n, u_int s) { @@ -427,7 +429,7 @@ void negate(struct number *n) { - bn_check(BN_sub(n->number, &zero, n->number)); + BN_set_negative(n->number, !BN_is_negative(n->number)); } static __inline void @@ -581,7 +583,7 @@ set_scale(void) n = pop_number(); if (n != NULL) { - if (BN_cmp(n->number, &zero) < 0) + if (BN_is_negative(n->number)) warnx("scale must be a nonnegative number"); else { scale = get_ulong(n); @@ -878,7 +880,7 @@ load_array(void) if (inumber == NULL) return; idx = get_ulong(inumber); - if (BN_cmp(inumber->number, &zero) < 0) + if (BN_is_negative(inumber->number)) warnx("negative idx"); else if (idx == BN_MASK2 || idx > MAX_ARRAY_INDEX) warnx("idx too big"); @@ -917,7 +919,7 @@ store_array(void) return; } idx = get_ulong(inumber); - if (BN_cmp(inumber->number, &zero) < 0) { + if (BN_is_negative(inumber->number)) { warnx("negative idx"); stack_free_value(value); } else if (idx == BN_MASK2 || idx > MAX_ARRAY_INDEX) { @@ -1009,7 +1011,7 @@ bsub(void) } void -bmul_number(struct number *r, struct number *a, struct number *b) +bmul_number(struct number *r, struct number *a, struct number *b, u_int scale) { BN_CTX *ctx; @@ -1023,11 +1025,9 @@ bmul_number(struct number *r, struct number *a, struct number *b) bn_check(BN_mul(r->number, a->number, b->number, ctx)); BN_CTX_free(ctx); - if (rscale > bmachine.scale && rscale > ascale && rscale > bscale) { - r->scale = rscale; - normalize(r, max(bmachine.scale, max(ascale, bscale))); - } else - r->scale = rscale; + r->scale = rscale; + if (rscale > bmachine.scale && rscale > ascale && rscale > bscale) + normalize(r, max(scale, max(ascale, bscale))); } static void @@ -1046,7 +1046,7 @@ bmul(void) } r = new_number(); - bmul_number(r, a, b); + bmul_number(r, a, b, bmachine.scale); push_number(r); free_number(a); @@ -1172,7 +1172,7 @@ static void bexp(void) { struct number *a, *p, *r; - u_int scale; + u_int rscale; bool neg; p = pop_number(); @@ -1185,15 +1185,27 @@ bexp(void) return; } - if (p->scale != 0) - warnx("Runtime warning: non-zero scale in exponent"); + if (p->scale != 0) { + BIGNUM *i, *f; + i = BN_new(); + bn_checkp(i); + f = BN_new(); + bn_checkp(f); + split_number(p, i, f); + if (!BN_is_zero(f)) + warnx("Runtime warning: non-zero fractional part " + "in exponent"); + BN_free(i); + BN_free(f); + } + normalize(p, 0); neg = false; - if (BN_cmp(p->number, &zero) < 0) { + if (BN_is_negative(p->number)) { neg = true; negate(p); - scale = bmachine.scale; + rscale = bmachine.scale; } else { /* Posix bc says min(a.scale * b, max(a.scale, scale) */ u_long b; @@ -1201,30 +1213,37 @@ bexp(void) b = BN_get_word(p->number); m = max(a->scale, bmachine.scale); - scale = a->scale * (u_int)b; - if (scale > m || (a->scale > 0 && (b == BN_MASK2 || + rscale = a->scale * (u_int)b; + if (rscale > m || (a->scale > 0 && (b == BN_MASK2 || b > UINT_MAX))) - scale = m; + rscale = m; } if (BN_is_zero(p->number)) { r = new_number(); bn_check(BN_one(r->number)); - normalize(r, scale); + normalize(r, rscale); } else { + u_int ascale, mscale; + + ascale = a->scale; while (!BN_is_bit_set(p->number, 0)) { - bmul_number(a, a, a); + ascale *= 2; + bmul_number(a, a, a, ascale); bn_check(BN_rshift1(p->number, p->number)); } r = dup_number(a); - normalize(r, scale); bn_check(BN_rshift1(p->number, p->number)); + mscale = ascale; while (!BN_is_zero(p->number)) { - bmul_number(a, a, a); - if (BN_is_bit_set(p->number, 0)) - bmul_number(r, r, a); + ascale *= 2; + bmul_number(a, a, a, ascale); + if (BN_is_bit_set(p->number, 0)) { + mscale += ascale; + bmul_number(r, r, a, mscale); + } bn_check(BN_rshift1(p->number, p->number)); } @@ -1237,13 +1256,18 @@ bexp(void) bn_check(BN_one(one)); ctx = BN_CTX_new(); bn_checkp(ctx); - scale_number(one, r->scale + scale); - normalize(r, scale); - bn_check(BN_div(r->number, NULL, one, r->number, ctx)); + scale_number(one, r->scale + rscale); + + if (BN_is_zero(r->number)) + warnx("divide by zero"); + else + bn_check(BN_div(r->number, NULL, one, + r->number, ctx)); BN_free(one); BN_CTX_free(ctx); + r->scale = rscale; } else - normalize(r, scale); + normalize(r, rscale); } push_number(r); free_number(a); @@ -1282,7 +1306,7 @@ bsqrt(void) if (BN_is_zero(n->number)) { r = new_number(); push_number(r); - } else if (BN_cmp(n->number, &zero) < 0) + } else if (BN_is_negative(n->number)) warnx("square root of negative number"); else { scale = max(bmachine.scale, n->scale); diff --git a/usr.bin/dc/bcode.h b/usr.bin/dc/bcode.h index 9290cf1..7dc5d2d 100644 --- a/usr.bin/dc/bcode.h +++ b/usr.bin/dc/bcode.h @@ -85,6 +85,7 @@ struct source { void init_bmachine(bool); void reset_bmachine(struct source *); +u_int bmachine_scale(void); void scale_number(BIGNUM *, int); void normalize(struct number *, u_int); void eval(void); @@ -93,6 +94,4 @@ void pbn(const char *, const BIGNUM *); void negate(struct number *); void split_number(const struct number *, BIGNUM *, BIGNUM *); void bmul_number(struct number *, struct number *, - struct number *); - -extern BIGNUM zero; + struct number *, u_int); diff --git a/usr.bin/dc/inout.c b/usr.bin/dc/inout.c index 4a2bb70..e35f0ad 100644 --- a/usr.bin/dc/inout.c +++ b/usr.bin/dc/inout.c @@ -322,7 +322,7 @@ printnumber(FILE *f, const struct number *b, u_int base) i++; } sz = i; - if (BN_cmp(b->number, &zero) < 0) + if (BN_is_negative(b->number)) putcharwrap(f, '-'); for (i = 0; i < sz; i++) { p = stack_popstring(&stack); @@ -353,7 +353,8 @@ printnumber(FILE *f, const struct number *b, u_int base) putcharwrap(f, ' '); i = 1; - bmul_number(fract_part, fract_part, num_base); + bmul_number(fract_part, fract_part, num_base, + bmachine_scale()); split_number(fract_part, int_part->number, NULL); rem = BN_get_word(int_part->number); p = get_digit(rem, digits, base); @@ -402,8 +403,8 @@ print_ascii(FILE *f, const struct number *n) v = BN_dup(n->number); bn_checkp(v); - if (BN_cmp(v, &zero) < 0) - bn_check(BN_sub(v, &zero, v)); + if (BN_is_negative(v)) + BN_set_negative(v, 0); numbits = BN_num_bytes(v) * 8; while (numbits > 0) { diff --git a/usr.bin/dtc/HACKING b/usr.bin/dtc/HACKING new file mode 100644 index 0000000..4fb46da --- /dev/null +++ b/usr.bin/dtc/HACKING @@ -0,0 +1,65 @@ +$FreeBSD$ + +Notes for people hacking on dtc +=============================== + +This file contains some notes for people wishing to hack on dtc. + +Upstreaming +----------- + +This code is developed in the FreeBSD svn repository: + +https://svn.freebsd.org/base/head/usr.bin/dtc + +If you got the source from anywhere else and wish to make changes, please +ensure that you are working against the latest version, or you may end up +fixing bugs that are already fixed upstream. Although the license makes no +requirement that you share any improvements that you make, patches are very +welcome. + +C++11 +----- + +This project currently aims to compile with g++ 4.2.1 and so doesn't make any +use of C++11 features. It would be a good idea to relax this restriction once +clang is the default compiler for ARM, MIPS and PowerPC. + +This code makes use of a lot of iterator loops, which would be cleaner using +the new syntax in C++11. It also explicitly deletes a lot of objects held in +collections in destructors that have these collections as their members. This +could be simplified by using `shared_ptr`. + +The code does make use of `static_assert()`, but uses a macro in utility.hh to +remove these if they are not supported. The FreeBSD standard headers also +define a compatibility macro the implements static asserts in terms of an array +with 1 element on success and -1 elements on failure. + +Adding New Checks +----------------- + +Currently, the biggest weakness of this version of the tool is that it lacks +most of the semantic checkers that can be implemented by simply reading the +ePAPR spec. The `checker` class provides a simple superclass for implementing +these quite easily. There are also helper methods on `device_tree` for finding +specific nodes, for checks that require some understanding of the structure of +the tree. + +We should probably add a parent pointer to the `node` class for easily walking +up the tree. + +Adding Direct C Output +---------------------- + +The FreeBSD build system currently uses dtc to generate a blob and then +converts this to C source code. A new `output_writer` subclass could easily +generate the C directly. + +Parser Improvements +------------------- + +There are a few FIXME lines in the parser for some corner cases that are not +currently used by FreeBSD. These are mainly related to labels in the middle of +values. These can be fixed by creating a new `property_value` with the +specified label, starting at the location of the label. Don't forget to remove +the associated comments from the BUGS section of the man page if you fix this. diff --git a/usr.bin/dtc/Makefile b/usr.bin/dtc/Makefile new file mode 100644 index 0000000..78973fc --- /dev/null +++ b/usr.bin/dtc/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +PROG_CXX=dtc +SRCS= dtc.cc input_buffer.cc string.cc dtb.cc fdt.cc checking.cc +MAN= dtc.1 + +WARNS?= 3 + +NO_SHARED?=NO + +.include <bsd.prog.mk> diff --git a/usr.bin/dtc/checking.cc b/usr.bin/dtc/checking.cc new file mode 100644 index 0000000..5462a49 --- /dev/null +++ b/usr.bin/dtc/checking.cc @@ -0,0 +1,211 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "checking.hh" +#include <stdio.h> + +namespace dtc +{ +namespace fdt +{ +namespace checking +{ + +bool +checker::visit_node(device_tree *tree, node *n) +{ + path.push_back(std::make_pair(n->name, n->unit_address)); + // Check this node + if (!check_node(tree, n)) + { + return false; + } + // Now check its properties + for (node::property_iterator i=n->property_begin(), e=n->property_end() + ; i!=e ; ++i) + { + if (!check_property(tree, n, *i)) + { + return false; + } + } + // And then recursively check the children + for (node::child_iterator i=n->child_begin(), e=n->child_end() ; i!=e ; + ++i) + { + if (!visit_node(tree, *i)) + { + return false; + } + } + path.pop_back(); + return true; +} + +void +checker::report_error(const char *errmsg) +{ + fprintf(stderr, "Error: %s, while checking node: ", errmsg); + for (device_tree::node_path::iterator p=path.begin()+1, pe=path.end() ; + p!=pe ; ++p) + { + putc('/', stderr); + p->first.dump(); + if (!(p->second.empty())) + { + putc('@', stderr); + p->second.dump(); + } + } + fprintf(stderr, " [-W%s]\n", checker_name); +} + +bool +property_checker::check_property(device_tree *tree, node *n, property *p) +{ + if (p->get_key() == key) + { + if (!check(tree, n, p)) + { + report_error("property check failed"); + return false; + } + } + return true; +} + +bool +property_size_checker::check(device_tree *tree, node *n, property *p) +{ + uint32_t psize = 0; + for (property::value_iterator i=p->begin(),e=p->end() ; i!=e ; ++i) + { + if (!i->is_binary()) + { + return false; + } + psize += i->byte_data.size(); + } + return psize == size; +} + +template<property_value::value_type T> +void +check_manager::add_property_type_checker(const char *name, string prop) +{ + checkers.insert(std::make_pair(string(name), + new property_type_checker<T>(name, prop))); +} + +void +check_manager::add_property_size_checker(const char *name, + string prop, + uint32_t size) +{ + checkers.insert(std::make_pair(string(name), + new property_size_checker(name, prop, size))); +} + +check_manager::~check_manager() +{ + while (checkers.begin() != checkers.end()) + { + delete checkers.begin()->second; + checkers.erase(checkers.begin()); + } + while (disabled_checkers.begin() != disabled_checkers.end()) + { + delete disabled_checkers.begin()->second; + } +} + +check_manager::check_manager() +{ + // NOTE: All checks listed here MUST have a corresponding line + // in the man page! + add_property_type_checker<property_value::STRING_LIST>( + "type-compatible", string("compatible")); + add_property_type_checker<property_value::STRING>( + "type-model", string("model")); + add_property_size_checker("type-phandle", string("phandle"), 4); +} + +bool +check_manager::run_checks(device_tree *tree, bool keep_going) +{ + bool success = true; + for (std::map<string, checker*>::iterator i=checkers.begin(), + e=checkers.end() ; i!=e ; ++i) + { + success &= i->second->check_tree(tree); + if (!(success || keep_going)) + { + break; + } + } + return success; +} + +bool +check_manager::disable_checker(string name) +{ + std::map<string, checker*>::iterator checker = checkers.find(name); + if (checker != checkers.end()) + { + disabled_checkers.insert(std::make_pair(name, + checker->second)); + checkers.erase(checker); + return true; + } + return false; +} + +bool +check_manager::enable_checker(string name) +{ + std::map<string, checker*>::iterator checker = + disabled_checkers.find(name); + if (checker != disabled_checkers.end()) + { + checkers.insert(std::make_pair(name, checker->second)); + disabled_checkers.erase(checker); + return true; + } + return false; +} + +} // namespace checking + +} // namespace fdt + +} // namespace dtc + diff --git a/usr.bin/dtc/checking.hh b/usr.bin/dtc/checking.hh new file mode 100644 index 0000000..0de1d60 --- /dev/null +++ b/usr.bin/dtc/checking.hh @@ -0,0 +1,308 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _CHECKING_HH_ +#define _CHECKING_HH_ +#include "string.hh" +#include "fdt.hh" + +namespace dtc +{ +namespace fdt +{ +namespace checking +{ +/** + * Base class for all checkers. This will visit the entire tree and perform + * semantic checks defined in subclasses. Note that device trees are generally + * small (a few dozen nodes at most) and so we optimise for flexibility and + * extensibility here, not for performance. Each checker will visit the entire + * tree. + */ +class checker +{ + /** + * The path to the current node being checked. This is used for + * printing error messages. + */ + device_tree::node_path path; + /** + * The name of the checker. This is used for printing error messages + * and for enabling / disabling specific checkers from the command + * line. + */ + const char *checker_name; + /** + * Visits each node, calling the checker functions on properties and + * nodes. + */ + bool visit_node(device_tree *tree, node *n); + protected: + /** + * Prints the error message, along with the path to the node that + * caused the error and the name of the checker. + */ + void report_error(const char *errmsg); + public: + /** + * Constructor. Takes the name of this checker, which is which is used + * when reporting errors. + */ + checker(const char *name) : checker_name(name) {} + /** + * Virtual destructor in case any subclasses need to do cleanup. + */ + virtual ~checker() {} + /** + * Method for checking that a node is valid. The root class version + * does nothing, subclasses should override this. + */ + virtual bool check_node(device_tree *tree, node *n) + { + return true; + } + /** + * Method for checking that a property is valid. The root class + * version does nothing, subclasses should override this. + */ + virtual bool check_property(device_tree *tree, node *n, property *p) + { + return true; + } + /** + * Runs the checker on the specified device tree. + */ + bool check_tree(fdt::device_tree *tree) + { + return visit_node(tree, tree->get_root()); + } +}; + +/** + * Abstract base class for simple property checks. This class defines a check + * method for subclasses, which is invoked only when it finds a property with + * the matching name. To define simple property checkers, just subclass this + * and override the check() method. + */ +class property_checker : public checker +{ + /** + * The name of the property that this checker is looking for. + */ + string key; + public: + /** + * Implementation of the generic property-checking method that checks + * for a property with the name specified in the constructor + */ + virtual bool check_property(device_tree *tree, node *n, property *p); + /** + * Constructor. Takes the name of the checker and the name of the + * property to check. + */ + property_checker(const char* name, string property_name) + : checker(name), key(property_name) {} + /** + * The check method, which subclasses should implement. + */ + virtual bool check(device_tree *tree, node *n, property *p) = 0; +}; + +/** + * Property type checker. + */ +template<property_value::value_type T> +struct property_type_checker : public property_checker +{ + /** + * Constructor, takes the name of the checker and the name of the + * property to check as arguments. + */ + property_type_checker(const char* name, string property_name) : + property_checker(name, property_name) {} + virtual bool check(device_tree *tree, node *n, property *p) = 0; +}; + +/** + * Empty property checker. This checks that the property has no value. + */ +template<> +struct property_type_checker <property_value::EMPTY> : public property_checker +{ + property_type_checker(const char* name, string property_name) : + property_checker(name, property_name) {} + virtual bool check(device_tree *tree, node *n, property *p) + { + return p->begin() == p->end(); + } +}; + +/** + * String property checker. This checks that the property has exactly one + * value, which is a string. + */ +template<> +struct property_type_checker <property_value::STRING> : public property_checker +{ + property_type_checker(const char* name, string property_name) : + property_checker(name, property_name) {} + virtual bool check(device_tree *tree, node *n, property *p) + { + return (p->begin() + 1 == p->end()) && p->begin()->is_string(); + } +}; +/** + * String list property checker. This checks that the property has at least + * one value, all of which are strings. + */ +template<> +struct property_type_checker <property_value::STRING_LIST> : + public property_checker +{ + property_type_checker(const char* name, string property_name) : + property_checker(name, property_name) {} + virtual bool check(device_tree *tree, node *n, property *p) + { + for (property::value_iterator i=p->begin(),e=p->end() ; i!=e ; + ++i) + { + if (!(i->is_string() || i->is_string_list())) + { + return false; + } + } + return p->begin() != p->end(); + } +}; + +/** + * Phandle property checker. This checks that the property has exactly one + * value, which is a valid phandle. + */ +template<> +struct property_type_checker <property_value::PHANDLE> : public property_checker +{ + property_type_checker(const char* name, string property_name) : + property_checker(name, property_name) {} + virtual bool check(device_tree *tree, node *n, property *p) + { + return (p->begin() + 1 == p->end()) && + (tree->referenced_node(*p->begin()) != 0); + } +}; + +/** + * Check that a property has the correct size. + */ +struct property_size_checker : public property_checker +{ + /** + * The expected size of the property. + */ + uint32_t size; + public: + /** + * Constructor, takes the name of the checker, the name of the property + * to check, and its expected size as arguments. + */ + property_size_checker(const char* name, string property_name, uint32_t bytes) + : property_checker(name, property_name), size(bytes) {} + /** + * Check, validates that the property has the correct size. + */ + virtual bool check(device_tree *tree, node *n, property *p); +}; + + +/** + * The check manager is the interface to running the checks. This allows + * default checks to be enabled, non-default checks to be enabled, and so on. + */ +class check_manager +{ + /** + * The enabled checkers, indexed by their names. The name is used when + * disabling checkers from the command line. When this manager runs, + * it will only run the checkers from this map. + */ + std::map<string, checker*> checkers; + /** + * The disabled checkers. Moving checkers to this list disables them, + * but allows them to be easily moved back. + */ + std::map<string, checker*> disabled_checkers; + /** + * Helper function for adding a property value checker. + */ + template<property_value::value_type T> + void add_property_type_checker(const char *name, string prop); + /** + * Helper function for adding a simple type checker. + */ + void add_property_type_checker(const char *name, string prop); + /** + * Helper function for adding a property value checker. + */ + void add_property_size_checker(const char *name, + string prop, + uint32_t size); + public: + /** + * Delete all of the checkers that are part of this checker manager. + */ + ~check_manager(); + /** + * Default constructor, creates check manager containing all of the + * default checks. + */ + check_manager(); + /** + * Run all of the checks on the specified tree. + */ + bool run_checks(device_tree *tree, bool keep_going); + /** + * Disables the named checker. + */ + bool disable_checker(string name); + /** + * Enables the named checker. + */ + bool enable_checker(string name); +}; + +} // namespace checking + +} // namespace fdt + +} // namespace dtc + +#endif // !_CHECKING_HH_ diff --git a/usr.bin/dtc/dtb.cc b/usr.bin/dtc/dtb.cc new file mode 100644 index 0000000..986ef6f --- /dev/null +++ b/usr.bin/dtc/dtb.cc @@ -0,0 +1,312 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "dtb.hh" +#include <sys/types.h> +#include <inttypes.h> +#include <stdio.h> +#include <unistd.h> + + +namespace dtc +{ +namespace dtb +{ + +void output_writer::write_data(byte_buffer b) +{ + for (byte_buffer::iterator i=b.begin(), e=b.end(); i!=e ; i++) + { + write_data(*i); + } +} + +void +binary_writer::write_string(string name) +{ + name.push_to_buffer(buffer); + // Trailing nul + buffer.push_back(0); +} + +void +binary_writer::write_data(uint8_t v) +{ + buffer.push_back(v); +} + +void +binary_writer::write_data(uint32_t v) +{ + while (buffer.size() % 4 != 0) + { + buffer.push_back(0); + } + push_big_endian(buffer, v); +} + +void +binary_writer::write_data(uint64_t v) +{ + while (buffer.size() % 8 != 0) + { + buffer.push_back(0); + } + push_big_endian(buffer, v); +} + +void +binary_writer::write_to_file(int fd) +{ + // FIXME: Check return + write(fd, buffer.data(), buffer.size()); +} + +uint32_t +binary_writer::size() +{ + return buffer.size(); +} + +void +asm_writer::write_string(const char *c) +{ + while (*c) + { + buffer.push_back((uint8_t)*(c++)); + } +} + +void +asm_writer::write_line(const char *c) +{ + if (byte_count != 0) + { + byte_count = 0; + buffer.push_back('\n'); + } + write_string(c); +} + +void +asm_writer::write_byte(uint8_t b) +{ + char out[3] = {0}; + if (byte_count++ == 0) + { + buffer.push_back('\t'); + } + write_string(".byte 0x"); + snprintf(out, 3, "%.2hhx", b); + buffer.push_back(out[0]); + buffer.push_back(out[1]); + if (byte_count == 4) + { + buffer.push_back('\n'); + byte_count = 0; + } + else + { + buffer.push_back(';'); + buffer.push_back(' '); + } +} + +void +asm_writer::write_label(string name) +{ + write_line("\t.globl "); + name.push_to_buffer(buffer); + buffer.push_back('\n'); + name.push_to_buffer(buffer); + buffer.push_back(':'); + buffer.push_back('\n'); + buffer.push_back('_'); + name.push_to_buffer(buffer); + buffer.push_back(':'); + buffer.push_back('\n'); + +} + +void +asm_writer::write_comment(string name) +{ + write_line("\t/* "); + name.push_to_buffer(buffer); + write_string(" */\n"); +} + +void +asm_writer::write_string(string name) +{ + write_line("\t.string \""); + name.push_to_buffer(buffer); + write_line("\"\n"); + bytes_written += name.size() + 1; +} + +void +asm_writer::write_data(uint8_t v) +{ + write_byte(v); + bytes_written++; +} + +void +asm_writer::write_data(uint32_t v) +{ + if (bytes_written % 4 != 0) + { + write_line("\t.balign 4\n"); + bytes_written += (4 - (bytes_written % 4)); + } + write_byte((v >> 24) & 0xff); + write_byte((v >> 16) & 0xff); + write_byte((v >> 8) & 0xff); + write_byte((v >> 0) & 0xff); + bytes_written += 4; +} + +void +asm_writer::write_data(uint64_t v) +{ + if (bytes_written % 8 != 0) + { + write_line("\t.balign 8\n"); + bytes_written += (8 - (bytes_written % 8)); + } + write_byte((v >> 56) & 0xff); + write_byte((v >> 48) & 0xff); + write_byte((v >> 40) & 0xff); + write_byte((v >> 32) & 0xff); + write_byte((v >> 24) & 0xff); + write_byte((v >> 16) & 0xff); + write_byte((v >> 8) & 0xff); + write_byte((v >> 0) & 0xff); + bytes_written += 8; +} + +void +asm_writer::write_to_file(int fd) +{ + // FIXME: Check return + write(fd, buffer.data(), buffer.size()); +} + +uint32_t +asm_writer::size() +{ + return bytes_written; +} + +void +header::write(output_writer &out) +{ + out.write_label(string("dt_blob_start")); + out.write_label(string("dt_header")); + out.write_comment("magic"); + out.write_data(magic); + out.write_comment("totalsize"); + out.write_data(totalsize); + out.write_comment("off_dt_struct"); + out.write_data(off_dt_struct); + out.write_comment("off_dt_strings"); + out.write_data(off_dt_strings); + out.write_comment("off_mem_rsvmap"); + out.write_data(off_mem_rsvmap); + out.write_comment("version"); + out.write_data(version); + out.write_comment("last_comp_version"); + out.write_data(last_comp_version); + out.write_comment("boot_cpuid_phys"); + out.write_data(boot_cpuid_phys); + out.write_comment("size_dt_strings"); + out.write_data(size_dt_strings); + out.write_comment("size_dt_struct"); + out.write_data(size_dt_struct); +} + +bool +header::read_dtb(input_buffer &input) +{ + if (!(input.consume_binary(magic) && magic == 0xd00dfeed)) + { + fprintf(stderr, "Missing magic token in header. Got %" PRIx32 + " expected 0xd00dfeed\n", magic); + return false; + } + return input.consume_binary(totalsize) && + input.consume_binary(off_dt_struct) && + input.consume_binary(off_dt_strings) && + input.consume_binary(off_mem_rsvmap) && + input.consume_binary(version) && + input.consume_binary(last_comp_version) && + input.consume_binary(boot_cpuid_phys) && + input.consume_binary(size_dt_strings) && + input.consume_binary(size_dt_struct); +} +uint32_t +string_table::add_string(string str) +{ + std::map<string, uint32_t>::iterator old = string_offsets.find(str); + if (old == string_offsets.end()) + { + uint32_t start = size; + // Don't forget the trailing nul + size += str.size() + 1; + string_offsets.insert(std::make_pair(str, start)); + strings.push_back(str); + return start; + } + else + { + return old->second; + } +} + +void +string_table::write(dtb::output_writer &writer) +{ + writer.write_comment(string("Strings table.")); + writer.write_label(string("dt_strings_start")); + for (std::vector<string>::iterator i=strings.begin(), e=strings.end() ; + i!=e ; ++i) + { + writer.write_string(*i); + } + writer.write_label(string("dt_strings_end")); +} + +} // namespace dtb + +} // namespace dtc + diff --git a/usr.bin/dtc/dtb.hh b/usr.bin/dtc/dtb.hh new file mode 100644 index 0000000..2c5f39e --- /dev/null +++ b/usr.bin/dtc/dtb.hh @@ -0,0 +1,365 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _DTB_HH_ +#define _DTB_HH_ +#include <map> +#include "string.hh" + +#include <assert.h> + +namespace dtc +{ +/** + * The dtb namespace contains code related to the generation of device tree + * blobs, the binary representation of flattened device trees. The abstract + * tree representation calls into this code to generate the output. + */ +namespace dtb +{ +/** The token types in the DTB, as defined by §7.4.1 of the ePAPR + * specification. All of these values are written in big-endian format in the + * output. + */ +enum token_type +{ + /** + * Marker indicating the start of a node in the tree. This is followed + * by the nul-terminated name. If a unit address is specified, then + * the name also contains the address, with an @ symbol between the end + * of the name and the start of the address. + * + * The name is then padded such that the next token begins on a 4-byte + * boundary. The node may contain properties, other nodes, both, or be + * empty. + */ + FDT_BEGIN_NODE = 0x00000001, + /** + * Marker indicating the end of a node. + */ + FDT_END_NODE = 0x00000002, + /** + * The start of a property. This is followed by two 32-bit big-endian + * values. The first indicates the length of the property value, the + * second its index in the strings table. It is then followed by the + * property value, if the value is of non-zero length. + */ + FDT_PROP = 0x00000003, + /** + * Ignored token. May be used for padding inside DTB nodes. + */ + FDT_NOP = 0x00000004, + /** + * Marker indicating the end of the tree. + */ + FDT_END = 0x00000009 +}; + +/** + * Returns the token as a string. This is used for debugging and for printing + * human-friendly error messages about malformed DTB input. + */ +inline const char *token_type_name(token_type t) +{ + switch(t) + { + case FDT_BEGIN_NODE: + return "FDT_BEGIN_NODE"; + case FDT_END_NODE: + return "FDT_END_NODE"; + case FDT_PROP: + return "FDT_PROP"; + case FDT_NOP: + return "FDT_NOP"; + case FDT_END: + return "FDT_END"; + } + assert(0); +} + +/** + * Abstract class for writing a section of the output. We create one + * of these for each section that needs to be written. It is intended to build + * a temporary buffer of the output in memory and then write it to a file + * stream. The size can be returned after all of the data has been written + * into the internal buffer, so the sizes of the three tables can be calculated + * before storing them in the buffer. + */ +struct output_writer +{ + /** + * Writes a label into the output stream. This is only applicable for + * assembly output, where the labels become symbols that can be + * resolved at link time. + */ + virtual void write_label(string name) = 0; + /** + * Writes a comment into the output stream. Useful only when debugging + * the output. + */ + virtual void write_comment(string name) = 0; + /** + * Writes a string. A nul terminator is implicitly added. + */ + virtual void write_string(string name) = 0; + /** + * Writes a single 8-bit value. + */ + virtual void write_data(uint8_t) = 0; + /** + * Writes a single 32-bit value. The value is written in big-endian + * format, but should be passed in the host's native endian. + */ + virtual void write_data(uint32_t) = 0; + /** + * Writes a single 64-bit value. The value is written in big-endian + * format, but should be passed in the host's native endian. + */ + virtual void write_data(uint64_t) = 0; + /** + * Writes the collected output to the specified file descriptor. + */ + virtual void write_to_file(int fd) = 0; + /** + * Returns the number of bytes. + */ + virtual uint32_t size() = 0; + /** + * Helper for writing tokens to the output stream. This writes a + * comment above the token describing its value, for easier debugging + * of the output. + */ + inline void write_token(token_type t) + { + write_comment(token_type_name(t)); + write_data((uint32_t)t); + } + /** + * Helper function that writes a byte buffer to the output, one byte at + * a time. + */ + void write_data(byte_buffer b); +}; + +/** + * Binary file writer. This class is responsible for writing the DTB output + * directly in blob format. + */ +class binary_writer : public output_writer +{ + /** + * The internal buffer used to store the blob while it is being + * constructed. + */ + byte_buffer buffer; + public: + /** + * The binary format does not support labels, so this method + * does nothing. + */ + virtual void write_label(string name) {} + /** + * Comments are ignored by the binary writer. + */ + virtual void write_comment(string name) {} + virtual void write_string(string name); + virtual void write_data(uint8_t v); + virtual void write_data(uint32_t v); + virtual void write_data(uint64_t v); + virtual void write_to_file(int fd); + virtual uint32_t size(); +}; +/** + * Assembly writer. This class is responsible for writing the output in an + * assembly format that is suitable for linking into a kernel, loader, and so + * on. + */ +class asm_writer : public output_writer +{ + /** + * The internal buffer for temporary values. Note that this actually + * contains ASCII text, but it is a byte buffer so that we can just + * copy strings across as-is. + */ + byte_buffer buffer; + /** + * The number of bytes written to the current line. This is used to + * allow line wrapping, where we aim to write four .byte directives to + * make the alignment clearer. + */ + int byte_count; + /** + * The current number of bytes written. This is the number in binary + * format, not the number of bytes in the buffer. + */ + uint32_t bytes_written; + + /** + * Writes a C string directly to the ouput as-is. This is mainly used + * for writing directives. + */ + void write_string(const char *c); + /** + * Writes the string, starting on a new line. + */ + void write_line(const char *c); + /** + * Writes a byte in binary format. This will emit a single .byte + * directive, with up to four per line. + */ + void write_byte(uint8_t b); + public: + asm_writer() : byte_count(0), bytes_written(0) {} + virtual void write_label(string name); + virtual void write_comment(string name); + virtual void write_string(string name); + virtual void write_data(uint8_t v); + virtual void write_data(uint32_t v); + virtual void write_data(uint64_t v); + virtual void write_to_file(int fd); + virtual uint32_t size(); +}; + +/** + * Class encapsulating the device tree blob header. This class stores all of + * the values found in the header and is responsible for writing them to the + * output. + */ +struct header +{ + /** + * Magic value, used to validate that this really is a device tree + * blob. Should always be set to 0xd00dfeed. + */ + uint32_t magic; + /** + * The total size of the blob, including header, reservations, strings + * table, and padding. + */ + uint32_t totalsize; + /** + * The offset from the start of the blob of the struct table (i.e. the + * part of the blob containing the entire device tree). + */ + uint32_t off_dt_struct; + /** + * The offset from the start of the blob of the strings table. + */ + uint32_t off_dt_strings; + /** + * The offset of the reservation map from the start of the blob. + */ + uint32_t off_mem_rsvmap; + /** + * The version of the blob. This should always be 17. + */ + uint32_t version; + /** + * The earliest version of the DTB specification with which this blob + * is backwards compatible. This should always be 16. + */ + uint32_t last_comp_version; + /** + * The ID of the CPU where this boots. + */ + uint32_t boot_cpuid_phys; + /** + * The size of the strings table. + */ + uint32_t size_dt_strings; + /** + * The size of the struct table. + */ + uint32_t size_dt_struct; + /** + * Writes the entire header to the specified output buffer. + */ + void write(output_writer &out); + /** + * Reads the header from bits binary representation in a blob. + */ + bool read_dtb(input_buffer &input); + /** + * Default constructor. Initialises the values that have sensible + * defaults, leaves the others blank. + */ + header() : magic(0xd00dfeed), version(17), last_comp_version(16), + boot_cpuid_phys(0) {} +}; + +/** + * Class encapsulating the string table. FDT strings are stored in a string + * section. This maintains a map from strings to their offsets in the strings + * section. + * + * Note: We don't currently do suffix matching, which may save a small amount + * of space. + */ +class string_table { + /** + * Map from strings to their offset. + */ + std::map<string, uint32_t> string_offsets; + /** + * The strings, in the order in which they should be written to the + * output. The order must be stable - adding another string must not + * change the offset of any that we have already referenced - and so we + * simply write the strings in the order that they are passed. + */ + std::vector<string> strings; + /** + * The current size of the strings section. + */ + uint32_t size; + public: + /** + * Default constructor, creates an empty strings table. + */ + string_table() : size(0) {} + /** + * Adds a string to the table, returning the offset from the start + * where it will be written. If the string is already present, this + * will return its existing offset, otherwise it will return a new + * offset. + */ + uint32_t add_string(string str); + /** + * Writes the strings table to the specified output. + */ + void write(dtb::output_writer &writer); +}; + +} // namespace dtb + +} // namespace dtc + +#endif // !_DTB_HH_ diff --git a/usr.bin/dtc/dtc.1 b/usr.bin/dtc/dtc.1 new file mode 100644 index 0000000..567aa03 --- /dev/null +++ b/usr.bin/dtc/dtc.1 @@ -0,0 +1,309 @@ +.\"- +.\" Copyright (c) 2013 David Chisnall +.\" All rights reserved. +.\" +.\" This software was developed by SRI International and the University of +.\" Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) +.\" ("CTSRD"), as part of the DARPA CRASH research programme. +.\" +.\" This software was developed by SRI International and the University of +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\"/ +.Dd January 1, 2013 +.Dt DTC 1 +.Os +.Sh NAME +.Nm dtc +.Nd device tree compiler +.Sh SYNOPSIS +.Nm +.Op Fl fhsv +.Op Fl b Ar boot_cpu_id +.Op Fl d Ar dependency_file +.Op Fl E Ar [no-]checker_name +.Op Fl H Ar phandle_format +.Op Fl I Ar input_format +.Op Fl O Ar output_format +.Op Fl o Ar output_file +.Op Fl R Ar entries +.Op Fl S Ar bytes +.Op Fl p Ar bytes +.Op Fl V Ar blob_version +.Op Fl W Ar [no-]checker_name +.Ar input_file +.Sh DESCRIPTION +The +.Nm +utility converts flattened device tree (FDT) representations. + It is most commonly used to generate device tree blobs (DTB), the binary +representation of an FDT, from device tree sources (DTS), the ASCII text source +representation. +.Pp +The binary can be written in two formats, binary and assembly. +The binary is identical to the in-memory representation and can be used +directly by firmware, loaders, and so on. +The assembly format, documented in +.Sx "ASM FORMAT" , +will produce the same binary format when assembled, but also includes some +global variables that refer to parts of the table. +This format is most commonly used to produce a kernel specific to a device, +with the device tree blob compiled in. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl d Ar dependency_file +Writes a dependency file understandable by make to the specified file. +This file can be included in a Makefile and will ensure that the output file +depends on the input file and any files that it includes. +This argument is only useful when the input is DTS, as only the source format +has a notion of inclusions. +.It Fl E Ar [no-]checker_name +Enable or disable a specified checker. +The argument is the name of the checker. +The full list of checkers is given in +.Sx CHECKERS . +.It Fl f +Force the tool to attempt to generate the output, even if the input had errors. +.It Fl h +Display the help text and exit. +.It Fl H Ar phandle_format +Specifies the type of phandle nodes to generate in the output. +Valid values are: +.Pp +.Bl -tag -width indent -compact +.It Ar linux +Generate the legacy linux,phandle nodes expected by older systems. +.It Ar epapr +Generate the phandle nodes, as described in the ePAPR specification. +This is the most sensible option for device trees being used with +.Fx . +.It Ar both +Generate both, for maximum compatibility. +.El +.It Fl I Ar input_format +Specifies the input format. +Valid values are: +.Pp +.Bl -tag -width indent -compact +.It Ar dtb +Device tree blob. +The binary representation of the FDT. +.It Ar dts +Device tree source. +The ASCII representation of the FDT. +This is the default if the input format is not explicitly stated. +.El +.It Fl O Ar output_format +Specifies the output format. +Valid values are: +.Pp +.Bl -tag -width indent -compact +.It Ar asm +Assembler source for generating a device tree blob, as described in +.Sx "ASM FORMAT" . +.It Ar dtb +Device tree blob. +The binary representation of the FDT. +This is the default if the output format is not explicitly stated. +.It Ar dts +Device tree source. +The ASCII representation of the FDT. +.El +.It Fl o Ar output_file +The file to which to write the output. +.It Fl R Ar entries +The number of empty reservation table entries to pad the table with. +This is +useful if you are generating a device tree blob for bootloader or similar that +needs to reserve some memory before passing control to the operating system. +.It Fl S Ar bytes +The minimum size in bytes of the blob. +The blob will be padded after the strings table to ensure that it is the +correct size. +This is useful for environments where the device tree blob must be modified in +place. +.It Fl p Ar bytes +The number of bytes of padding to add to the blob. +The blob will be padded after the strings table to ensure that it is the +correct size. +This is useful for environments where the device tree blob must be modified in +place. +.It Fl W Ar [no-]checker_name +Enable or disable a specified checker. +This is an alias for +.Fl E . +.It Fl s +Sorts the properties and nodes in the tree. +This is mainly useful when using tools like +.Xr diff 1 +to compare two device tree sources. +.It Fl V Ar output_version +The version of the format to output. +This is only relevant for binary outputs, and only a value of 17 is currently +supported. +.It Fl v +Display the tool version and exit. +.It Ar input_file +The source file. +.El +.Sh "ASM FORMAT" +The assembly format defines several globals that can be referred to from other +compilation units, in addition to any labels specified in the source. +These are: +.Pp +.Bl -tag -width "dt_strings_start" -compact -offset indent +.It dt_blob_start +start of the device tree blob. +.It dt_header +start of the header, usually identical to the start of the blob. +.It dt_reserve_map +start of the reservation map. +.It dt_struct_start +start of the structure table. +.It dt_struct_end +end of the structure table. +.It dt_strings_start +start of the strings table. +.It dt_strings_end +end of the strings table. +.It dt_blob_end +end of the device tree blob. +.El +.Sh CHECKERS +The utility provides a number of semantic checks on the correctness of the +tree. +These can be disabled with the +.Fl W +flag. +For example, +.Fl W Ar no-type-phandle +will disable the phandle type check. +The supported checks are: +.Pp +.Bl -tag -width "no-type-phandle" -compact -offset indent +.It type-compatible +Checks the type of the +.Va compatible +property. +.It type-model +Checks the type of the +.Va model +property. +.It type-compatible +Checks the type of the +.Va compatible +property. +.El +.Sh EXAMPLES +The command: +.Pp +.Dl "dtc -o blob.S -O asm device.dts" +.Pp +will generate a +.Pa blob.S +file from the device tree source +.Pa device.dts +and print errors if any occur during parsing or property checking. +The +resulting file can be assembled and linked into a binary. +.Pp +The command: +.Pp +.Dl "dtc -o - -O dts -I dtb device.dtb" +.Pp +will write the device tree source for the device tree blob +.Pa device.dtb +to the standard output. +This is useful when debugging device trees. +.Sh COMPATIBILITY +This utility is intended to be compatible with the device tree compiler +provided by elinux.org. +Currently, it implements the subset of features +required to build FreeBSD and others that have been requested by FreeBSD +developers. +.Pp +The +.Ar fs +input format is not supported. +This builds a tree from a Linux +.Pa /proc/device-tree , +a file system hierarchy not found in FreeBSD, which instead exposes the DTB +directly via a sysctl. +.Pp +The warnings and errors supported by the elinux.org tool are not documented. +This tool supports the warnings described in the +.Sx CHECKERS +section. +.Sh SEE ALSO +.Xr fdt 4 +.Sh STANDARDS +The device tree formats understood by this tool conform to the Power.org +Standard for Embedded Power Architecture Platform Requirements +.Pq Vt ePAPR , +except as noted in the +.Sx BUGS +section and with the following exceptions for compatibility with the elinux.org +tool: +.Pp +.Bl -bullet -compact +.It +The target of cross references is defined to be a node name in the +specification, but is in fact a label. +.El +.Pp +The /include/ directive is not part of the standard, however it is implemented +with the semantics compatible with the elinux.org tool. +It must appear in the top level of a file, and imports a new root definition. +If a file, plus all of its inclusions, contains multiple roots then they are +merged. +All nodes that are present in the second but not the first are imported. +Any that appear in both are recursively merged, with properties from the second +replacing those from the first and properties child nodes being recursively +merged. +.Sh HISTORY +A dtc tool first appeared in +.Fx 9.0 . +This version of the tool first appeared in +.Fx 10.0 . +.Sh AUTHORS +.An David T. Chisnall +.Pp +Note: The fact that the tool and the author share the same initials is entirely +coincidental. +.Sh BUGS +The device tree compiler does not yet support the following features: +.Pp +.Bl -bullet -compact +.It +Labels in the middle of property values. +This is only useful in the assembly output, and only vaguely useful there, so +is unlikely to be added soon. +.It +Full paths, rather than labels, as the targets for phandles. +This is not very hard to add, but will probably not be added until something +actually needs it. +.El +.Pp +The current version performs a very limited set of semantic checks on the tree. +This will be improved in future versions. diff --git a/usr.bin/dtc/dtc.cc b/usr.bin/dtc/dtc.cc new file mode 100644 index 0000000..cffb3eb --- /dev/null +++ b/usr.bin/dtc/dtc.cc @@ -0,0 +1,337 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/resource.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + + +#include "fdt.hh" +#include "checking.hh" + +using namespace dtc; + +/** + * The current major version of the tool. + */ +int version_major = 0; +/** + * The current minor version of the tool. + */ +int version_minor = 4; +/** + * The current patch level of the tool. + */ +int version_patch = 0; + +static void usage(const char* argv0) +{ + fprintf(stderr, "Usage:\n" + "\t%s\t[-fhsv] [-b boot_cpu_id] [-d dependency_file]" + "[-E [no-]checker_name]\n" + "\t\t[-H phandle_format] [-I input_format]" + "[-O output_format]\n" + "\t\t[-o output_file] [-R entries] [-S bytes] [-p bytes]" + "[-V blob_version]\n" + "\t\t-W [no-]checker_name] input_file\n", basename(argv0)); +} + +/** + * Prints the current version of this program.. + */ +static void version(const char* progname) +{ + fprintf(stderr, "Version: %s %d.%d.%d\n", progname, version_major, + version_minor, version_patch); +} + +using fdt::device_tree; + +int +main(int argc, char **argv) +{ + int ch; + int outfile = fileno(stdout); + const char *outfile_name = "-"; + const char *in_file = "-"; + FILE *depfile = 0; + bool debug_mode = false; + void (device_tree::*write_fn)(int) = &device_tree::write_binary; + void (device_tree::*read_fn)(const char*, FILE*) = + &device_tree::parse_dts; + uint32_t boot_cpu; + bool boot_cpu_specified = false; + bool keep_going = false; + bool sort = false; + clock_t c0 = clock(); + class device_tree tree; + fdt::checking::check_manager checks; + const char *options = "hqI:O:o:V:d:R:S:p:b:fisvH:W:E:D"; + + // Don't forget to update the man page if any more options are added. + while ((ch = getopt(argc, argv, options)) != -1) + { + switch (ch) + { + case 'h': + usage(argv[0]); + return EXIT_SUCCESS; + case 'v': + version(argv[0]); + return EXIT_SUCCESS; + case 'I': + { + string arg = string(optarg); + if (arg == string("dtb")) + { + read_fn = &device_tree::parse_dtb; + } + else if (arg == string("dts")) + { + read_fn = &device_tree::parse_dts; + } + else + { + fprintf(stderr, "Unknown input format: %s\n", optarg); + return EXIT_FAILURE; + } + break; + } + case 'O': + { + string arg = string(optarg); + if (arg == string("dtb")) + { + write_fn = &device_tree::write_binary; + } + else if (arg == string("asm")) + { + write_fn = &device_tree::write_asm; + } + else if (arg == string("dts")) + { + write_fn = &device_tree::write_dts; + } + else + { + fprintf(stderr, "Unknown output format: %s\n", optarg); + return EXIT_FAILURE; + } + break; + } + case 'o': + { + outfile_name = optarg; + outfile = open(optarg, O_CREAT | O_TRUNC | O_WRONLY, 0666); + if (outfile == -1) + { + perror("Unable to open output file"); + return EXIT_FAILURE; + } + break; + } + case 'D': + debug_mode = true; + break; + case 'V': + if (string(optarg) != string("17")) + { + fprintf(stderr, "Unknown output format version: %s\n", optarg); + return EXIT_FAILURE; + } + break; + case 'd': + { + if (depfile != 0) + { + fclose(depfile); + } + if (string(optarg) == string("-")) + { + depfile = stdout; + } + else + { + depfile = fdopen(open(optarg, O_CREAT | O_TRUNC | O_WRONLY, 0666), "w"); + if (depfile == 0) + { + perror("Unable to open dependency file"); + return EXIT_FAILURE; + } + } + break; + } + case 'H': + { + string arg = string(optarg); + if (arg == string("both")) + { + tree.set_phandle_format(device_tree::BOTH); + } + else if (arg == string("epapr")) + { + tree.set_phandle_format(device_tree::EPAPR); + } + else if (arg == string("linux")) + { + tree.set_phandle_format(device_tree::LINUX); + } + else + { + fprintf(stderr, "Unknown phandle format: %s\n", optarg); + return EXIT_FAILURE; + } + break; + } + case 'b': + // Don't bother to check if strtoll fails, just + // use the 0 it returns. + boot_cpu = (uint32_t)strtoll(optarg, 0, 10); + boot_cpu_specified = true; + break; + case 'f': + keep_going = true; + break; + case 'W': + case 'E': + { + string arg = string(optarg); + if ((arg.size() > 3) && (strncmp(optarg, "no-", 3) == 0)) + { + arg = string(optarg+3); + if (!checks.disable_checker(arg)) + { + fprintf(stderr, "Checker %s either does not exist or is already disabled\n", optarg+3); + } + break; + } + if (!checks.enable_checker(arg)) + { + fprintf(stderr, "Checker %s either does not exist or is already enabled\n", optarg); + } + break; + } + case 's': + { + sort = true; + break; + } + case 'i': + { + tree.add_include_path(optarg); + break; + } + // Should quiet warnings, but for now is silently ignored. + case 'q': + break; + case 'R': + tree.set_empty_reserve_map_entries(strtoll(optarg, 0, 10)); + break; + case 'S': + tree.set_blob_minimum_size(strtoll(optarg, 0, 10)); + break; + case 'p': + tree.set_blob_padding(strtoll(optarg, 0, 10)); + break; + default: + fprintf(stderr, "Unknown option %c\n", ch); + return EXIT_FAILURE; + } + } + if (optind < argc) + { + in_file = argv[optind]; + } + if (depfile != 0) + { + fputs(outfile_name, depfile); + fputs(": ", depfile); + fputs(in_file, depfile); + } + clock_t c1 = clock(); + (tree.*read_fn)(in_file, depfile); + // Override the boot CPU found in the header, if we're loading from dtb + if (boot_cpu_specified) + { + tree.set_boot_cpu(boot_cpu); + } + if (sort) + { + tree.sort(); + } + if (depfile != 0) + { + putc('\n', depfile); + fclose(depfile); + } + if (!(tree.is_valid() || keep_going)) + { + fprintf(stderr, "Failed to parse tree. Unhappy face!\n"); + return EXIT_FAILURE; + } + clock_t c2 = clock(); + if (!(checks.run_checks(&tree, true) || keep_going)) + { + return EXIT_FAILURE; + } + clock_t c3 = clock(); + (tree.*write_fn)(outfile); + close(outfile); + clock_t c4 = clock(); + + if (debug_mode) + { + struct rusage r; + + getrusage(RUSAGE_SELF, &r); + fprintf(stderr, "Peak memory usage: %ld bytes\n", r.ru_maxrss); + fprintf(stderr, "Setup and option parsing took %f seconds\n", + ((double)(c1-c0))/CLOCKS_PER_SEC); + fprintf(stderr, "Parsing took %f seconds\n", + ((double)(c2-c1))/CLOCKS_PER_SEC); + fprintf(stderr, "Checking took %f seconds\n", + ((double)(c3-c2))/CLOCKS_PER_SEC); + fprintf(stderr, "Generating output took %f seconds\n", + ((double)(c4-c3))/CLOCKS_PER_SEC); + fprintf(stderr, "Total time: %f seconds\n", + ((double)(c4-c0))/CLOCKS_PER_SEC); + // This is not needed, but keeps valgrind quiet. + fclose(stdin); + } + return EXIT_SUCCESS; +} + diff --git a/usr.bin/dtc/fdt.cc b/usr.bin/dtc/fdt.cc new file mode 100644 index 0000000..b6e7d56 --- /dev/null +++ b/usr.bin/dtc/fdt.cc @@ -0,0 +1,1361 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "fdt.hh" + +#include <algorithm> +#include <ctype.h> +#include <fcntl.h> +#include <inttypes.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include "dtb.hh" + +namespace dtc +{ + +namespace fdt +{ + +uint32_t +property_value::get_as_uint32() +{ + if (byte_data.size() != 4) + { + return 0; + } + uint32_t v = 0; + v &= byte_data[0] << 24; + v &= byte_data[1] << 16; + v &= byte_data[2] << 8; + v &= byte_data[3] << 0; + return v; +} + +void +property_value::push_to_buffer(byte_buffer &buffer) +{ + if (!byte_data.empty()) + { + buffer.insert(buffer.end(), byte_data.begin(), byte_data.end()); + } + else + { + string_data.push_to_buffer(buffer, true); + // Trailing nul + buffer.push_back(0); + } +} + +void +property_value::write_dts(FILE *file) +{ + resolve_type(); + switch (type) + { + default: + assert(0 && "Invalid type"); + case STRING: + case STRING_LIST: + case CROSS_REFERENCE: + write_as_string(file); + break; + case PHANDLE: + write_as_cells(file); + break; + case BINARY: + if (byte_data.size() % 4 == 0) + { + write_as_cells(file); + break; + } + write_as_bytes(file); + break; + } +} + +void +property_value::resolve_type() +{ + if (type != UNKNOWN) + { + return; + } + if (byte_data.empty()) + { + type = STRING; + return; + } + if (byte_data.back() == 0) + { + bool is_all_printable = true; + int nuls = 0; + int bytes = 0; + for (byte_buffer::iterator i=byte_data.begin(), e=byte_data.end()-1; i<e ; i++) + { + bytes++; + is_all_printable &= (*i == '\0') || isprint(*i); + if (*i == '\0') + { + nuls++; + } + if (!is_all_printable) + { + break; + } + } + if (is_all_printable && (bytes > nuls)) + { + type = STRING; + if (nuls > 0) + { + type = STRING_LIST; + } + return; + } + } + type = BINARY; +} + +void +property_value::write_as_string(FILE *file) +{ + putc('"', file); + if (byte_data.empty()) + { + string_data.print(file); + } + else + { + for (byte_buffer::iterator i=byte_data.begin(), e=byte_data.end()-1; i!=e ; ++i) + { + // FIXME Escape tabs, newlines, and so on. + if (*i == '\0') + { + fputs("\", \"", file); + continue; + } + putc(*i, file); + } + } + putc('"', file); +} + +void +property_value::write_as_cells(FILE *file) +{ + putc('<', file); + assert((byte_data.size() % 4) == 0); + for (byte_buffer::iterator i=byte_data.begin(), e=byte_data.end(); i!=e ; ++i) + { + uint32_t v = 0; + v = (v << 8) | *i; + ++i; + v = (v << 8) | *i; + ++i; + v = (v << 8) | *i; + ++i; + v = (v << 8) | *i; + fprintf(file, "0x%" PRIx32, v); + if (i+1 != e) + { + putc(' ', file); + } + } + putc('>', file); +} + +void +property_value::write_as_bytes(FILE *file) +{ + putc('[', file); + for (byte_buffer::iterator i=byte_data.begin(), e=byte_data.end(); i!=e ; i++) + { + fprintf(file, "%hhx", *i); + if (i+1 != e) + { + putc(' ', file); + } + } + putc(']', file); +} + +void +property::parse_string(input_buffer &input) +{ + property_value v; + assert(input[0] == '"'); + ++input; + const char *start = (const char*)input; + int length = 0; + while (char c = input[0]) + { + if (c == '"' && input[-1] != '\\') + { + input.consume('"'); + break; + } + ++input; + ++length; + } + v.string_data = string(start, length); + values.push_back(v); +} + +void +property::parse_cells(input_buffer &input) +{ + assert(input[0] == '<'); + ++input; + property_value v; + input.next_token(); + while (!input.consume('>')) + { + input.next_token(); + // If this is a phandle then we need to get the name of the + // referenced node + if (input.consume('&')) + { + input.next_token(); + // FIXME: We should support full paths here, but we + // don't. + string referenced = string::parse_node_name(input); + if (referenced.empty()) + { + input.parse_error("Expected node name"); + valid = false; + return; + } + input.next_token(); + // If we already have some bytes, make the phandle a + // separate component. + if (!v.byte_data.empty()) + { + values.push_back(v); + v = property_value(); + } + v.string_data = referenced; + v.type = property_value::PHANDLE; + values.push_back(v); + v = property_value(); + } + else + { + //FIXME: We should support labels in the middle + //of these, but we don't. + long long val; + if (!input.consume_integer(val)) + { + input.parse_error("Expected numbers in array of cells"); + valid = false; + return; + } + push_big_endian(v.byte_data, (uint32_t)val); + input.next_token(); + } + } + // Don't store an empty string value here. + if (v.byte_data.size() > 0) + { + values.push_back(v); + } +} + +void +property::parse_bytes(input_buffer &input) +{ + assert(input[0] == '['); + ++input; + property_value v; + input.next_token(); + while (!input.consume(']')) + { + { + //FIXME: We should support + //labels in the middle of + //these, but we don't. + uint8_t val; + if (!input.consume_hex_byte(val)) + { + input.parse_error("Expected hex bytes in array of bytes"); + valid = false; + return; + } + v.byte_data.push_back(val); + input.next_token(); + } + } + values.push_back(v); +} + +void +property::parse_reference(input_buffer &input) +{ + assert(input[0] == '&'); + ++input; + input.next_token(); + property_value v; + v.string_data = string::parse_node_name(input); + if (v.string_data.empty()) + { + input.parse_error("Expected node name"); + valid = false; + return; + } + v.type = property_value::CROSS_REFERENCE; + values.push_back(v); +} + +property::property(input_buffer &structs, input_buffer &strings) +{ + uint32_t name_offset; + uint32_t length; + valid = structs.consume_binary(length) && + structs.consume_binary(name_offset); + if (!valid) + { + fprintf(stderr, "Failed to read property\n"); + return; + } + // Find the name + input_buffer name_buffer = strings.buffer_from_offset(name_offset); + if (name_buffer.empty()) + { + fprintf(stderr, "Property name offset %" PRIu32 + " is past the end of the strings table\n", + name_offset); + valid = false; + return; + } + key = string(name_buffer); + // Read the value + uint8_t byte; + property_value v; + for (uint32_t i=0 ; i<length ; i++) + { + if (!(valid = structs.consume_binary(byte))) + { + fprintf(stderr, "Failed to read property value\n"); + return; + } + v.byte_data.push_back(byte); + } + values.push_back(v); +} + +property::property(input_buffer &input, string k, string l) : key(k), label(l), + valid(true) +{ + do { + input.next_token(); + switch (input[0]) + { + default: + input.parse_error("Invalid property value."); + valid = false; + return; + case '"': + parse_string(input); + break; + case '<': + parse_cells(input); + break; + case '[': + parse_bytes(input); + break; + case '&': + parse_reference(input); + break; + case ';': + { + break; + } + } + input.next_token(); + } while (input.consume(',')); + if (!input.consume(';')) + { + input.parse_error("Expected ; at end of property"); + valid = false; + } +} + +property* +property::parse_dtb(input_buffer &structs, input_buffer &strings) +{ + property *p = new property(structs, strings); + if (!p->valid) + { + delete p; + p = 0; + } + return p; +} + +property* +property::parse(input_buffer &input, string key, string label) +{ + property *p = new property(input, key, label); + if (!p->valid) + { + delete p; + p = 0; + } + return p; +} + +void +property::write(dtb::output_writer &writer, dtb::string_table &strings) +{ + writer.write_token(dtb::FDT_PROP); + byte_buffer value_buffer; + for (value_iterator i=begin(), e=end() ; i!=e ; ++i) + { + i->push_to_buffer(value_buffer); + } + writer.write_data((uint32_t)value_buffer.size()); + writer.write_comment(key); + writer.write_data(strings.add_string(key)); + writer.write_data(value_buffer); +} + +void +property::write_dts(FILE *file, int indent) +{ + for (int i=0 ; i<indent ; i++) + { + putc('\t', file); + } + if (label != string()) + { + label.print(file); + fputs(": ", file); + } + if (key != string()) + { + key.print(file); + } + if (!values.empty()) + { + fputs(" = ", file); + for (value_iterator i=begin(), e=end() ; i!=e ; ++i) + { + i->write_dts(file); + if (i+1 != e) + { + putc(',', file); + putc(' ', file); + } + } + } + fputs(";\n", file); +} + +string +node::parse_name(input_buffer &input, bool &is_property, const char *error) +{ + if (!valid) + { + return string(); + } + input.next_token(); + if (is_property) + { + return string::parse_property_name(input); + } + string n = string::parse_node_or_property_name(input, is_property); + if (n.empty()) + { + if (n.empty()) + { + input.parse_error(error); + valid = false; + } + } + return n; +} + +node::node(input_buffer &structs, input_buffer &strings) : valid(true) +{ + const char *name_start = (const char*)structs; + int name_length = 0; + while (structs[0] != '\0' && structs[0] != '@') + { + name_length++; + ++structs; + } + name = string(name_start, name_length); + if (structs[0] == '@') + { + ++structs; + name_start = (const char*)structs; + name_length = 0; + while (structs[0] != '\0') + { + name_length++; + ++structs; + } + unit_address = string(name_start, name_length); + } + ++structs; + uint32_t token; + while (structs.consume_binary(token)) + { + switch (token) + { + default: + fprintf(stderr, "Unexpected token 0x%" PRIx32 + " while parsing node.\n", token); + valid = false; + return; + // Child node, parse it. + case dtb::FDT_BEGIN_NODE: + { + node *child = node::parse_dtb(structs, strings); + if (child == 0) + { + valid = false; + return; + } + children.push_back(child); + break; + } + // End of this node, no errors. + case dtb::FDT_END_NODE: + return; + // Property, parse it. + case dtb::FDT_PROP: + { + property *prop = property::parse_dtb(structs, strings); + if (prop == 0) + { + valid = false; + return; + } + properties.push_back(prop); + break; + } + break; + // End of structs table. Should appear after + // the end of the last node. + case dtb::FDT_END: + fprintf(stderr, "Unexpected FDT_END token while parsing node.\n"); + valid = false; + return; + // NOPs are padding. Ignore them. + case dtb::FDT_NOP: + break; + } + } + fprintf(stderr, "Failed to read token from structs table while parsing node.\n"); + valid = false; + return; +} + +node::node(input_buffer &input, string n, string l, string a) : + label(l), name(n), unit_address(a), valid(true) +{ + if (!input.consume('{')) + { + input.parse_error("Expected { to start new device tree node.\n"); + } + input.next_token(); + while (valid && !input.consume('}')) + { + // flag set if we find any characters that are only in + // the property name character set, not the node + bool is_property = false; + string child_name, child_label, child_address; + child_name = parse_name(input, is_property, + "Expected property or node name"); + if (input.consume(':')) + { + // Node labels can contain any characters? The + // spec doesn't say, so we guess so... + is_property = false; + child_label = child_name; + child_name = parse_name(input, is_property, "Expected property or node name"); + } + if (input.consume('@')) + { + child_address = parse_name(input, is_property, "Expected unit address"); + } + if (!valid) + { + return; + } + input.next_token(); + // If we're parsing a property, then we must actually do that. + if (input.consume('=')) + { + property *p= property::parse(input, child_name, + child_label); + if (p == 0) + { + valid = false; + } + else + { + properties.push_back(p); + } + } + else if (!is_property && input[0] == ('{')) + { + node *child = node::parse(input, child_name, + child_label, child_address); + if (child) + { + children.push_back(child); + } + else + { + valid = false; + } + } + else if (input.consume(';')) + { + properties.push_back(new property(child_name, child_label)); + } + else + { + input.parse_error("Error parsing property."); + valid = false; + } + input.next_token(); + } + input.consume(';'); +} + +bool +node::cmp_properties(property *p1, property *p2) +{ + return p1->get_key() < p2->get_key(); +} + +bool +node::cmp_children(node *c1, node *c2) +{ + if (c1->name == c2->name) + { + return c1->unit_address < c2->unit_address; + } + return c1->name < c2->name; +} + +void +node::sort() +{ + std::sort(property_begin(), property_end(), cmp_properties); + std::sort(child_begin(), child_end(), cmp_children); + for (child_iterator i=child_begin(), e=child_end() ; i!=e ; ++i) + { + (*i)->sort(); + } +} + +node* +node::parse(input_buffer &input, string name, string label, string address) +{ + node *n = new node(input, name, label, address); + if (!n->valid) + { + delete n; + n = 0; + } + return n; +} + +node* +node::parse_dtb(input_buffer &structs, input_buffer &strings) +{ + node *n = new node(structs, strings); + if (!n->valid) + { + delete n; + n = 0; + } + return n; +} + +node::~node() +{ + while (!children.empty()) + { + delete children.back(); + children.pop_back(); + } + while (!properties.empty()) + { + delete properties.back(); + properties.pop_back(); + } +} + +property* +node::get_property(string key) +{ + for (property_iterator i=property_begin(), e=property_end() ; i!=e ; ++i) + { + if ((*i)->get_key() == key) + { + return *i; + } + } + return 0; +} + +void +node::merge_node(node *other) +{ + if (!other->label.empty()) + { + label = other->label; + } + // Note: this is an O(n*m) operation. It might be sensible to + // optimise this if we find that there are nodes with very + // large numbers of properties, but for typical usage the + // entire vector will fit (easily) into cache, so iterating + // over it repeatedly isn't that expensive. + while (!other->properties.empty()) + { + property *p = other->properties.front(); + for (property_iterator i=property_begin(), e=property_end() ; i!=e ; ++i) + { + if ((*i)->get_key() == p->get_key()) + { + delete *i; + properties.erase(i); + break; + } + } + add_property(p); + other->properties.erase(other->properties.begin()); + } + while (!other->children.empty()) + { + node *c = other->children.front(); + bool found = false; + for (child_iterator i=child_begin(), e=child_end() ; i!=e ; ++i) + { + if ((*i)->name == c->name && (*i)->unit_address == c->unit_address) + { + (*i)->merge_node(c); + delete c; + found = true; + break; + } + } + if (!found) + { + children.push_back(c); + } + other->children.erase(other->children.begin()); + } +} + +void +node::write(dtb::output_writer &writer, dtb::string_table &strings) +{ + writer.write_token(dtb::FDT_BEGIN_NODE); + byte_buffer name_buffer; + name.push_to_buffer(name_buffer); + if (unit_address != string()) + { + name_buffer.push_back('@'); + unit_address.push_to_buffer(name_buffer); + } + writer.write_comment(name); + writer.write_data(name_buffer); + writer.write_data((uint8_t)0); + for (property_iterator i=property_begin(), e=property_end() ; i!=e ; ++i) + { + (*i)->write(writer, strings); + } + for (child_iterator i=child_begin(), e=child_end() ; i!=e ; ++i) + { + (*i)->write(writer, strings); + } + writer.write_token(dtb::FDT_END_NODE); +} + +void +node::write_dts(FILE *file, int indent) +{ + for (int i=0 ; i<indent ; i++) + { + putc('\t', file); + } + if (label != string()) + { + label.print(file); + fputs(": ", file); + } + if (name != string()) + { + name.print(file); + } + if (unit_address != string()) + { + putc('@', file); + unit_address.print(file); + } + fputs(" {\n\n", file); + for (property_iterator i=property_begin(), e=property_end() ; i!=e ; ++i) + { + (*i)->write_dts(file, indent+1); + } + for (child_iterator i=child_begin(), e=child_end() ; i!=e ; ++i) + { + (*i)->write_dts(file, indent+1); + } + for (int i=0 ; i<indent ; i++) + { + putc('\t', file); + } + fputs("};\n", file); +} + +void +device_tree::collect_names_recursive(node* n, node_path &path) +{ + string name = n->label; + path.push_back(std::make_pair(n->name, n->unit_address)); + if (name != string()) + { + if (node_names.find(name) == node_names.end()) + { + node_names.insert(std::make_pair(name, n)); + node_paths.insert(std::make_pair(name, path)); + } + else + { + node_names[name] = (node*)-1; + std::map<string, node_path>::iterator i = node_paths.find(name); + if (i != node_paths.end()) + { + node_paths.erase(name); + } + fprintf(stderr, "Label not unique: "); + name.dump(); + fprintf(stderr, ". References to this label will not be resolved."); + } + } + for (node::child_iterator i=n->child_begin(), e=n->child_end() ; i!=e ; ++i) + { + collect_names_recursive(*i, path); + } + path.pop_back(); + // Now we collect the phandles and properties that reference + // other nodes. + for (node::property_iterator i=n->property_begin(), e=n->property_end() ; i!=e ; ++i) + { + for (property::value_iterator p=(*i)->begin(),pe=(*i)->end() ; p!=pe ; ++p) + { + if (p->is_phandle()) + { + phandles.push_back(&*p); + } + if (p->is_cross_reference()) + { + cross_references.push_back(&*p); + } + } + if ((*i)->get_key() == string("phandle") || + (*i)->get_key() == string("linux,phandle")) + { + if ((*i)->begin()->byte_data.size() != 4) + { + fprintf(stderr, "Invalid phandle value for node "); + n->name.dump(); + fprintf(stderr, ". Should be a 4-byte value.\n"); + valid = false; + } + else + { + uint32_t phandle = (*i)->begin()->get_as_uint32(); + used_phandles.insert(std::make_pair(phandle, n)); + } + } + } +} + +void +device_tree::collect_names() +{ + node_path p; + collect_names_recursive(root, p); +} + +void +device_tree::resolve_cross_references() +{ + for (std::vector<property_value*>::iterator i=cross_references.begin(), e=cross_references.end() ; i!=e ; ++i) + { + property_value* pv = *i; + node_path path = node_paths[pv->string_data]; + // Skip the first name in the path. It's always "", and implicitly / + for (node_path::iterator p=path.begin()+1, pe=path.end() ; p!=pe ; ++p) + { + pv->byte_data.push_back('/'); + p->first.push_to_buffer(pv->byte_data); + if (!(p->second.empty())) + { + pv->byte_data.push_back('@'); + p->second.push_to_buffer(pv->byte_data); + } + } + pv->byte_data.push_back(0); + } + uint32_t phandle = 1; + for (std::vector<property_value*>::iterator i=phandles.begin(), e=phandles.end() ; i!=e ; ++i) + { + string target_name = (*i)->string_data; + node *target = node_names[target_name]; + if (target == 0) + { + fprintf(stderr, "Failed to find node with label:"); + target_name.dump(); + fprintf(stderr, "\n"); + valid = 0; + return; + } + // If there is an existing phandle, use it + property *p = target->get_property("phandle"); + if (p == 0) + { + p = target->get_property("linux,phandle"); + } + if (p == 0) + { + // Otherwise insert a new phandle node + property_value v; + while (used_phandles.find(phandle) != used_phandles.end()) + { + // Note that we only don't need to + // store this phandle in the set, + // because we are monotonically + // increasing the value of phandle and + // so will only ever revisit this value + // if we have used 2^32 phandles, at + // which point our blob won't fit in + // any 32-bit system and we've done + // something badly wrong elsewhere + // already. + phandle++; + } + push_big_endian(v.byte_data, phandle++); + if (phandle_node_name == BOTH || phandle_node_name == LINUX) + { + p = new property(string("linux,phandle")); + p->add_value(v); + target->add_property(p); + } + if (phandle_node_name == BOTH || phandle_node_name == EPAPR) + { + p = new property(string("phandle")); + p->add_value(v); + target->add_property(p); + } + } + p->begin()->push_to_buffer((*i)->byte_data); + assert((*i)->byte_data.size() == 4); + } +} + +void +device_tree::parse_roots(input_buffer &input, std::vector<node*> &roots) +{ + input.next_token(); + while (valid && input.consume('/')) + { + input.next_token(); + node *n = node::parse(input, string("", 1)); + if (n) + { + roots.push_back(n); + } + else + { + valid = false; + } + } +} + +input_buffer* +device_tree::buffer_for_file(const char *path) +{ + if (string(path) == string("-")) + { + input_buffer *b = new stream_input_buffer(); + buffers.push_back(b); + return b; + } + int source = open(path, O_RDONLY); + if (source == -1) + { + fprintf(stderr, "Unable to open file %s\n", path); + return 0; + } + input_buffer *b = new mmap_input_buffer(source); + // Keep the buffer that owns the memory around for the lifetime + // of this FDT. Ones simply referring to it may have shorter + // lifetimes. + buffers.push_back(b); + close(source); + return b; +} + +template<class writer> void +device_tree::write(int fd) +{ + dtb::string_table st; + dtb::header head; + writer head_writer; + writer reservation_writer; + writer struct_writer; + writer strings_writer; + + // Build the reservation table + reservation_writer.write_comment(string("Memory reservations")); + reservation_writer.write_label(string("dt_reserve_map")); + for (std::vector<reservation>::iterator i=reservations.begin(), + e=reservations.end() ; i!=e ; ++i) + { + reservation_writer.write_comment(string("Reservation start")); + reservation_writer.write_data(i->first); + reservation_writer.write_comment(string("Reservation length")); + reservation_writer.write_data(i->first); + } + // Write n spare reserve map entries, plus the trailing 0. + for (uint32_t i=0 ; i<=spare_reserve_map_entries ; i++) + { + reservation_writer.write_data((uint64_t)0); + reservation_writer.write_data((uint64_t)0); + } + + + struct_writer.write_comment(string("Device tree")); + struct_writer.write_label(string("dt_struct_start")); + root->write(struct_writer, st); + struct_writer.write_token(dtb::FDT_END); + struct_writer.write_label(string("dt_struct_end")); + + st.write(strings_writer); + // Find the strings size before we stick padding on the end. + // Note: We should possibly use a new writer for the padding. + head.size_dt_strings = strings_writer.size(); + + // Stick the padding in the strings writer, but after the + // marker indicating that it's the end. + // Note: We probably should add a padding call to the writer so + // that the asm back end can write padding directives instead + // of a load of 0 bytes. + for (uint32_t i=0 ; i<blob_padding ; i++) + { + strings_writer.write_data((uint8_t)0); + } + head.totalsize = sizeof(head) + strings_writer.size() + + struct_writer.size() + reservation_writer.size(); + while (head.totalsize < minimum_blob_size) + { + head.totalsize++; + strings_writer.write_data((uint8_t)0); + } + head.off_dt_struct = sizeof(head) + reservation_writer.size();; + head.off_dt_strings = head.off_dt_struct + struct_writer.size(); + head.off_mem_rsvmap = sizeof(head); + head.boot_cpuid_phys = boot_cpu; + head.size_dt_struct = struct_writer.size(); + head.write(head_writer); + + head_writer.write_to_file(fd); + reservation_writer.write_to_file(fd); + struct_writer.write_to_file(fd); + strings_writer.write_label(string("dt_blob_end")); + strings_writer.write_to_file(fd); +} + +node* +device_tree::referenced_node(property_value &v) +{ + if (v.is_phandle()) + { + return node_names[v.string_data]; + } + if (v.is_binary()) + { + return used_phandles[v.get_as_uint32()]; + } + return 0; +} + +void +device_tree::write_binary(int fd) +{ + write<dtb::binary_writer>(fd); +} + +void +device_tree::write_asm(int fd) +{ + write<dtb::asm_writer>(fd); +} + +void +device_tree::write_dts(int fd) +{ + FILE *file = fdopen(fd, "w"); + fputs("/dtc-v1/;\n\n", file); + + if (!reservations.empty()) + { + const char msg[] = "/memreserve/"; + fwrite(msg, sizeof(msg), 1, file); + for (std::vector<reservation>::iterator i=reservations.begin(), + e=reservations.end() ; i!=e ; ++i) + { + fprintf(stderr, " %" PRIx64 " %" PRIx64, i->first, i->second); + } + fputs(";\n\n", file); + } + putc('/', file); + putc(' ', file); + root->write_dts(file, 0); + fclose(file); +} + +void +device_tree::parse_dtb(const char *fn, FILE *depfile) +{ + input_buffer *in = buffer_for_file(fn); + if (in == 0) + { + valid = false; + return; + } + input_buffer &input = *in; + dtb::header h; + valid = h.read_dtb(input); + boot_cpu = h.boot_cpuid_phys; + if (h.last_comp_version > 17) + { + fprintf(stderr, "Don't know how to read this version of the device tree blob"); + valid = false; + } + if (!valid) + { + return; + } + input_buffer reservation_map = + input.buffer_from_offset(h.off_mem_rsvmap, 0); + uint64_t start, length; + do + { + if (!(reservation_map.consume_binary(start) && + reservation_map.consume_binary(length))) + { + fprintf(stderr, "Failed to read memory reservation table\n"); + valid = false; + return; + } + } while (!((start == 0) && (length == 0))); + input_buffer struct_table = + input.buffer_from_offset(h.off_dt_struct, h.size_dt_struct); + input_buffer strings_table = + input.buffer_from_offset(h.off_dt_strings, h.size_dt_strings); + uint32_t token; + if (!(struct_table.consume_binary(token) && + (token == dtb::FDT_BEGIN_NODE))) + { + fprintf(stderr, "Expected FDT_BEGIN_NODE token.\n"); + valid = false; + return; + } + root = node::parse_dtb(struct_table, strings_table); + if (!(struct_table.consume_binary(token) && (token == dtb::FDT_END))) + { + fprintf(stderr, "Expected FDT_END token after parsing root node.\n"); + valid = false; + return; + } + valid = (root != 0); +} + +void +device_tree::parse_dts(const char *fn, FILE *depfile) +{ + input_buffer *in = buffer_for_file(fn); + if (in == 0) + { + valid = false; + return; + } + std::vector<node*> roots; + input_buffer &input = *in; + input.next_token(); + bool read_header = false; + // Read the header + if (input.consume("/dts-v1/;")) + { + read_header = true; + } + input.next_token(); + while(input.consume("/include/")) + { + input.next_token(); + if (!input.consume('"')) + { + input.parse_error("Expected quoted filename"); + valid = false; + return; + } + int length = 0; + while (input[length] != '"') length++; + + const char *file = (const char*)input; + const char *dir = dirname(fn); + int dir_length = strlen(dir); + char *include_file = (char*)malloc(strlen(dir) + length + 2); + memcpy(include_file, dir, dir_length); + include_file[dir_length] = '/'; + memcpy(include_file+dir_length+1, file, length); + include_file[dir_length+length+1] = 0; + input_buffer *include_buffer = buffer_for_file(include_file); + + if (include_buffer == 0) + { + for (std::vector<const char*>::iterator i=include_paths.begin(), e=include_paths.end() ; e!=i ; ++i) + { + free(include_file); + dir = *i; + dir_length = strlen(dir); + include_file = (char*)malloc(strlen(dir) + + length + 2); + memcpy(include_file, dir, dir_length); + include_file[dir_length] = '/'; + memcpy(include_file+dir_length+1, file, length); + include_file[dir_length+length+1] = 0; + include_buffer = buffer_for_file(include_file); + if (include_buffer != 0) + { + break; + } + } + } + if (depfile != 0) + { + putc(' ', depfile); + fputs(include_file, depfile); + } + if (include_buffer == 0) + { + valid = false; + return; + } + input_buffer &include = *include_buffer; + input.consume(include_file+dir_length+1); + input.consume('"'); + free((void*)include_file); + + if (!read_header) + { + include.next_token(); + read_header = include.consume("/dts-v1/;"); + } + parse_roots(include, roots); + } + input.next_token(); + if (!read_header) + { + input.parse_error("Expected /dts-v1/; version string"); + } + // Read any memory reservations + while(input.consume("/memreserve/")) + { + long long start, len; + input.next_token(); + // Read the start and length. + if (!(input.consume_integer(start) && + (input.next_token(), + input.consume_integer(len)))) + { + input.parse_error("Expected /dts-v1/; version string"); + } + input.next_token(); + input.consume(';'); + reservations.push_back(reservation(start, len)); + } + parse_roots(input, roots); + switch (roots.size()) + { + case 0: + valid = false; + input.parse_error("Failed to find root node /."); + return; + case 1: + root = roots[0]; + break; + default: + { + root = roots[0]; + for (std::vector<node*>::iterator i=roots.begin()+1, + e=roots.end() ; i!=e ; ++i) + { + root->merge_node(*i); + delete *i; + } + roots.resize(1); + } + } + collect_names(); + resolve_cross_references(); +} + +device_tree::~device_tree() +{ + if (root != 0) + { + delete root; + } + while (!buffers.empty()) + { + delete buffers.back(); + buffers.pop_back(); + } +} + +} // namespace fdt + +} // namespace dtc + diff --git a/usr.bin/dtc/fdt.hh b/usr.bin/dtc/fdt.hh new file mode 100644 index 0000000..b00ea48 --- /dev/null +++ b/usr.bin/dtc/fdt.hh @@ -0,0 +1,782 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _FDT_HH_ +#define _FDT_HH_ +#include <map> + +#include "util.hh" +#include "string.hh" + +namespace dtc +{ + +namespace dtb +{ +struct output_writer; +class string_table; +} + +namespace fdt +{ +/** + * Properties may contain a number of different value, each with a different + * label. This class encapsulates a single value. + */ +struct property_value +{ + /** + * The label for this data. This is usually empty. + */ + string label; + /** + * If this value is a string, or something resolved from a string (a + * reference) then this contains the source string. + */ + string string_data; + /** + * The data that should be written to the final output. + */ + byte_buffer byte_data; + /** + * Enumeration describing the possible types of a value. Note that + * property-coded arrays will appear simply as binary (or possibly + * string, if they happen to be nul-terminated and printable), and must + * be checked separately. + */ + enum value_type + { + /** + * This is a list of strings. When read from source, string + * lists become one property value for each string, however + * when read from binary we have a single property value + * incorporating the entire text, with nul bytes separating the + * strings. + */ + STRING_LIST, + /** + * This property contains a single string. + */ + STRING, + /** + * This is a binary value. Check the size of byte_data to + * determine how many bytes this contains. + */ + BINARY, + /** This contains a short-form address that should be replaced + * by a fully-qualified version. This will only appear when + * the input is a device tree source. When parsed from a + * device tree blob, the cross reference will have already been + * resolved and the property value will be a string containing + * the full path of the target node. */ + CROSS_REFERENCE, + /** + * This is a phandle reference. When parsed from source, the + * string_data will contain the node label for the target and, + * after cross references have been resolved, the binary data + * will contain a 32-bit integer that should match the phandle + * property of the target node. + */ + PHANDLE, + /** + * An empty property value. This will never appear on a real + * property value, it is used by checkers to indicate that no + * property values should exist for a property. + */ + EMPTY, + /** + * The type of this property has not yet been determined. + */ + UNKNOWN + }; + /** + * The type of this property. + */ + value_type type; + /** + * Returns true if this value is a cross reference, false otherwise. + */ + inline bool is_cross_reference() + { + return is_type(CROSS_REFERENCE); + } + /** + * Returns true if this value is a phandle reference, false otherwise. + */ + inline bool is_phandle() + { + return is_type(PHANDLE); + } + /** + * Returns true if this value is a string, false otherwise. + */ + inline bool is_string() + { + return is_type(STRING); + } + /** + * Returns true if this value is a string list (a nul-separated + * sequence of strings), false otherwise. + */ + inline bool is_string_list() + { + return is_type(STRING_LIST); + } + /** + * Returns true if this value is binary, false otherwise. + */ + inline bool is_binary() + { + return is_type(BINARY); + } + /** + * Returns this property value as a 32-bit integer. Returns 0 if this + * property value is not 32 bits long. The bytes in the property value + * are assumed to be in big-endian format, but the return value is in + * the host native endian. + */ + uint32_t get_as_uint32(); + /** + * Default constructor, specifying the label of the value. + */ + property_value(string l=string()) : label(l), type(UNKNOWN) {} + /** + * Writes the data for this value into an output buffer. + */ + void push_to_buffer(byte_buffer &buffer); + + /** + * Writes the property value to the standard output. This uses the + * following heuristics for deciding how to print the output: + * + * - If the value is nul-terminated and only contains printable + * characters, it is written as a string. + * - If it is a multiple of 4 bytes long, then it is printed as cells. + * - Otherwise, it is printed as a byte buffer. + */ + void write_dts(FILE *file); + private: + /** + * Returns whether the value is of the specified type. If the type of + * the value has not yet been determined, then this calculates it. + */ + inline bool is_type(value_type v) + { + if (type == UNKNOWN) + { + resolve_type(); + } + return type == v; + } + /** + * Determines the type of the value based on its contents. + */ + void resolve_type(); + /** + * Writes the property value to the specified file as a quoted string. + * This is used when generating DTS. + */ + void write_as_string(FILE *file); + /** + * Writes the property value to the specified file as a sequence of + * 32-bit big-endian cells. This is used when generating DTS. + */ + void write_as_cells(FILE *file); + /** + * Writes the property value to the specified file as a sequence of + * bytes. This is used when generating DTS. + */ + void write_as_bytes(FILE *file); +}; + +/** + * A value encapsulating a single property. This contains a key, optionally a + * label, and optionally one or more values. + */ +class property +{ + /** + * The name of this property. + */ + string key; + /** + * An optional label. + */ + string label; + /** + * The values in this property. + */ + std::vector<property_value> values; + /** + * Value indicating that this is a valid property. If a parse error + * occurs, then this value is false. + */ + bool valid; + /** + * Parses a string property value, i.e. a value enclosed in double quotes. + */ + void parse_string(input_buffer &input); + /** + * Parses one or more 32-bit values enclosed in angle brackets. + */ + void parse_cells(input_buffer &input); + /** + * Parses an array of bytes, contained within square brackets. + */ + void parse_bytes(input_buffer &input); + /** + * Parses a reference. This is a node label preceded by an ampersand + * symbol, which should expand to the full path to that node. + * + * Note: The specification says that the target of such a reference is + * a node name, however dtc assumes that it is a label, and so we + * follow their interpretation for compatibility. + */ + void parse_reference(input_buffer &input); + /** + * Constructs a new property from two input buffers, pointing to the + * struct and strings tables in the device tree blob, respectively. + * The structs input buffer is assumed to have just consumed the + * FDT_PROP token. + */ + property(input_buffer &structs, input_buffer &strings); + /** + * Parses a new property from the input buffer. + */ + property(input_buffer &input, string k, string l); + public: + /** + * Creates an empty property. + */ + property(string k, string l=string()) : key(k), label(l), valid(true) + {} + /** + * Copy constructor. + */ + property(property &p) : key(p.key), label(p.label), values(p.values), + valid(p.valid) {} + /** + * Factory method for constructing a new property. Attempts to parse a + * property from the input, and returns it on success. On any parse + * error, this will return 0. + */ + static property* parse_dtb(input_buffer &structs, + input_buffer &strings); + /** + * Factory method for constructing a new property. Attempts to parse a + * property from the input, and returns it on success. On any parse + * error, this will return 0. + */ + static property* parse(input_buffer &input, + string key, + string label=string()); + /** + * Iterator type used for accessing the values of a property. + */ + typedef std::vector<property_value>::iterator value_iterator; + /** + * Returns an iterator referring to the first value in this property. + */ + inline value_iterator begin() + { + return values.begin(); + } + /** + * Returns an iterator referring to the last value in this property. + */ + inline value_iterator end() + { + return values.end(); + } + /** + * Adds a new value to an existing property. + */ + inline void add_value(property_value v) + { + values.push_back(v); + } + /** + * Returns the key for this property. + */ + inline string get_key() + { + return key; + } + /** + * Writes the property to the specified writer. The property name is a + * reference into the strings table. + */ + void write(dtb::output_writer &writer, dtb::string_table &strings); + /** + * Writes in DTS format to the specified file, at the given indent + * level. This will begin the line with the number of tabs specified + * as the indent level and then write the property in the most + * applicable way that it can determine. + */ + void write_dts(FILE *file, int indent); +}; + +/** + * Class encapsulating a device tree node. Nodes may contain properties and + * other nodes. + */ +class node +{ + public: + /** + * The label for this node, if any. Node labels are used as the + * targets for cross references. + */ + string label; + /** + * The name of the node. + */ + string name; + /** + * The unit address of the node, which is optionally written after the + * name followed by an at symbol. + */ + string unit_address; + private: + /** + * The properties contained within this node. + */ + std::vector<property*> properties; + /** + * The children of this node. + */ + std::vector<node*> children; + /** + * A flag indicating whether this node is valid. This is set to false + * if an error occurs during parsing. + */ + bool valid; + /** + * Parses a name inside a node, writing the string passed as the last + * argument as an error if it fails. + */ + string parse_name(input_buffer &input, + bool &is_property, + const char *error); + /** + * Constructs a new node from two input buffers, pointing to the struct + * and strings tables in the device tree blob, respectively. + */ + node(input_buffer &structs, input_buffer &strings); + /** + * Parses a new node from the specified input buffer. This is called + * when the input cursor is on the open brace for the start of the + * node. The name, and optionally label and unit address, should have + * already been parsed. + */ + node(input_buffer &input, string n, string l, string a); + /** + * Comparison function for properties, used when sorting the properties + * vector. Orders the properties based on their names. + */ + static inline bool cmp_properties(property *p1, property *p2); + /* + { + return p1->get_key() < p2->get_key(); + } + */ + /** + * Comparison function for nodes, used when sorting the children + * vector. Orders the nodes based on their names or, if the names are + * the same, by the unit addresses. + */ + static inline bool cmp_children(node *c1, node *c2); + /* + { + if (c1->name == c2->name) + { + return c1->unit_address < c2->unit_address; + } + return c1->name < c2->name; + } + */ + public: + /** + * Sorts the node's properties and children into alphabetical order and + * recursively sorts the children. + */ + void sort(); + /** + * Iterator type for child nodes. + */ + typedef std::vector<node*>::iterator child_iterator; + /** + * Returns an iterator for the first child of this node. + */ + inline child_iterator child_begin() + { + return children.begin(); + } + /** + * Returns an iterator after the last child of this node. + */ + inline child_iterator child_end() + { + return children.end(); + } + /** + * Iterator type for properties of a node. + */ + typedef std::vector<property*>::iterator property_iterator; + /** + * Returns an iterator after the last property of this node. + */ + inline property_iterator property_begin() + { + return properties.begin(); + } + /** + * Returns an iterator for the first property of this node. + */ + inline property_iterator property_end() + { + return properties.end(); + } + /** + * Factory method for constructing a new node. Attempts to parse a + * node in DTS format from the input, and returns it on success. On + * any parse error, this will return 0. This should be called with the + * cursor on the open brace of the property, after the name and so on + * have been parsed. + */ + static node* parse(input_buffer &input, + string name, + string label=string(), + string address=string()); + /** + * Factory method for constructing a new node. Attempts to parse a + * node in DTB format from the input, and returns it on success. On + * any parse error, this will return 0. This should be called with the + * cursor on the open brace of the property, after the name and so on + * have been parsed. + */ + static node* parse_dtb(input_buffer &structs, input_buffer &strings); + /** + * Destroys the node, recursively deleting all of its properties and + * children. + */ + ~node(); + /** + * Returns a property corresponding to the specified key, or 0 if this + * node does not contain a property of that name. + */ + property *get_property(string key); + /** + * Adds a new property to this node. + */ + inline void add_property(property *p) + { + properties.push_back(p); + } + /** + * Merges a node into this one. Any properties present in both are + * overridden, any properties present in only one are preserved. + */ + void merge_node(node *other); + /** + * Write this node to the specified output. Although nodes do not + * refer to a string table directly, their properties do. The string + * table passed as the second argument is used for the names of + * properties within this node and its children. + */ + void write(dtb::output_writer &writer, dtb::string_table &strings); + /** + * Writes the current node as DTS to the specified file. The second + * parameter is the indent level. This function will start every line + * with this number of tabs. + */ + void write_dts(FILE *file, int indent); +}; + +/** + * Class encapsulating the entire parsed FDT. This is the top-level class, + * which parses the entire DTS representation and write out the finished + * version. + */ +class device_tree +{ + public: + /** + * Type used for node paths. A node path is sequence of names and unit + * addresses. + */ + typedef std::vector<std::pair<string,string> > node_path; + /** + * Name that we should use for phandle nodes. + */ + enum phandle_format + { + /** linux,phandle */ + LINUX, + /** phandle */ + EPAPR, + /** Create both nodes. */ + BOTH + }; + private: + /** + * The format that we should use for writing phandles. + */ + phandle_format phandle_node_name; + /** + * Flag indicating that this tree is valid. This will be set to false + * on parse errors. + */ + bool valid; + /** + * Type used for memory reservations. A reservation is two 64-bit + * values indicating a base address and length in memory that the + * kernel should not use. The high 32 bits are ignored on 32-bit + * platforms. + */ + typedef std::pair<uint64_t, uint64_t> reservation; + /** + * The memory reserves table. + */ + std::vector<reservation> reservations; + /** + * Root node. All other nodes are children of this node. + */ + node *root; + /** + * Mapping from names to nodes. Only unambiguous names are recorded, + * duplicate names are stored as (node*)-1. + */ + std::map<string, node*> node_names; + /** + * A map from labels to node paths. When resolving cross references, + * we look up referenced nodes in this and replace the cross reference + * with the full path to its target. + */ + std::map<string, node_path> node_paths; + /** + * A collection of property values that are references to other nodes. + * These should be expanded to the full path of their targets. + */ + std::vector<property_value*> cross_references; + /** + * A collection of property values that refer to phandles. These will + * be replaced by the value of the phandle property in their + * destination. + */ + std::vector<property_value*> phandles; + /** + * A collection of input buffers that we are using. These input + * buffers are the ones that own their memory, and so we must preserve + * them for the lifetime of the device tree. + */ + std::vector<input_buffer*> buffers; + /** + * A map of used phandle values to nodes. All phandles must be unique, + * so we keep a set of ones that the user explicitly provides in the + * input to ensure that we don't reuse them. + * + * This is a map, rather than a set, because we also want to be able to + * find phandles that were provided by the user explicitly when we are + * doing checking. + */ + std::map<uint32_t, node*> used_phandles; + /** + * Paths to search for include files. This contains a set of + * nul-terminated strings, which are not owned by this class and so + * must be freed separately. + */ + std::vector<const char*> include_paths; + /** + * The default boot CPU, specified in the device tree header. + */ + uint32_t boot_cpu; + /** + * The number of empty reserve map entries to generate in the blob. + */ + uint32_t spare_reserve_map_entries; + /** + * The minimum size in bytes of the blob. + */ + uint32_t minimum_blob_size; + /** + * The number of bytes of padding to add to the end of the blob. + */ + uint32_t blob_padding; + /** + * Visit all of the nodes recursively, and if they have labels then add + * them to the node_paths and node_names vectors so that they can be + * used in resolving cross references. Also collects phandle + * properties that have been explicitly added. + */ + void collect_names_recursive(node* n, node_path &path); + /** + * Calls the recursive version of this method on every root node. + */ + void collect_names(); + /** + * Resolves all cross references. Any properties that refer to another + * node must have their values replaced by either the node path or + * phandle value. + */ + void resolve_cross_references(); + /** + * Parses root nodes from the top level of a dts file. + */ + void parse_roots(input_buffer &input, std::vector<node*> &roots); + /** + * Allocates a new mmap()'d input buffer for use in parsing. This + * object then keeps a reference to it, ensuring that it is not + * deallocated until the device tree is destroyed. + */ + input_buffer *buffer_for_file(const char *path); + /** + * Template function that writes a dtb blob using the specified writer. + * The writer defines the output format (assembly, blob). + */ + template<class writer> + void write(int fd); + public: + /** + * Returns the node referenced by the property. If this is a tree that + * is in source form, then we have a string that we can use to index + * the cross_references array and so we can just look that up. + */ + node *referenced_node(property_value &v); + /** + * Writes this FDT as a DTB to the specified output. + */ + void write_binary(int fd); + /** + * Writes this FDT as an assembly representation of the DTB to the + * specified output. The result can then be assembled and linked into + * a program. + */ + void write_asm(int fd); + /** + * Writes the tree in DTS (source) format. + */ + void write_dts(int fd); + /** + * Default constructor. Creates a valid, but empty FDT. + */ + device_tree() : phandle_node_name(EPAPR), valid(true), root(0), + boot_cpu(0), spare_reserve_map_entries(0), + minimum_blob_size(0), blob_padding(0) {} + /** + * Constructs a device tree from the specified file name, referring to + * a file that contains a device tree blob. + */ + void parse_dtb(const char *fn, FILE *depfile); + /** + * Constructs a device tree from the specified file name, referring to + * a file that contains device tree source. + */ + void parse_dts(const char *fn, FILE *depfile); + /** + * Destroy the tree and any input buffers that it holds. + */ + ~device_tree(); + /** + * Returns whether this tree is valid. + */ + inline bool is_valid() + { + return valid; + } + /** + * Sets the format for writing phandle properties. + */ + inline void set_phandle_format(phandle_format f) + { + phandle_node_name = f; + } + /** + * Returns a pointer to the root node of this tree. No ownership + * transfer. + */ + inline node *get_root() const + { + return root; + } + /** + * Sets the physical boot CPU. + */ + void set_boot_cpu(uint32_t cpu) + { + boot_cpu = cpu; + } + /** + * Sorts the tree. Useful for debugging device trees. + */ + void sort() + { + root->sort(); + } + /** + * Adds a path to search for include files. The argument must be a + * nul-terminated string representing the path. The device tree keeps + * a pointer to this string, but does not own it: the caller is + * responsible for freeing it if required. + */ + void add_include_path(const char *path) + { + include_paths.push_back(path); + } + /** + * Sets the number of empty reserve map entries to add. + */ + void set_empty_reserve_map_entries(uint32_t e) + { + spare_reserve_map_entries = e; + } + /** + * Sets the minimum size, in bytes, of the blob. + */ + void set_blob_minimum_size(uint32_t s) + { + minimum_blob_size = s; + } + /** + * Sets the amount of padding to add to the blob. + */ + void set_blob_padding(uint32_t p) + { + blob_padding = p; + } +}; + +} // namespace fdt + +} // namespace dtc + +#endif // !_FDT_HH_ diff --git a/usr.bin/dtc/input_buffer.cc b/usr.bin/dtc/input_buffer.cc new file mode 100644 index 0000000..2a483f3 --- /dev/null +++ b/usr.bin/dtc/input_buffer.cc @@ -0,0 +1,263 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "input_buffer.hh" +#include <ctype.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +#include <sys/stat.h> +#include <sys/mman.h> +#include <assert.h> + +namespace dtc +{ + +void +input_buffer::skip_spaces() +{ + if (cursor >= size) { return; } + if (cursor < 0) { return; } + char c = buffer[cursor]; + while ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\f') + || (c == '\v') || (c == '\r')) + { + cursor++; + if (cursor > size) + { + c = '\0'; + } + else + { + c = buffer[cursor]; + } + } +} + +input_buffer +input_buffer::buffer_from_offset(int offset, int s) +{ + if (s == 0) + { + s = size - offset; + } + if (offset > size) + { + return input_buffer(); + } + if (s > (size-offset)) + { + return input_buffer(); + } + return input_buffer(&buffer[offset], s); +} + +bool +input_buffer::consume(const char *str) +{ + int len = strlen(str); + if (len > size - cursor) + { + return false; + } + else + { + for (int i=0 ; i<len ; ++i) + { + if (str[i] != buffer[cursor + i]) + { + return false; + } + } + cursor += len; + return true; + } + return false; +} + +bool +input_buffer::consume_integer(long long &outInt) +{ + // The first character must be a digit. Hex and octal strings + // are prefixed by 0 and 0x, respectively. + if (!isdigit((*this)[0])) + { + return false; + } + char *end=0; + outInt = strtoll(&buffer[cursor], &end, 0); + if (end == &buffer[cursor]) + { + return false; + } + cursor = end - buffer; + return true; +} + +bool +input_buffer::consume_hex_byte(uint8_t &outByte) +{ + if (!ishexdigit((*this)[0]) && !ishexdigit((*this)[1])) + { + return false; + } + outByte = (digittoint((*this)[0]) << 4) | digittoint((*this)[1]); + cursor += 2; + return true; +} + +input_buffer& +input_buffer::next_token() +{ + int start; + do { + start = cursor; + skip_spaces(); + // Parse /* comments + if (((*this)[0] == '/') && ((*this)[1] == '*')) + { + // eat the start of the comment + ++(*this); + ++(*this); + do { + // Find the ending * of */ + while ((**this != '\0') && (**this != '*')) + { + ++(*this); + } + // Eat the * + ++(*this); + } while ((**this != '\0') && (**this != '/')); + // Eat the / + ++(*this); + } + // Parse // comments + if (((*this)[0] == '/') && ((*this)[1] == '/')) + { + // eat the start of the comment + ++(*this); + ++(*this); + // Find the ending * of */ + while (**this != '\n') + { + ++(*this); + } + // Eat the \n + ++(*this); + } + } while (start != cursor); + return *this; +} + +void +input_buffer::parse_error(const char *msg) +{ + int line_count = 1; + int line_start = 0; + int line_end = cursor; + for (int i=cursor ; i>0 ; --i) + { + if (buffer[i] == '\n') + { + line_count++; + if (line_start == 0) + { + line_start = i+1; + } + } + } + for (int i=cursor+1 ; i<size ; ++i) + { + if (buffer[i] == '\n') + { + line_end = i; + break; + } + } + fprintf(stderr, "Error on line %d: %s\n", line_count, msg); + fwrite(&buffer[line_start], line_end-line_start, 1, stderr); + putc('\n', stderr); + for (int i=0 ; i<(cursor-line_start) ; ++i) + { + putc(' ', stderr); + } + putc('^', stderr); + putc('\n', stderr); +} +void +input_buffer::dump() +{ + fprintf(stderr, "Current cursor: %d\n", cursor); + fwrite(&buffer[cursor], size-cursor, 1, stderr); +} + +mmap_input_buffer::mmap_input_buffer(int fd) : input_buffer(0, 0) +{ + struct stat sb; + if (fstat(fd, &sb)) + { + perror("Failed to stat file"); + } + size = sb.st_size; + buffer = (const char*)mmap(0, size, PROT_READ, + MAP_PREFAULT_READ, fd, 0); + if (buffer == 0) + { + perror("Failed to mmap file"); + } +} + +mmap_input_buffer::~mmap_input_buffer() +{ + if (buffer != 0) + { + munmap((void*)buffer, size); + } +} + +stream_input_buffer::stream_input_buffer() : input_buffer(0, 0) +{ + int c; + while ((c = fgetc(stdin)) != EOF) + { + b.push_back(c); + } + buffer = b.data(); + size = b.size(); +} + +} // namespace dtc + diff --git a/usr.bin/dtc/input_buffer.hh b/usr.bin/dtc/input_buffer.hh new file mode 100644 index 0000000..9a38312 --- /dev/null +++ b/usr.bin/dtc/input_buffer.hh @@ -0,0 +1,289 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _INPUT_BUFFER_HH_ +#define _INPUT_BUFFER_HH_ +#include "util.hh" +#include <assert.h> + +namespace dtc +{ + +/** + * Class encapsulating the input file. Can be used as a const char*, but has + * range checking. Attempting to access anything out of range will return a 0 + * byte. The input buffer can be cheaply copied, without copying the + * underlying memory, however it is the user's responsibility to ensure that + * such copies do not persist beyond the lifetime of the underlying memory. + * + * This also contains methods for reporting errors and for consuming the token + * stream. + */ +class input_buffer +{ + protected: + /** + * The buffer. This class doesn't own the buffer, but the + * mmap_input_buffer subclass does. + */ + const char* buffer; + /** + * The size of the buffer. + */ + int size; + private: + /** + * The current place in the buffer where we are reading. This class + * keeps a separate size, pointer, and cursor so that we can move + * forwards and backwards and still have checks that we haven't fallen + * off either end. + */ + int cursor; + /** + * Private constructor. This is used to create input buffers that + * refer to the same memory, but have different cursors. + */ + input_buffer(const char* b, int s, int c) : buffer(b), size(s), + cursor(c) {} + /** + * Reads forward past any spaces. The DTS format is not whitespace + * sensitive and so we want to scan past whitespace when reading it. + */ + void skip_spaces(); + public: + /** + * Virtual destructor. Does nothing, but exists so that subclasses + * that own the memory can run cleanup code for deallocating it. + */ + virtual ~input_buffer() {}; + /** + * Constructs an empty buffer. + */ + input_buffer() : buffer(0), size(0), cursor(0) {} + /** + * Constructs a new buffer with a specified memory region and size. + */ + input_buffer(const char* b, int s) : buffer(b), size(s), cursor(0){} + /** + * Returns a new input buffer referring into this input, clamped to the + * specified size. If the requested buffer would fall outside the + * range of this one, then it returns an empty buffer. + * + * The returned buffer shares the same underlying storage as the + * original. This is intended to be used for splitting up the various + * sections of a device tree blob. Requesting a size of 0 will give a + * buffer that extends to the end of the available memory. + */ + input_buffer buffer_from_offset(int offset, int s=0); + /** + * Returns true if this buffer has no unconsumed space in it. + */ + inline bool empty() + { + return cursor >= size; + } + /** + * Dereferencing operator, allows the buffer to be treated as a char* + * and dereferenced to give a character. This returns a null byte if + * the cursor is out of range. + */ + inline char operator*() + { + if (cursor >= size) { return '\0'; } + if (cursor < 0) { return '\0'; } + return buffer[cursor]; + } + /** + * Array subscripting operator, returns a character at the specified + * index offset from the current cursor. The offset may be negative, + * to reread characters that have already been read. If the current + * cursor plus offset is outside of the range, this returns a nul + * byte. + */ + inline char operator[](int offset) + { + if (cursor + offset >= size) { return '\0'; } + if (cursor + offset < 0) { return '\0'; } + return buffer[cursor + offset]; + } + /** + * Increments the cursor, iterating forward in the buffer. + */ + inline input_buffer &operator++() + { + cursor++; + return *this; + } + /** + * Cast to char* operator. Returns a pointer into the buffer that can + * be used for constructing strings. + */ + inline operator const char*() + { + if (cursor >= size) { return 0; } + if (cursor < 0) { return 0; } + return &buffer[cursor]; + } + /** + * Consumes a character. Moves the cursor one character forward if the + * next character matches the argument, returning true. If the current + * character does not match the argument, returns false. + */ + inline bool consume(char c) + { + if ((*this)[0] == c) + { + ++(*this); + return true; + } + return false; + } + /** + * Consumes a string. If the (null-terminated) string passed as the + * argument appears in the input, advances the cursor to the end and + * returns true. Returns false if the string does not appear at the + * current point in the input. + */ + bool consume(const char *str); + /** + * Reads an integer in base 8, 10, or 16. Returns true and advances + * the cursor to the end of the integer if the cursor points to an + * integer, returns false and does not move the cursor otherwise. + * + * The parsed value is returned via the argument. + */ + bool consume_integer(long long &outInt); + /** + * Template function that consumes a binary value in big-endian format + * from the input stream. Returns true and advances the cursor if + * there is a value of the correct size. This function assumes that + * all values must be natively aligned, and so advances the cursor to + * the correct alignment before reading. + */ + template<typename T> + bool consume_binary(T &out) + { + int align = 0; + int type_size = sizeof(T); + if (cursor % type_size != 0) + { + align = type_size - (cursor % type_size); + } + if (size < cursor + align + type_size) + { + return false; + } + cursor += align; + assert(cursor % type_size == 0); + out = 0; + for (int i=0 ; i<type_size ; ++i) + { + out <<= 8; + out |= (((T)buffer[cursor++]) & 0xff); + } + return true; + } + /** + * Consumes two hex digits and return the resulting byte via the first + * argument. If the next two characters are hex digits, returns true + * and advances the cursor. If not, then returns false and leaves the + * cursor in place. + */ + bool consume_hex_byte(uint8_t &outByte); + /** + * Advances the cursor to the start of the next token, skipping + * comments and whitespace. If the cursor already points to the start + * of a token, then this function does nothing. + */ + input_buffer &next_token(); + /** + * Prints a message indicating the location of a parse error. + */ + void parse_error(const char *msg); + /** + * Dumps the current cursor value and the unconsumed values in the + * input buffer to the standard error. This method is intended solely + * for debugging. + */ + void dump(); +}; +/** + * Explicit specialisation for reading a single byte. + */ +template<> +inline bool input_buffer::consume_binary(uint8_t &out) +{ + if (size < cursor + 1) + { + return false; + } + out = buffer[cursor++]; + return true; +} + +/** + * Subclass of input_buffer that mmap()s a file and owns the resulting memory. + * When this object is destroyed, the memory is unmapped. + */ +struct mmap_input_buffer : public input_buffer +{ + /** + * Constructs a new buffer from the file passed in as a file + * descriptor. + */ + mmap_input_buffer(int fd); + /** + * Unmaps the buffer, if one exists. + */ + virtual ~mmap_input_buffer(); +}; +/** + * Input buffer read from standard input. This is used for reading device tree + * blobs and source from standard input. It reads the entire input into + * malloc'd memory, so will be very slow for large inputs. DTS and DTB files + * are very rarely more than 10KB though, so this is probably not a problem. + */ +struct stream_input_buffer : public input_buffer +{ + /** + * The buffer that will store the data read from the standard input. + */ + std::vector<char> b; + /** + * Constructs a new buffer from the standard input. + */ + stream_input_buffer(); +}; + +} // namespace dtc + +#endif // !_INPUT_BUFFER_HH_ diff --git a/usr.bin/dtc/string.cc b/usr.bin/dtc/string.cc new file mode 100644 index 0000000..283bafa --- /dev/null +++ b/usr.bin/dtc/string.cc @@ -0,0 +1,258 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "string.hh" +#include <ctype.h> +#include <stdio.h> + +namespace +{ +/** + * The source files are ASCII, so we provide a non-locale-aware version of + * isalpha. This is a class so that it can be used with a template function + * for parsing strings. + */ +struct is_alpha +{ + static inline bool check(const char c) + { + return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && + (c <= 'Z')); + } +}; +/** + * Check whether a character is in the set allowed for node names. This is a + * class so that it can be used with a template function for parsing strings. + */ +struct is_node_name_character +{ + static inline bool check(const char c) + { + switch(c) + { + default: + return false; + case 'a'...'z': case 'A'...'Z': case '0'...'9': + case ',': case '.': case '+': case '-': + case '_': + return true; + } + } +}; +/** + * Check whether a character is in the set allowed for property names. This is + * a class so that it can be used with a template function for parsing strings. + */ +struct is_property_name_character +{ + static inline bool check(const char c) + { + switch(c) + { + default: + return false; + case 'a'...'z': case 'A'...'Z': case '0'...'9': + case ',': case '.': case '+': case '-': + case '_': case '#': + return true; + } + } +}; + +} + +namespace dtc +{ + +template<class T> string +string::parse(input_buffer &s) +{ + const char *start = s; + int l=0; + while (T::check(*s)) { l++; ++s; } + return string(start, l); +} + +string::string(input_buffer &s) : start((const char*)s), length(0) +{ + while(s[length] != '\0') + { + length++; + } +} + +string +string::parse_node_name(input_buffer &s) +{ + return parse<is_node_name_character>(s); +} + +string +string::parse_property_name(input_buffer &s) +{ + return parse<is_property_name_character>(s); +} +string +string::parse_node_or_property_name(input_buffer &s, bool &is_property) +{ + if (is_property) + { + return parse_property_name(s); + } + const char *start = s; + int l=0; + while (is_node_name_character::check(*s)) + { + l++; + ++s; + } + while (is_property_name_character::check(*s)) + { + l++; + ++s; + is_property = true; + } + return string(start, l); +} + +bool +string::operator==(const string& other) const +{ + return (length == other.length) && + (memcmp(start, other.start, length) == 0); +} + +bool +string::operator==(const char *other) const +{ + return strncmp(other, start, length) == 0; +} + +bool +string::operator<(const string& other) const +{ + if (length < other.length) { return true; } + if (length > other.length) { return false; } + return memcmp(start, other.start, length) < 0; +} + +void +string::push_to_buffer(byte_buffer &buffer, bool escapes) +{ + for (int i=0 ; i<length ; ++i) + { + uint8_t c = start[i]; + if (escapes && c == '\\' && i+1 < length) + { + c = start[++i]; + switch (c) + { + // For now, we just ignore invalid escape sequences. + default: + case '"': + case '\'': + case '\\': + break; + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 't': + c = '\t'; + break; + case 'n': + c = '\n'; + break; + case 'v': + c = '\v'; + break; + case 'f': + c = '\f'; + break; + case 'r': + c = '\r'; + break; + case '0'...'7': + { + int v = digittoint(c); + if (i+1 < length && start[i+1] <= '7' && start[i+1] >= '0') + { + v <<= 3; + v |= digittoint(start[i+1]); + i++; + if (i+1 < length && start[i+1] <= '7' && start[i+1] >= '0') + { + v <<= 3; + v |= digittoint(start[i+1]); + } + } + c = (uint8_t)v; + break; + } + case 'x': + { + ++i; + if (i >= length) + { + break; + } + int v = digittoint(start[i]); + if (i+1 < length && ishexdigit(start[i+1])) + { + v <<= 4; + v |= digittoint(start[++i]); + } + c = (uint8_t)v; + break; + } + } + } + buffer.push_back(c); + } +} + +void +string::print(FILE *file) +{ + fwrite(start, length, 1, file); +} + +void +string::dump() +{ + print(stderr); +} + +} // namespace dtc + diff --git a/usr.bin/dtc/string.hh b/usr.bin/dtc/string.hh new file mode 100644 index 0000000..45bc4fd --- /dev/null +++ b/usr.bin/dtc/string.hh @@ -0,0 +1,147 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _STRING_HH_ +#define _STRING_HH_ +#include "input_buffer.hh" + +namespace dtc +{ + +/** + * String, referring to a place in the input file. We don't bother copying + * strings until we write them to the final output. These strings should be + * two words long: a start and a length. They are intended to be cheap to copy + * and store in collections. Copying the string object does not copy the + * underlying storage. + * + * Strings are not nul-terminated. + */ +class string +{ + /** Start address. Contained within the mmap()'d input file and not + * owned by this object. */ + const char *start; + /** length of the string. DTS strings are allowed to contain nuls */ + int length; + /** Generic function for parsing strings matching the character set + * defined by the template argument. */ + template<class T> + static string parse(input_buffer &s); + public: + /** + * Constructs a string referring into another buffer. + */ + string(const char *s, int l) : start(s), length(l) {} + /** Constructs a string from a C string. */ + string(const char *s) : start(s), length(strlen(s)) {} + /** Default constructor, returns an empty string. */ + string() : start(0), length(0) {} + /** Construct a from an input buffer, ending with a nul terminator. */ + string(input_buffer &s); + /** + * Returns the longest string in the input buffer starting at the + * current cursor and composed entirely of characters that are valid in + * node names. + */ + static string parse_node_name(input_buffer &s); + /** + * Returns the longest string in the input buffer starting at the + * current cursor and composed entirely of characters that are valid in + * property names. + */ + static string parse_property_name(input_buffer &s); + /** + * Parses either a node or a property name. If is_property is true on + * entry, then only property names are parsed. If it is false, then it + * will be set, on return, to indicate whether the parsed name is only + * valid as a property. + */ + static string parse_node_or_property_name(input_buffer &s, + bool &is_property); + /** + * Compares two strings for equality. Strings are equal if they refer + * to identical byte sequences. + */ + bool operator==(const string& other) const; + /** + * Compares a string against a C string. The trailing nul in the C + * string is ignored for the purpose of comparison, so this will always + * fail if the string contains nul bytes. + */ + bool operator==(const char *other) const; + /** + * Inequality operator, defined as the inverse of the equality + * operator. + */ + template <typename T> + inline bool operator!=(T other) + { + return !(*this == other); + } + /** + * Comparison operator, defined to allow strings to be used as keys in + * maps. + */ + bool operator<(const string& other) const; + /** + * Returns true if this is the empty string, false otherwise. + */ + inline bool empty() const + { + return length == 0; + } + /** + * Returns the size of the string, in bytes. + */ + inline size_t size() + { + return length; + } + /** + * Writes the string to the specified buffer. + */ + void push_to_buffer(byte_buffer &buffer, bool escapes=false); + /** + * Prints the string to the specified output stream. + */ + void print(FILE *file); + /** + * Dumps the string to the standard error stream. Intended to be used + * for debugging. + */ + void dump(); +}; + +} // namespace dtc + +#endif // !_STRING_HH_ diff --git a/usr.bin/dtc/util.hh b/usr.bin/dtc/util.hh new file mode 100644 index 0000000..7f2ec67 --- /dev/null +++ b/usr.bin/dtc/util.hh @@ -0,0 +1,92 @@ +/*- + * Copyright (c) 2013 David Chisnall + * All rights reserved. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _UTIL_HH_ +#define _UTIL_HH_ + +#include <vector> + +// If we aren't using C++11, then just ignore static asserts. +#if __cplusplus < 201103L +#ifndef static_assert +#define static_assert(x, y) ((void)0) +#endif +#endif + +namespace dtc { + +/** + * Type for a buffer of bytes. This is used for a lot of short-lived temporary + * variables, so may eventually be changed to something like LLVM's + * SmallVector, but currently the program runs in a tiny fraction of a second, + * so this is not an issue. + */ +typedef std::vector<uint8_t> byte_buffer; + +/** + * Helper function to push a big endian value into a byte buffer. We use + * native-endian values for all of the in-memory data structures and only + * transform them into big endian form for output. + */ +template<typename T> +inline void push_big_endian(byte_buffer &v, T val) +{ + static_assert(sizeof(T) > 1, + "Big endian doesn't make sense for single-byte values"); + for (int bit=(sizeof(T) - 1)*8 ; bit>=0 ; bit-= 8) + { + v.push_back((val >> bit) & 0xff); + } +} + +/** + * Simple inline non-locale-aware check that this is a valid ASCII + * digit. + */ +inline bool isdigit(char c) +{ + return (c >= '0') && (c <= '9'); +} + +/** + * Simple inline non-locale-aware check that this is a valid ASCII + * hex digit. + */ +inline bool ishexdigit(char c) +{ + return ((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'f')) || + ((c >= 'A') && (c <= 'Z')); +} + +}// namespace dtc + +#endif // !_UTIL_HH_ diff --git a/usr.bin/du/du.1 b/usr.bin/du/du.1 index d946f47..e1224c9 100644 --- a/usr.bin/du/du.1 +++ b/usr.bin/du/du.1 @@ -28,7 +28,7 @@ .\" @(#)du.1 8.2 (Berkeley) 4/1/94 .\" $FreeBSD$ .\" -.Dd July 18, 2012 +.Dd November 4, 2012 .Dt DU 1 .Os .Sh NAME @@ -191,6 +191,24 @@ or options are not specified, the block counts will be displayed in 512-byte blocks. .El +.Sh EXAMPLES +Show disk usage for all files in the current directory. +Output is in human-readable form: +.Pp +.Dl # du -ah +.Pp +Summarize disk usage in the current directory: +.Pp +.Dl # du -hs +.Pp +Summarize disk usage for a specific directory: +.Pp +.Dl # du -hs /home +.Pp +Show name and size of all C files in a specific directory. +Also display a grand total at the end: +.Pp +.Dl # du -ch /usr/src/sys/kern/*.c .Sh SEE ALSO .Xr df 1 , .Xr chflags 2 , diff --git a/usr.bin/ee/Makefile b/usr.bin/ee/Makefile index ffa47b9..2236777 100644 --- a/usr.bin/ee/Makefile +++ b/usr.bin/ee/Makefile @@ -13,7 +13,7 @@ LDADD= -lncurses WARNS?= 2 -NLS= en_US.US-ASCII fr_FR.ISO8859-1 de_DE.ISO8859-1 pl_PL.ISO8859-2 \ +NLS= C fr_FR.ISO8859-1 de_DE.ISO8859-1 pl_PL.ISO8859-2 \ uk_UA.KOI8-U pt_BR.ISO8859-1 ru_RU.KOI8-R hu_HU.ISO8859-2 NLSLINKS_en_US.US-ASCII= en_US.ISO8859-1 en_US.ISO8859-15 diff --git a/usr.bin/fetch/fetch.c b/usr.bin/fetch/fetch.c index 3e897d7..d3e9b21 100644 --- a/usr.bin/fetch/fetch.c +++ b/usr.bin/fetch/fetch.c @@ -115,11 +115,13 @@ sig_handler(int sig) struct xferstat { char name[64]; - struct timeval start; - struct timeval last; - off_t size; - off_t offset; - off_t rcvd; + struct timeval start; /* start of transfer */ + struct timeval last; /* time of last update */ + struct timeval last2; /* time of previous last update */ + off_t size; /* size of file per HTTP hdr */ + off_t offset; /* starting offset in file */ + off_t rcvd; /* bytes already received */ + off_t lastrcvd; /* bytes received since last update */ }; /* @@ -139,9 +141,12 @@ stat_eta(struct xferstat *xs) if (eta > 3600) snprintf(str, sizeof str, "%02ldh%02ldm", eta / 3600, (eta % 3600) / 60); - else + else if (eta > 0) snprintf(str, sizeof str, "%02ldm%02lds", eta / 60, eta % 60); + else + snprintf(str, sizeof str, "%02ldm%02lds", + elapsed / 60, elapsed % 60); return (str); } @@ -173,11 +178,12 @@ stat_bps(struct xferstat *xs) double delta, bps; delta = (xs->last.tv_sec + (xs->last.tv_usec / 1.e6)) - - (xs->start.tv_sec + (xs->start.tv_usec / 1.e6)); + - (xs->last2.tv_sec + (xs->last2.tv_usec / 1.e6)); + if (delta == 0.0) { snprintf(str, sizeof str, "?? Bps"); } else { - bps = (xs->rcvd - xs->offset) / delta; + bps = (xs->rcvd - xs->lastrcvd) / delta; snprintf(str, sizeof str, "%sps", stat_bytes((off_t)bps)); } return (str); @@ -200,6 +206,7 @@ stat_display(struct xferstat *xs, int force) gettimeofday(&now, NULL); if (!force && now.tv_sec <= xs->last.tv_sec) return; + xs->last2 = xs->last; xs->last = now; fprintf(stderr, "\r%-46.46s", xs->name); @@ -214,10 +221,16 @@ stat_display(struct xferstat *xs, int force) (int)((100.0 * xs->rcvd) / xs->size), stat_bytes(xs->size)); } + if (force == 2) { + xs->lastrcvd = xs->offset; + xs->last2 = xs->start; + } fprintf(stderr, " %s", stat_bps(xs)); - if (xs->size > 0 && xs->rcvd > 0 && - xs->last.tv_sec >= xs->start.tv_sec + 10) + if ((xs->size > 0 && xs->rcvd > 0 && + xs->last.tv_sec >= xs->start.tv_sec + 3) || + force == 2) fprintf(stderr, " %s", stat_eta(xs)); + xs->lastrcvd = xs->rcvd; } /* @@ -232,6 +245,7 @@ stat_start(struct xferstat *xs, const char *name, off_t size, off_t offset) xs->size = size; xs->offset = offset; xs->rcvd = offset; + xs->lastrcvd = offset; if (v_tty && v_level > 0) stat_display(xs, 1); else if (v_level > 0) @@ -257,7 +271,7 @@ stat_end(struct xferstat *xs) { gettimeofday(&xs->last, NULL); if (v_tty && v_level > 0) { - stat_display(xs, 1); + stat_display(xs, 2); putc('\n', stderr); } else if (v_level > 0) { fprintf(stderr, " %s %s\n", @@ -604,7 +618,10 @@ fetch(char *URL, const char *path) asprintf(&tmppath, "%.*s.fetch.XXXXXX.%s", (int)(slash - path), path, slash); if (tmppath != NULL) { - mkstemps(tmppath, strlen(slash) + 1); + if (mkstemps(tmppath, strlen(slash) + 1) == -1) { + warn("%s: mkstemps()", path); + goto failure; + } of = fopen(tmppath, "w"); chown(tmppath, sb.st_uid, sb.st_gid); chmod(tmppath, sb.st_mode & ALLPERMS); @@ -974,7 +991,8 @@ main(int argc, char *argv[]) if (v_tty) fetchAuthMethod = query_auth; if (N_filename != NULL) - setenv("NETRC", N_filename, 1); + if (setenv("NETRC", N_filename, 1) == -1) + err(1, "setenv: cannot set NETRC=%s", N_filename); while (argc) { if ((p = strrchr(*argv, '/')) == NULL) diff --git a/usr.bin/find/find.1 b/usr.bin/find/find.1 index cf10bf2..47c40d8 100644 --- a/usr.bin/find/find.1 +++ b/usr.bin/find/find.1 @@ -31,7 +31,7 @@ .\" @(#)find.1 8.7 (Berkeley) 5/9/95 .\" $FreeBSD$ .\" -.Dd September 9, 2012 +.Dd November 18, 2012 .Dt FIND 1 .Os .Sh NAME @@ -203,12 +203,19 @@ files with extended ACLs. See .Xr acl 3 for more information. -.It Ic -amin Ar n +.It Ic -amin Oo Cm - Ns | Ns Cm + Oc Ns Ar n True if the difference between the file last access time and the time .Nm was started, rounded up to the next full minute, is +more than .Ar n -minutes. +.Pq + Ns Ar n , +less than +.Ar n +.Pq - Ns Ar n , +or exactly +.Ar n +minutes ago. .It Ic -anewer Ar file Same as .Ic -neweram . @@ -250,13 +257,20 @@ Units are probably only useful when used in conjunction with the or .Cm - modifier. -.It Ic -cmin Ar n +.It Ic -cmin Oo Cm - Ns | Ns Cm + Oc Ns Ar n True if the difference between the time of last change of file status information and the time .Nm was started, rounded up to the next full minute, is +more than .Ar n -minutes. +.Pq + Ns Ar n , +less than +.Ar n +.Pq - Ns Ar n , +or exactly +.Ar n +minutes ago. .It Ic -cnewer Ar file Same as .Ic -newercm . @@ -284,7 +298,7 @@ Non-portable, BSD-specific version of .Ic depth . GNU find implements this as a primary in mistaken emulation of .Fx -.Xr find 1 . +.Nm . .It Ic -delete Delete found files and/or directories. Always returns true. @@ -459,7 +473,7 @@ for compatibility with GNU find. GNU find imposes a restriction that .Ar gname is numeric, while -.Xr find 1 +.Nm does not. .It Ic -group Ar gname True if the file belongs to the group @@ -540,12 +554,18 @@ primary is specified, it applies to the entire expression even if it would not normally be evaluated. .Dq Ic -mindepth Li 1 processes all but the command line arguments. -.It Ic -mmin Ar n +.It Ic -mmin Oo Cm - Ns | Ns Cm + Oc Ns Ar n True if the difference between the file last modification time and the time .Nm was started, rounded up to the next full minute, is .Ar n -minutes. +.Pq + Ns Ar n , +less than +.Ar n +.Pq - Ns Ar n , +or exactly +.Ar n +minutes ago. .It Ic -mnewer Ar file Same as .Ic -newer . @@ -626,7 +646,7 @@ This is default behaviour. .It Ic -noleaf This option is for GNU find compatibility. In GNU find it disables an optimization not relevant to -.Xr find 1 , +.Nm , so it is ignored. .It Ic -nouser True if the file belongs to an unknown user. @@ -823,7 +843,7 @@ for compatibility with GNU find. GNU find imposes a restriction that .Ar uname is numeric, while -.Xr find 1 +.Nm does not. .It Ic -user Ar uname True if the file belongs to the user diff --git a/usr.bin/grep/Makefile b/usr.bin/grep/Makefile index 3d4b67b..359954b 100644 --- a/usr.bin/grep/Makefile +++ b/usr.bin/grep/Makefile @@ -40,17 +40,24 @@ MLINKS= grep.1 egrep.1 \ grep.1 lzfgrep.1 .endif +LDADD= -lz +DPADD= ${LIBZ} + +.if !defined(WITHOUT_LZMA_SUPPORT) +LDADD+= -llzma +DPADD+= ${LIBLZMA} + LINKS+= ${BINDIR}/${PROG} ${BINDIR}/xzgrep \ ${BINDIR}/${PROG} ${BINDIR}/xzegrep \ ${BINDIR}/${PROG} ${BINDIR}/xzfgrep \ ${BINDIR}/${PROG} ${BINDIR}/lzgrep \ ${BINDIR}/${PROG} ${BINDIR}/lzegrep \ ${BINDIR}/${PROG} ${BINDIR}/lzfgrep +.else +CFLAGS+= -DWITHOUT_LZMA +.endif -LDADD= -lz -llzma -DPADD= ${LIBZ} ${LIBLZMA} - -.if !defined(WITHOUT_BZIP2) +.if !defined(WITHOUT_BZIP2_SUPPORT) LDADD+= -lbz2 DPADD+= ${LIBBZ2} diff --git a/usr.bin/grep/file.c b/usr.bin/grep/file.c index 8cee2c0..6bcaa52 100644 --- a/usr.bin/grep/file.c +++ b/usr.bin/grep/file.c @@ -41,7 +41,6 @@ __FBSDID("$FreeBSD$"); #include <err.h> #include <errno.h> #include <fcntl.h> -#include <lzma.h> #include <stddef.h> #include <stdlib.h> #include <string.h> @@ -50,6 +49,10 @@ __FBSDID("$FreeBSD$"); #include <wctype.h> #include <zlib.h> +#ifndef WITHOUT_LZMA +#include <lzma.h> +#endif + #ifndef WITHOUT_BZIP2 #include <bzlib.h> #endif @@ -60,7 +63,9 @@ __FBSDID("$FreeBSD$"); #define LNBUFBUMP 80 static gzFile gzbufdesc; +#ifndef WITHOUT_LZMA static lzma_stream lstrm = LZMA_STREAM_INIT; +#endif #ifndef WITHOUT_BZIP2 static BZFILE* bzbufdesc; #endif @@ -116,6 +121,7 @@ grep_refill(struct file *f) nr = -1; } #endif +#ifndef WITHOUT_LZMA } else if ((filebehave == FILE_XZ) || (filebehave == FILE_LZMA)) { lzma_action action = LZMA_RUN; uint8_t in_buf[MAXBUFSIZ]; @@ -146,6 +152,7 @@ grep_refill(struct file *f) return (-1); bufrem = MAXBUFSIZ - lstrm.avail_out; return (0); +#endif /* WIHTOUT_LZMA */ } else nr = read(f->fd, buffer, MAXBUFSIZ); diff --git a/usr.bin/grep/grep.c b/usr.bin/grep/grep.c index 43d9334..117a5a9 100644 --- a/usr.bin/grep/grep.c +++ b/usr.bin/grep/grep.c @@ -108,6 +108,7 @@ bool iflag; /* -i: ignore case */ bool lflag; /* -l: only show names of files with matches */ bool mflag; /* -m x: stop reading the files after x matches */ long long mcount; /* count for -m */ +long long mlimit; /* requested value for -m */ bool nflag; /* -n: show line numbers in front of matching lines */ bool oflag; /* -o: print only matching part */ bool qflag; /* -q: quiet mode (don't output anything) */ @@ -478,7 +479,13 @@ main(int argc, char *argv[]) grepbehave = GREP_EXTENDED; break; case 'e': - add_pattern(optarg, strlen(optarg)); + { + char *token; + char *string = optarg; + + while ((token = strsep(&string, "\n")) != NULL) + add_pattern(token, strlen(token)); + } needpattern = 0; break; case 'F': @@ -524,7 +531,7 @@ main(int argc, char *argv[]) case 'm': mflag = true; errno = 0; - mcount = strtoll(optarg, &ep, 10); + mlimit = mcount = strtoll(optarg, &ep, 10); if (((errno == ERANGE) && (mcount == LLONG_MAX)) || ((errno == EINVAL) && (mcount == 0))) err(2, NULL); @@ -667,7 +674,11 @@ main(int argc, char *argv[]) /* Process patterns from command line */ if (aargc != 0 && needpattern) { - add_pattern(*aargv, strlen(*aargv)); + char *token; + char *string = *aargv; + + while ((token = strsep(&string, "\n")) != NULL) + add_pattern(token, strlen(token)); --aargc; ++aargv; } diff --git a/usr.bin/grep/grep.h b/usr.bin/grep/grep.h index 2a8f425..4ee348e 100644 --- a/usr.bin/grep/grep.h +++ b/usr.bin/grep/grep.h @@ -115,6 +115,7 @@ extern bool Eflag, Fflag, Gflag, Hflag, Lflag, extern bool dexclude, dinclude, fexclude, finclude, lbflag, nullflag; extern unsigned long long Aflag, Bflag; extern long long mcount; +extern long long mlimit; extern char *label; extern const char *color; extern int binbehave, devbehave, dirbehave, filebehave, grepbehave, linkbehave; diff --git a/usr.bin/grep/regex/tre-fastmatch.c b/usr.bin/grep/regex/tre-fastmatch.c index 6f1aec6..b7a7c91 100644 --- a/usr.bin/grep/regex/tre-fastmatch.c +++ b/usr.bin/grep/regex/tre-fastmatch.c @@ -468,7 +468,7 @@ static int fastcmp(const fastmatch_t *fg, const void *data, fg->nosub = (cflags & REG_NOSUB); \ \ /* Cannot handle REG_ICASE with MB string */ \ - if (fg->icase && (TRE_MB_CUR_MAX > 1)) \ + if (fg->icase && (TRE_MB_CUR_MAX > 1) && n > 0) \ { \ DPRINT(("Cannot use fast matcher for MBS with REG_ICASE\n")); \ return REG_BADPAT; \ diff --git a/usr.bin/grep/util.c b/usr.bin/grep/util.c index 4fb1240..0a3706f 100644 --- a/usr.bin/grep/util.c +++ b/usr.bin/grep/util.c @@ -176,8 +176,7 @@ procfile(const char *fn) mode_t s; int c, t; - if (mflag && (mcount <= 0)) - return (0); + mcount = mlimit; if (strcmp(fn, "-") == 0) { fn = label != NULL ? label : getstr(1); diff --git a/usr.bin/indent/indent_globs.h b/usr.bin/indent/indent_globs.h index 087f41c..2ea1d8d 100644 --- a/usr.bin/indent/indent_globs.h +++ b/usr.bin/indent/indent_globs.h @@ -213,7 +213,7 @@ struct fstate { char font[4]; char size; int allcaps:1; -}; +} __aligned(sizeof(int)); char *chfont(struct fstate *, struct fstate *, char *); struct fstate diff --git a/usr.bin/join/join.c b/usr.bin/join/join.c index 97bb4cf..afa7cbc 100644 --- a/usr.bin/join/join.c +++ b/usr.bin/join/join.c @@ -516,7 +516,7 @@ static void outfield(LINE *lp, u_long fieldno, int out_empty) { if (needsep++) - (void)printf("%lc", *tabchar); + (void)printf("%lc", (wint_t)*tabchar); if (!ferror(stdout)) { if (lp->fieldcnt <= fieldno || out_empty) { if (empty != NULL) diff --git a/usr.bin/ktrdump/ktrdump.c b/usr.bin/ktrdump/ktrdump.c index d74b979..77ae522 100644 --- a/usr.bin/ktrdump/ktrdump.c +++ b/usr.bin/ktrdump/ktrdump.c @@ -86,6 +86,7 @@ main(int ac, char **av) u_long parms[KTR_PARMS]; struct ktr_entry *buf; uintmax_t tlast, tnow; + unsigned long bufptr; struct stat sb; kvm_t *kd; FILE *out; @@ -179,8 +180,9 @@ main(int ac, char **av) if ((buf = malloc(sizeof(*buf) * entries)) == NULL) err(1, NULL); if (kvm_read(kd, nl[2].n_value, &index, sizeof(index)) == -1 || - kvm_read(kd, nl[3].n_value, buf, sizeof(*buf) * entries) - == -1) + kvm_read(kd, nl[3].n_value, &bufptr, + sizeof(bufptr)) == -1 || + kvm_read(kd, bufptr, buf, sizeof(*buf) * entries) == -1) errx(1, "%s", kvm_geterr(kd)); } diff --git a/usr.bin/less/defines.h b/usr.bin/less/defines.h index fe8f1bc..27b73ce 100644 --- a/usr.bin/less/defines.h +++ b/usr.bin/less/defines.h @@ -184,6 +184,7 @@ /* * Sizes of various buffers. */ +#if 0 /* old sizes for small memory machines */ #define CMDBUF_SIZE 512 /* Buffer for multichar commands */ #define UNGOT_SIZE 100 /* Max chars to unget() */ #define LINEBUF_SIZE 1024 /* Max size of line in input file */ @@ -193,6 +194,17 @@ #define TERMSBUF_SIZE 1024 /* Buffer to hold termcap strings */ #define TAGLINE_SIZE 512 /* Max size of line in tags file */ #define TABSTOP_MAX 32 /* Max number of custom tab stops */ +#else /* more reasonable sizes for modern machines */ +#define CMDBUF_SIZE 2048 /* Buffer for multichar commands */ +#define UNGOT_SIZE 200 /* Max chars to unget() */ +#define LINEBUF_SIZE 1024 /* Initial max size of line in input file */ +#define OUTBUF_SIZE 1024 /* Output buffer */ +#define PROMPT_SIZE 2048 /* Max size of prompt string */ +#define TERMBUF_SIZE 2048 /* Termcap buffer for tgetent */ +#define TERMSBUF_SIZE 1024 /* Buffer to hold termcap strings */ +#define TAGLINE_SIZE 1024 /* Max size of line in tags file */ +#define TABSTOP_MAX 128 /* Max number of custom tab stops */ +#endif /* Settings automatically determined by configure. */ diff --git a/usr.bin/less/zless.sh b/usr.bin/less/zless.sh index b947b81..f2e035c 100644 --- a/usr.bin/less/zless.sh +++ b/usr.bin/less/zless.sh @@ -3,5 +3,5 @@ # $FreeBSD$ # -export LESSOPEN="|/usr/bin/lesspipe.sh %s" +export LESSOPEN="||/usr/bin/lesspipe.sh %s" exec /usr/bin/less "$@" diff --git a/usr.bin/locale/locale.1 b/usr.bin/locale/locale.1 index 144f280..73de7ae 100644 --- a/usr.bin/locale/locale.1 +++ b/usr.bin/locale/locale.1 @@ -25,7 +25,7 @@ .\" .\" $FreeBSD$ .\" -.Dd November 1, 2005 +.Dd November 18, 2012 .Dt LOCALE 1 .Os .Sh NAME @@ -40,7 +40,7 @@ .Op Ar prefix .Nm .Op Fl ck -.Ar keyword ... +.Op Ar keyword ... .Sh DESCRIPTION The .Nm @@ -70,12 +70,16 @@ will respect the .Ev PATH_LOCALE environment variable, and use it instead of the system's default locale directory. -.It Fl m -Print names of all available charmaps. -.It Fl k -Print the names and values of all selected keywords. .It Fl c Print the category name for all selected keywords. +If no keywords are selected, print the category name for all defined +keywords. +.It Fl k +Print the names and values of all selected keywords. +If no keywords are selected, print the names and values of all defined +keywords. +.It Fl m +Print names of all available charmaps. .El .Sh IMPLEMENTATION NOTES The special @@ -89,6 +93,17 @@ a prefix string can be defined to limit the amount of keywords returned. .Ex -std .Sh SEE ALSO .Xr setlocale 3 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.1-2004 . +The +.Ev LC_CTYPE , +.Ev LC_MESSAGES +and +.Ev NLSPATH +environment variables are not interpreted. .Sh BUGS Since .Fx diff --git a/usr.bin/locale/locale.c b/usr.bin/locale/locale.c index cad3afe..ee9bf84 100644 --- a/usr.bin/locale/locale.c +++ b/usr.bin/locale/locale.c @@ -31,7 +31,7 @@ * nl_langinfo(3) extensions) * * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require - * localedef(1) implementation). Currently it's handled via + * localedef(1) implementation). Currently it's handled via * nl_langinfo(CODESET). */ @@ -79,32 +79,32 @@ struct _lcinfo { { "LC_MONETARY", LC_MONETARY }, { "LC_MESSAGES", LC_MESSAGES } }; -#define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0])) +#define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0])) /* ids for values not referenced by nl_langinfo() */ #define KW_ZERO 10000 #define KW_GROUPING (KW_ZERO+1) -#define KW_INT_CURR_SYMBOL (KW_ZERO+2) -#define KW_CURRENCY_SYMBOL (KW_ZERO+3) -#define KW_MON_DECIMAL_POINT (KW_ZERO+4) -#define KW_MON_THOUSANDS_SEP (KW_ZERO+5) -#define KW_MON_GROUPING (KW_ZERO+6) -#define KW_POSITIVE_SIGN (KW_ZERO+7) -#define KW_NEGATIVE_SIGN (KW_ZERO+8) -#define KW_INT_FRAC_DIGITS (KW_ZERO+9) -#define KW_FRAC_DIGITS (KW_ZERO+10) -#define KW_P_CS_PRECEDES (KW_ZERO+11) -#define KW_P_SEP_BY_SPACE (KW_ZERO+12) -#define KW_N_CS_PRECEDES (KW_ZERO+13) -#define KW_N_SEP_BY_SPACE (KW_ZERO+14) -#define KW_P_SIGN_POSN (KW_ZERO+15) -#define KW_N_SIGN_POSN (KW_ZERO+16) -#define KW_INT_P_CS_PRECEDES (KW_ZERO+17) -#define KW_INT_P_SEP_BY_SPACE (KW_ZERO+18) -#define KW_INT_N_CS_PRECEDES (KW_ZERO+19) -#define KW_INT_N_SEP_BY_SPACE (KW_ZERO+20) -#define KW_INT_P_SIGN_POSN (KW_ZERO+21) -#define KW_INT_N_SIGN_POSN (KW_ZERO+22) +#define KW_INT_CURR_SYMBOL (KW_ZERO+2) +#define KW_CURRENCY_SYMBOL (KW_ZERO+3) +#define KW_MON_DECIMAL_POINT (KW_ZERO+4) +#define KW_MON_THOUSANDS_SEP (KW_ZERO+5) +#define KW_MON_GROUPING (KW_ZERO+6) +#define KW_POSITIVE_SIGN (KW_ZERO+7) +#define KW_NEGATIVE_SIGN (KW_ZERO+8) +#define KW_INT_FRAC_DIGITS (KW_ZERO+9) +#define KW_FRAC_DIGITS (KW_ZERO+10) +#define KW_P_CS_PRECEDES (KW_ZERO+11) +#define KW_P_SEP_BY_SPACE (KW_ZERO+12) +#define KW_N_CS_PRECEDES (KW_ZERO+13) +#define KW_N_SEP_BY_SPACE (KW_ZERO+14) +#define KW_P_SIGN_POSN (KW_ZERO+15) +#define KW_N_SIGN_POSN (KW_ZERO+16) +#define KW_INT_P_CS_PRECEDES (KW_ZERO+17) +#define KW_INT_P_SEP_BY_SPACE (KW_ZERO+18) +#define KW_INT_N_CS_PRECEDES (KW_ZERO+19) +#define KW_INT_N_SEP_BY_SPACE (KW_ZERO+20) +#define KW_INT_P_SIGN_POSN (KW_ZERO+21) +#define KW_INT_N_SIGN_POSN (KW_ZERO+22) struct _kwinfo { const char *name; @@ -218,7 +218,7 @@ struct _kwinfo { "(POSIX legacy)" } /* compat */ }; -#define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0])) +#define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0])) const char *boguslocales[] = { "UTF-8" }; #define NBOGUS (sizeof(boguslocales)/sizeof(boguslocales[0])) @@ -253,12 +253,10 @@ main(int argc, char *argv[]) /* validate arguments */ if (all_locales && all_charmaps) usage(); - if ((all_locales || all_charmaps) && argc > 0) + if ((all_locales || all_charmaps) && argc > 0) usage(); if ((all_locales || all_charmaps) && (prt_categories || prt_keywords)) usage(); - if ((prt_categories || prt_keywords) && argc <= 0) - usage(); /* process '-a' */ if (all_locales) { @@ -281,13 +279,19 @@ main(int argc, char *argv[]) exit(0); } - /* process '-c' and/or '-k' */ + /* process '-c', '-k', or command line arguments. */ if (prt_categories || prt_keywords || argc > 0) { - setlocale(LC_ALL, ""); - while (argc > 0) { - showdetails(*argv); - argv++; - argc--; + if (argc > 0) { + setlocale(LC_ALL, ""); + while (argc > 0) { + showdetails(*argv); + argv++; + argc--; + } + } else { + uint i; + for (i = 0; i < sizeof (kwinfo) / sizeof (struct _kwinfo); i++) + showdetails ((char *)kwinfo [i].name); } exit(0); } @@ -302,8 +306,8 @@ void usage(void) { printf("Usage: locale [ -a | -m ]\n" - " locale -k list [prefix]\n" - " locale [ -ck ] keyword ...\n"); + " locale -k list [prefix]\n" + " locale [ -ck ] [keyword ...]\n"); exit(1); } @@ -423,10 +427,10 @@ init_locales_list(void) } closedir(dirp); - /* make sure that 'POSIX' and 'C' locales are present in the list. + /* make sure that 'POSIX' and 'C' locales are present in the list. * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but - * we also list 'C' for constistency - */ + * we also list 'C' for constistency + */ if (sl_find(locales, "POSIX") == NULL) sl_add(locales, "POSIX"); @@ -612,7 +616,10 @@ showdetails(char *kw) } if (prt_categories) { - printf("%s\n", lookup_localecat(cat)); + if (prt_keywords) + printf("%-20s ", lookup_localecat(cat)); + else + printf("%-20s\t%s\n", kw, lookup_localecat(cat)); } if (prt_keywords) { @@ -657,7 +664,7 @@ showkeywordslist(char *substring) { size_t i; -#define FMT "%-20s %-12s %-7s %-20s\n" +#define FMT "%-20s %-12s %-7s %-20s\n" if (substring == NULL) printf("List of available keywords\n\n"); diff --git a/usr.bin/locate/locate/locate.c b/usr.bin/locate/locate/locate.c index b0faefb..3a87b14 100644 --- a/usr.bin/locate/locate/locate.c +++ b/usr.bin/locate/locate/locate.c @@ -292,7 +292,9 @@ search_mmap(db, s) err(1, "`%s'", db); len = sb.st_size; if (len < (2*NBG)) - errx(1, "database too small: %s", db); + errx(1, + "database too small: %s\nRun /usr/libexec/locate.updatedb", + db); if ((p = mmap((caddr_t)0, (size_t)len, PROT_READ, MAP_SHARED, diff --git a/usr.bin/m4/Makefile b/usr.bin/m4/Makefile index ce50d6c..1f1ec5e 100644 --- a/usr.bin/m4/Makefile +++ b/usr.bin/m4/Makefile @@ -6,6 +6,7 @@ PROG= m4 CFLAGS+=-DEXTENDED -I${.CURDIR} -I${.CURDIR}/lib +DPADD= ${LIBY} ${LIBL} ${LIBM} LDADD= -ly -ll -lm # clang needs 1 while with gcc we can use 2 #WARNS= 1 diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c index 8079cc9..f019d52 100644 --- a/usr.bin/make/main.c +++ b/usr.bin/make/main.c @@ -976,7 +976,7 @@ main(int argc, char **argv) */ if ((machine_cpu = getenv("MACHINE_CPU")) == NULL) { if (!strcmp(machine_arch, "i386")) - machine_cpu = "i386"; + machine_cpu = "i486"; else machine_cpu = "unknown"; } diff --git a/usr.bin/man/man.1 b/usr.bin/man/man.1 index d8486bd..4763cc6 100644 --- a/usr.bin/man/man.1 +++ b/usr.bin/man/man.1 @@ -84,7 +84,7 @@ environment variable. .It Fl S Ar mansect Restricts manual sections searched to the specified colon delimited list. Defaults to -.Dq Li 1:1aout:8:2:3:n:4:5:6:7:9:l . +.Dq Li 1:8:2:3:n:4:5:6:7:9:l . Overrides the .Ev MANSECT environment variable. diff --git a/usr.bin/man/man.sh b/usr.bin/man/man.sh index 0725e0b..0f6249a 100755 --- a/usr.bin/man/man.sh +++ b/usr.bin/man/man.sh @@ -945,7 +945,7 @@ STTY=/bin/stty SYSCTL=/sbin/sysctl debug=0 -man_default_sections='1:1aout:8:2:3:n:4:5:6:7:9:l' +man_default_sections='1:8:2:3:n:4:5:6:7:9:l' man_default_path='/usr/share/man:/usr/share/openssl/man:/usr/local/man' cattool='/usr/bin/zcat -f' diff --git a/usr.bin/ministat/ministat.1 b/usr.bin/ministat/ministat.1 index 88d5324..ea31c23 100644 --- a/usr.bin/ministat/ministat.1 +++ b/usr.bin/ministat/ministat.1 @@ -25,7 +25,7 @@ .\" .\" $FreeBSD$ .\" -.Dd June 28, 2010 +.Dd November 10, 2012 .Dt MINISTAT 1 .Os .Sh NAME @@ -33,7 +33,7 @@ .Nd statistics utility .Sh SYNOPSIS .Nm -.Op Fl ns +.Op Fl Ans .Op Fl C Ar column .Op Fl c Ar confidence_level .Op Fl d Ar delimiter @@ -47,6 +47,9 @@ in the specified files or, if no file is specified, standard input. .Pp The options are as follows: .Bl -tag -width Fl +.It Fl A +Just report the statistics of the input and relative comparisons, +suppress the ASCII-art plot. .It Fl n Just report the raw statistics of the input, suppress the ASCII-art plot and the relative comparisons. diff --git a/usr.bin/ministat/ministat.c b/usr.bin/ministat/ministat.c index 3b51e40..218715c 100644 --- a/usr.bin/ministat/ministat.c +++ b/usr.bin/ministat/ministat.c @@ -509,7 +509,7 @@ usage(char const *whine) fprintf(stderr, "%s\n", whine); fprintf(stderr, - "Usage: ministat [-C column] [-c confidence] [-d delimiter(s)] [-ns] [-w width] [file [file ...]]\n"); + "Usage: ministat [-C column] [-c confidence] [-d delimiter(s)] [-Ans] [-w width] [file [file ...]]\n"); fprintf(stderr, "\tconfidence = {"); for (i = 0; i < NCONF; i++) { fprintf(stderr, "%s%g%%", @@ -517,6 +517,7 @@ usage(char const *whine) studentpct[i]); } fprintf(stderr, "}\n"); + fprintf(stderr, "\t-A : print statistics only. suppress the graph.\n"); fprintf(stderr, "\t-C : column number to extract (starts and defaults to 1)\n"); fprintf(stderr, "\t-d : delimiter(s) string, default to \" \\t\"\n"); fprintf(stderr, "\t-n : print summary statistics only, no graph/test\n"); @@ -538,6 +539,7 @@ main(int argc, char **argv) int flag_s = 0; int flag_n = 0; int termwidth = 74; + int suppress_plot = 0; if (isatty(STDOUT_FILENO)) { struct winsize wsz; @@ -550,8 +552,11 @@ main(int argc, char **argv) } ci = -1; - while ((c = getopt(argc, argv, "C:c:d:snw:")) != -1) + while ((c = getopt(argc, argv, "AC:c:d:snw:")) != -1) switch (c) { + case 'A': + suppress_plot = 1; + break; case 'C': column = strtol(optarg, &p, 10); if (p != NULL && *p != '\0') @@ -610,7 +615,7 @@ main(int argc, char **argv) for (i = 0; i < nds; i++) printf("%c %s\n", symbol[i+1], ds[i]->name); - if (!flag_n) { + if (!flag_n && !suppress_plot) { SetupPlot(termwidth, flag_s, nds); for (i = 0; i < nds; i++) DimPlot(ds[i]); diff --git a/usr.bin/mkcsmapper/Makefile.inc b/usr.bin/mkcsmapper/Makefile.inc index ca8a675..e0acc9b 100644 --- a/usr.bin/mkcsmapper/Makefile.inc +++ b/usr.bin/mkcsmapper/Makefile.inc @@ -1,7 +1,11 @@ # $FreeBSD$ +.include <bsd.compiler.mk> + SRCS+= lex.l yacc.y CFLAGS+= -I${.CURDIR} -I${.CURDIR}/../mkcsmapper \ -I${.CURDIR}/../../lib/libc/iconv \ - -I${.CURDIR}/../../lib/libiconv_modules/mapper_std \ - --param max-inline-insns-single=64 + -I${.CURDIR}/../../lib/libiconv_modules/mapper_std +.if ${COMPILER_TYPE} == "gcc" +CFLAGS+= --param max-inline-insns-single=64 +.endif diff --git a/usr.bin/mktemp/mktemp.1 b/usr.bin/mktemp/mktemp.1 index 3ec7585..3a38f84 100644 --- a/usr.bin/mktemp/mktemp.1 +++ b/usr.bin/mktemp/mktemp.1 @@ -158,10 +158,7 @@ Use of this option is not encouraged. .El .Sh EXIT STATUS -The -.Nm -utility -exits 0 on success, and 1 if an error occurs. +.Ex -std .Sh EXAMPLES The following .Xr sh 1 diff --git a/usr.bin/netstat/inet.c b/usr.bin/netstat/inet.c index 9d8c97e..8094e5b 100644 --- a/usr.bin/netstat/inet.c +++ b/usr.bin/netstat/inet.c @@ -699,7 +699,7 @@ tcp_stats(u_long off, const char *name, int af1 __unused, int proto __unused) p(tcps_sc_sendcookie, "\t%lu cookie%s sent\n"); p(tcps_sc_recvcookie, "\t%lu cookie%s received\n"); - p(tcps_hc_added, "\t%lu hostcache entrie%s added\n"); + p3(tcps_hc_added, "\t%lu hostcache entr%s added\n"); p1a(tcps_hc_bucketoverflow, "\t\t%lu bucket overflow\n"); p(tcps_sack_recovery_episode, "\t%lu SACK recovery episode%s\n"); diff --git a/usr.bin/netstat/route.c b/usr.bin/netstat/route.c index 270f6bd..dd91330 100644 --- a/usr.bin/netstat/route.c +++ b/usr.bin/netstat/route.c @@ -637,6 +637,12 @@ fmt_sockaddr(struct sockaddr *sa, struct sockaddr *mask, int flags) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)sa; + /* + * The sa6->sin6_scope_id must be filled here because + * this sockaddr is extracted from kmem(4) directly + * and has KAME-specific embedded scope id in + * sa6->sin6_addr.s6_addr[2]. + */ in6_fillscopeid(sa6); if (flags & RTF_HOST) diff --git a/usr.bin/newgrp/newgrp.1 b/usr.bin/newgrp/newgrp.1 index 44ab9fd..4635584 100644 --- a/usr.bin/newgrp/newgrp.1 +++ b/usr.bin/newgrp/newgrp.1 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd May 23, 2002 +.Dd February 8, 2013 .Dt NEWGRP 1 .Os .Sh NAME @@ -90,6 +90,15 @@ A utility appeared in .At v6 . .Sh BUGS +For security reasons, the +.Nm +utility is normally installed without the setuid bit. +To enable it, run the following command: +.Bd -literal -offset indent +chmod u+s /usr/bin/newgrp +.Ed +.Pp Group passwords are inherently insecure as there is no way to stop -users obtaining the crypted passwords from the group database. +users obtaining the password hash from the group database. Their use is discouraged. +Instead, users should simply be added to the necessary groups. diff --git a/usr.bin/newgrp/newgrp.c b/usr.bin/newgrp/newgrp.c index b3f6103..9b3972e 100644 --- a/usr.bin/newgrp/newgrp.c +++ b/usr.bin/newgrp/newgrp.c @@ -73,7 +73,8 @@ main(int argc, char *argv[]) { int ch, login; - euid = geteuid(); + if ((euid = geteuid()) != 0) + warnx("need root permissions to function properly, check setuid bit"); if (seteuid(getuid()) < 0) err(1, "seteuid"); diff --git a/usr.bin/nfsstat/nfsstat.1 b/usr.bin/nfsstat/nfsstat.1 index 14dc782..2b0340d 100644 --- a/usr.bin/nfsstat/nfsstat.1 +++ b/usr.bin/nfsstat/nfsstat.1 @@ -28,7 +28,7 @@ .\" From: @(#)nfsstat.1 8.1 (Berkeley) 6/6/93 .\" $FreeBSD$ .\" -.Dd May 4, 2011 +.Dd November 14, 2012 .Dt NFSSTAT 1 .Os .Sh NAME @@ -38,7 +38,7 @@ statistics .Sh SYNOPSIS .Nm -.Op Fl ceoszW +.Op Fl cemoszW .Op Fl M Ar core .Op Fl N Ar system .Op Fl w Ar wait @@ -62,6 +62,12 @@ This option is incompatible with Extract values associated with the name list from the specified core instead of the default .Pa /dev/kmem . +.It Fl m +Report the mount options for all new NFS client mounts. +This option overrides all others and +.Nm +will exit after completing the report. +This option is only supported by the new NFS client. .It Fl N Extract the name list from the specified system instead of the default .Pa /boot/kernel/kernel . diff --git a/usr.bin/nfsstat/nfsstat.c b/usr.bin/nfsstat/nfsstat.c index efbaed5..4a3228a 100644 --- a/usr.bin/nfsstat/nfsstat.c +++ b/usr.bin/nfsstat/nfsstat.c @@ -107,14 +107,36 @@ main(int argc, char **argv) int ch; char *memf, *nlistf; char errbuf[_POSIX2_LINE_MAX]; + int mntlen, i; + char buf[1024]; + struct statfs *mntbuf; + struct nfscl_dumpmntopts dumpmntopts; interval = 0; memf = nlistf = NULL; - while ((ch = getopt(argc, argv, "cesWM:N:ow:z")) != -1) + while ((ch = getopt(argc, argv, "cesWM:mN:ow:z")) != -1) switch(ch) { case 'M': memf = optarg; break; + case 'm': + /* Display mount options for NFS mount points. */ + mntlen = getmntinfo(&mntbuf, MNT_NOWAIT); + for (i = 0; i < mntlen; i++) { + if (strcmp(mntbuf->f_fstypename, "nfs") == 0) { + dumpmntopts.ndmnt_fname = + mntbuf->f_mntonname; + dumpmntopts.ndmnt_buf = buf; + dumpmntopts.ndmnt_blen = sizeof(buf); + if (nfssvc(NFSSVC_DUMPMNTOPTS, + &dumpmntopts) >= 0) + printf("%s on %s\n%s\n", + mntbuf->f_mntfromname, + mntbuf->f_mntonname, buf); + } + mntbuf++; + } + exit(0); case 'N': nlistf = optarg; break; @@ -646,7 +668,7 @@ void usage(void) { (void)fprintf(stderr, - "usage: nfsstat [-ceoszW] [-M core] [-N system] [-w wait]\n"); + "usage: nfsstat [-cemoszW] [-M core] [-N system] [-w wait]\n"); exit(1); } diff --git a/usr.bin/passwd/Makefile b/usr.bin/passwd/Makefile index 7aeee56..d657c80 100644 --- a/usr.bin/passwd/Makefile +++ b/usr.bin/passwd/Makefile @@ -19,7 +19,9 @@ beforeinstall: chflags noschg ${DESTDIR}${BINDIR}/$i || true .endfor +.if !defined(NO_FSCHG) afterinstall: -chflags schg ${DESTDIR}${BINDIR}/passwd +.endif .include <bsd.prog.mk> diff --git a/usr.bin/patch/Makefile b/usr.bin/patch/Makefile new file mode 100644 index 0000000..382a05f --- /dev/null +++ b/usr.bin/patch/Makefile @@ -0,0 +1,18 @@ +# $OpenBSD: Makefile,v 1.4 2005/05/16 15:22:46 espie Exp $ +# $FreeBSD$ + +.include <bsd.own.mk> + +.if ${MK_BSD_PATCH} == "yes" +PROG= patch +.else +PROG= bsdpatch +CLEANFILES+= bsdpatch.1 + +bsdpatch.1: patch.1 + cp ${.ALLSRC} ${.TARGET} +.endif + +SRCS= backupfile.c inp.c mkpath.c patch.c pch.c util.c + +.include <bsd.prog.mk> diff --git a/usr.bin/patch/backupfile.c b/usr.bin/patch/backupfile.c new file mode 100644 index 0000000..d22d607 --- /dev/null +++ b/usr.bin/patch/backupfile.c @@ -0,0 +1,246 @@ +/*- + * Copyright (C) 1990 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * without restriction. + * + * 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. + * + * backupfile.c -- make Emacs style backup file names + * + * David MacKenzie <djm@ai.mit.edu>. Some algorithms adapted from GNU Emacs. + * + * $OpenBSD: backupfile.c,v 1.20 2009/10/27 23:59:41 deraadt Exp $ + * $FreeBSD$ + */ + +#include <ctype.h> +#include <dirent.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "backupfile.h" + + +#define ISDIGIT(c) (isascii ((unsigned char)c) && isdigit ((unsigned char)c)) + +/* Which type of backup file names are generated. */ +enum backup_type backup_type = none; + +/* + * The extension added to file names to produce a simple (as opposed to + * numbered) backup file name. + */ +const char *simple_backup_suffix = "~"; + +static char *concat(const char *, const char *); +static char *make_version_name(const char *, int); +static int max_backup_version(const char *, const char *); +static int version_number(const char *, const char *, size_t); +static int argmatch(const char *, const char **); +static void invalid_arg(const char *, const char *, int); + +/* + * Return the name of the new backup file for file FILE, allocated with + * malloc. Return 0 if out of memory. FILE must not end with a '/' unless it + * is the root directory. Do not call this function if backup_type == none. + */ +char * +find_backup_file_name(const char *file) +{ + char *dir, *base_versions, *tmp_file; + int highest_backup; + + if (backup_type == simple) + return concat(file, simple_backup_suffix); + tmp_file = strdup(file); + if (tmp_file == NULL) + return NULL; + base_versions = concat(basename(tmp_file), ".~"); + free(tmp_file); + if (base_versions == NULL) + return NULL; + tmp_file = strdup(file); + if (tmp_file == NULL) { + free(base_versions); + return NULL; + } + dir = dirname(tmp_file); + if (dir == NULL) { + free(base_versions); + free(tmp_file); + return NULL; + } + highest_backup = max_backup_version(base_versions, dir); + free(base_versions); + free(tmp_file); + if (backup_type == numbered_existing && highest_backup == 0) + return concat(file, simple_backup_suffix); + return make_version_name(file, highest_backup + 1); +} + +/* + * Return the number of the highest-numbered backup file for file FILE in + * directory DIR. If there are no numbered backups of FILE in DIR, or an + * error occurs reading DIR, return 0. FILE should already have ".~" appended + * to it. + */ +static int +max_backup_version(const char *file, const char *dir) +{ + DIR *dirp; + struct dirent *dp; + int highest_version, this_version; + size_t file_name_length; + + dirp = opendir(dir); + if (dirp == NULL) + return 0; + + highest_version = 0; + file_name_length = strlen(file); + + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_namlen <= file_name_length) + continue; + + this_version = version_number(file, dp->d_name, file_name_length); + if (this_version > highest_version) + highest_version = this_version; + } + closedir(dirp); + return highest_version; +} + +/* + * Return a string, allocated with malloc, containing "FILE.~VERSION~". + * Return 0 if out of memory. + */ +static char * +make_version_name(const char *file, int version) +{ + char *backup_name; + + if (asprintf(&backup_name, "%s.~%d~", file, version) == -1) + return NULL; + return backup_name; +} + +/* + * If BACKUP is a numbered backup of BASE, return its version number; + * otherwise return 0. BASE_LENGTH is the length of BASE. BASE should + * already have ".~" appended to it. + */ +static int +version_number(const char *base, const char *backup, size_t base_length) +{ + int version; + const char *p; + + version = 0; + if (!strncmp(base, backup, base_length) && ISDIGIT(backup[base_length])) { + for (p = &backup[base_length]; ISDIGIT(*p); ++p) + version = version * 10 + *p - '0'; + if (p[0] != '~' || p[1]) + version = 0; + } + return version; +} + +/* + * Return the newly-allocated concatenation of STR1 and STR2. If out of + * memory, return 0. + */ +static char * +concat(const char *str1, const char *str2) +{ + char *newstr; + + if (asprintf(&newstr, "%s%s", str1, str2) == -1) + return NULL; + return newstr; +} + +/* + * If ARG is an unambiguous match for an element of the null-terminated array + * OPTLIST, return the index in OPTLIST of the matched element, else -1 if it + * does not match any element or -2 if it is ambiguous (is a prefix of more + * than one element). + */ +static int +argmatch(const char *arg, const char **optlist) +{ + int i; /* Temporary index in OPTLIST. */ + size_t arglen; /* Length of ARG. */ + int matchind = -1; /* Index of first nonexact match. */ + int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ + + arglen = strlen(arg); + + /* Test all elements for either exact match or abbreviated matches. */ + for (i = 0; optlist[i]; i++) { + if (!strncmp(optlist[i], arg, arglen)) { + if (strlen(optlist[i]) == arglen) + /* Exact match found. */ + return i; + else if (matchind == -1) + /* First nonexact match found. */ + matchind = i; + else + /* Second nonexact match found. */ + ambiguous = 1; + } + } + if (ambiguous) + return -2; + else + return matchind; +} + +/* + * Error reporting for argmatch. KIND is a description of the type of entity + * that was being matched. VALUE is the invalid value that was given. PROBLEM + * is the return value from argmatch. + */ +static void +invalid_arg(const char *kind, const char *value, int problem) +{ + fprintf(stderr, "patch: "); + if (problem == -1) + fprintf(stderr, "invalid"); + else /* Assume -2. */ + fprintf(stderr, "ambiguous"); + fprintf(stderr, " %s `%s'\n", kind, value); +} + +static const char *backup_args[] = { + "never", "simple", "nil", "existing", "t", "numbered", 0 +}; + +static enum backup_type backup_types[] = { + simple, simple, numbered_existing, + numbered_existing, numbered, numbered +}; + +/* + * Return the type of backup indicated by VERSION. Unique abbreviations are + * accepted. + */ +enum backup_type +get_version(const char *version) +{ + int i; + + if (version == NULL || *version == '\0') + return numbered_existing; + i = argmatch(version, backup_args); + if (i >= 0) + return backup_types[i]; + invalid_arg("version control type", version, i); + exit(2); +} diff --git a/usr.bin/patch/backupfile.h b/usr.bin/patch/backupfile.h new file mode 100644 index 0000000..71999f7 --- /dev/null +++ b/usr.bin/patch/backupfile.h @@ -0,0 +1,39 @@ +/*- + * Copyright (C) 1990 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * without restriction. + * + * 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. + * + * backupfile.h -- declarations for making Emacs style backup file names + * + * $OpenBSD: backupfile.h,v 1.6 2003/07/28 18:35:36 otto Exp $ + * $FreeBSD$ + */ + +/* When to make backup files. */ +enum backup_type { + /* Never make backups. */ + none, + + /* Make simple backups of every file. */ + simple, + + /* + * Make numbered backups of files that already have numbered backups, + * and simple backups of the others. + */ + numbered_existing, + + /* Make numbered backups of every file. */ + numbered +}; + +extern enum backup_type backup_type; +extern const char *simple_backup_suffix; + +char *find_backup_file_name(const char *file); +enum backup_type get_version(const char *version); diff --git a/usr.bin/patch/common.h b/usr.bin/patch/common.h new file mode 100644 index 0000000..9425968 --- /dev/null +++ b/usr.bin/patch/common.h @@ -0,0 +1,119 @@ +/*- + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * patch - a program to apply diffs to original files + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + * + * $OpenBSD: common.h,v 1.26 2006/03/11 19:41:30 otto Exp $ + * $FreeBSD$ + */ + +#include <sys/types.h> + +#include <stdbool.h> +#include <stdint.h> + +#define DEBUGGING + +/* constants */ + +#define MAXHUNKSIZE 200000 /* is this enough lines? */ +#define INITHUNKMAX 125 /* initial dynamic allocation size */ +#define INITLINELEN 4096 +#define BUFFERSIZE 4096 + +#define SCCSPREFIX "s." +#define GET "get -e %s" +#define SCCSDIFF "get -p %s | diff - %s >/dev/null" + +#define RCSSUFFIX ",v" +#define CHECKOUT "co -l %s" +#define RCSDIFF "rcsdiff %s > /dev/null" + +#define ORIGEXT ".orig" +#define REJEXT ".rej" + +/* handy definitions */ + +#define strNE(s1,s2) (strcmp(s1, s2)) +#define strEQ(s1,s2) (!strcmp(s1, s2)) +#define strnNE(s1,s2,l) (strncmp(s1, s2, l)) +#define strnEQ(s1,s2,l) (!strncmp(s1, s2, l)) + +/* typedefs */ + +typedef long LINENUM; /* must be signed */ + +/* globals */ + +extern mode_t filemode; + +extern char *buf; /* general purpose buffer */ +extern size_t buf_size; /* size of general purpose buffer */ + +extern bool using_plan_a; /* try to keep everything in memory */ +extern bool out_of_mem; /* ran out of memory in plan a */ + +#define MAXFILEC 2 + +extern char *filearg[MAXFILEC]; +extern bool ok_to_create_file; +extern char *outname; +extern char *origprae; + +extern char *TMPOUTNAME; +extern char *TMPINNAME; +extern char *TMPREJNAME; +extern char *TMPPATNAME; +extern bool toutkeep; +extern bool trejkeep; + +#ifdef DEBUGGING +extern int debug; +#endif + +extern bool force; +extern bool batch; +extern bool verbose; +extern bool reverse; +extern bool noreverse; +extern bool skip_rest_of_patch; +extern int strippath; +extern bool canonicalize; +/* TRUE if -C was specified on command line. */ +extern bool check_only; +extern bool warn_on_invalid_line; +extern bool last_line_missing_eol; + + +#define CONTEXT_DIFF 1 +#define NORMAL_DIFF 2 +#define ED_DIFF 3 +#define NEW_CONTEXT_DIFF 4 +#define UNI_DIFF 5 + +extern int diff_type; +extern char *revision; /* prerequisite revision, if any */ +extern LINENUM input_lines; /* how long is input file in lines */ + +extern int posix; + diff --git a/usr.bin/patch/inp.c b/usr.bin/patch/inp.c new file mode 100644 index 0000000..54b0bf2 --- /dev/null +++ b/usr.bin/patch/inp.c @@ -0,0 +1,484 @@ +/*- + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * patch - a program to apply diffs to original files + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + * + * $OpenBSD: inp.c,v 1.36 2012/04/10 14:46:34 ajacoutot Exp $ + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include <ctype.h> +#include <libgen.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" +#include "util.h" +#include "pch.h" +#include "inp.h" + + +/* Input-file-with-indexable-lines abstract type */ + +static size_t i_size; /* size of the input file */ +static char *i_womp; /* plan a buffer for entire file */ +static char **i_ptr; /* pointers to lines in i_womp */ +static char empty_line[] = { '\0' }; + +static int tifd = -1; /* plan b virtual string array */ +static char *tibuf[2]; /* plan b buffers */ +static LINENUM tiline[2] = {-1, -1}; /* 1st line in each buffer */ +static LINENUM lines_per_buf; /* how many lines per buffer */ +static int tireclen; /* length of records in tmp file */ + +static bool rev_in_string(const char *); +static bool reallocate_lines(size_t *); + +/* returns false if insufficient memory */ +static bool plan_a(const char *); + +static void plan_b(const char *); + +/* New patch--prepare to edit another file. */ + +void +re_input(void) +{ + if (using_plan_a) { + free(i_ptr); + i_ptr = NULL; + if (i_womp != NULL) { + munmap(i_womp, i_size); + i_womp = NULL; + } + i_size = 0; + } else { + using_plan_a = true; /* maybe the next one is smaller */ + close(tifd); + tifd = -1; + free(tibuf[0]); + free(tibuf[1]); + tibuf[0] = tibuf[1] = NULL; + tiline[0] = tiline[1] = -1; + tireclen = 0; + } +} + +/* Construct the line index, somehow or other. */ + +void +scan_input(const char *filename) +{ + if (!plan_a(filename)) + plan_b(filename); + if (verbose) { + say("Patching file %s using Plan %s...\n", filename, + (using_plan_a ? "A" : "B")); + } +} + +static bool +reallocate_lines(size_t *lines_allocated) +{ + char **p; + size_t new_size; + + new_size = *lines_allocated * 3 / 2; + p = realloc(i_ptr, (new_size + 2) * sizeof(char *)); + if (p == NULL) { /* shucks, it was a near thing */ + munmap(i_womp, i_size); + i_womp = NULL; + free(i_ptr); + i_ptr = NULL; + *lines_allocated = 0; + return false; + } + *lines_allocated = new_size; + i_ptr = p; + return true; +} + +/* Try keeping everything in memory. */ + +static bool +plan_a(const char *filename) +{ + int ifd, statfailed; + char *p, *s, lbuf[INITLINELEN]; + struct stat filestat; + ptrdiff_t sz; + size_t i; + size_t iline, lines_allocated; + +#ifdef DEBUGGING + if (debug & 8) + return false; +#endif + + if (filename == NULL || *filename == '\0') + return false; + + statfailed = stat(filename, &filestat); + if (statfailed && ok_to_create_file) { + if (verbose) + say("(Creating file %s...)\n", filename); + + /* + * in check_patch case, we still display `Creating file' even + * though we're not. The rule is that -C should be as similar + * to normal patch behavior as possible + */ + if (check_only) + return true; + makedirs(filename, true); + close(creat(filename, 0666)); + statfailed = stat(filename, &filestat); + } + if (statfailed && check_only) + fatal("%s not found, -C mode, can't probe further\n", filename); + /* For nonexistent or read-only files, look for RCS or SCCS versions. */ + if (statfailed || + /* No one can write to it. */ + (filestat.st_mode & 0222) == 0 || + /* I can't write to it. */ + ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) { + const char *cs = NULL, *filebase, *filedir; + struct stat cstat; + char *tmp_filename1, *tmp_filename2; + + tmp_filename1 = strdup(filename); + tmp_filename2 = strdup(filename); + if (tmp_filename1 == NULL || tmp_filename2 == NULL) + fatal("strdupping filename"); + filebase = basename(tmp_filename1); + filedir = dirname(tmp_filename2); + + /* Leave room in lbuf for the diff command. */ + s = lbuf + 20; + +#define try(f, a1, a2, a3) \ + (snprintf(s, buf_size - 20, f, a1, a2, a3), stat(s, &cstat) == 0) + + if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || + try("%s/RCS/%s%s", filedir, filebase, "") || + try("%s/%s%s", filedir, filebase, RCSSUFFIX)) { + snprintf(buf, buf_size, CHECKOUT, filename); + snprintf(lbuf, sizeof lbuf, RCSDIFF, filename); + cs = "RCS"; + } else if (try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) || + try("%s/%s%s", filedir, SCCSPREFIX, filebase)) { + snprintf(buf, buf_size, GET, s); + snprintf(lbuf, sizeof lbuf, SCCSDIFF, s, filename); + cs = "SCCS"; + } else if (statfailed) + fatal("can't find %s\n", filename); + + free(tmp_filename1); + free(tmp_filename2); + + /* + * else we can't write to it but it's not under a version + * control system, so just proceed. + */ + if (cs) { + if (!statfailed) { + if ((filestat.st_mode & 0222) != 0) + /* The owner can write to it. */ + fatal("file %s seems to be locked " + "by somebody else under %s\n", + filename, cs); + /* + * It might be checked out unlocked. See if + * it's safe to check out the default version + * locked. + */ + if (verbose) + say("Comparing file %s to default " + "%s version...\n", + filename, cs); + if (system(lbuf)) + fatal("can't check out file %s: " + "differs from default %s version\n", + filename, cs); + } + if (verbose) + say("Checking out file %s from %s...\n", + filename, cs); + if (system(buf) || stat(filename, &filestat)) + fatal("can't check out file %s from %s\n", + filename, cs); + } + } + filemode = filestat.st_mode; + if (!S_ISREG(filemode)) + fatal("%s is not a normal file--can't patch\n", filename); + if ((uint64_t)filestat.st_size > SIZE_MAX) { + say("block too large to mmap\n"); + return false; + } + i_size = (size_t)filestat.st_size; + if (out_of_mem) { + set_hunkmax(); /* make sure dynamic arrays are allocated */ + out_of_mem = false; + return false; /* force plan b because plan a bombed */ + } + if ((ifd = open(filename, O_RDONLY)) < 0) + pfatal("can't open file %s", filename); + + if (i_size) { + i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0); + if (i_womp == MAP_FAILED) { + perror("mmap failed"); + i_womp = NULL; + close(ifd); + return false; + } + } else { + i_womp = NULL; + } + + close(ifd); + if (i_size) + madvise(i_womp, i_size, MADV_SEQUENTIAL); + + /* estimate the number of lines */ + lines_allocated = i_size / 25; + if (lines_allocated < 100) + lines_allocated = 100; + + if (!reallocate_lines(&lines_allocated)) + return false; + + /* now scan the buffer and build pointer array */ + iline = 1; + i_ptr[iline] = i_womp; + /* test for NUL too, to maintain the behavior of the original code */ + for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) { + if (*s == '\n') { + if (iline == lines_allocated) { + if (!reallocate_lines(&lines_allocated)) + return false; + } + /* these are NOT NUL terminated */ + i_ptr[++iline] = s + 1; + } + } + /* if the last line contains no EOL, append one */ + if (i_size > 0 && i_womp[i_size - 1] != '\n') { + last_line_missing_eol = true; + /* fix last line */ + sz = s - i_ptr[iline]; + p = malloc(sz + 1); + if (p == NULL) { + free(i_ptr); + i_ptr = NULL; + munmap(i_womp, i_size); + i_womp = NULL; + return false; + } + + memcpy(p, i_ptr[iline], sz); + p[sz] = '\n'; + i_ptr[iline] = p; + /* count the extra line and make it point to some valid mem */ + i_ptr[++iline] = empty_line; + } else + last_line_missing_eol = false; + + input_lines = iline - 1; + + /* now check for revision, if any */ + + if (revision != NULL) { + if (!rev_in_string(i_womp)) { + if (force) { + if (verbose) + say("Warning: this file doesn't appear " + "to be the %s version--patching anyway.\n", + revision); + } else if (batch) { + fatal("this file doesn't appear to be the " + "%s version--aborting.\n", + revision); + } else { + ask("This file doesn't appear to be the " + "%s version--patch anyway? [n] ", + revision); + if (*buf != 'y') + fatal("aborted\n"); + } + } else if (verbose) + say("Good. This file appears to be the %s version.\n", + revision); + } + return true; /* plan a will work */ +} + +/* Keep (virtually) nothing in memory. */ + +static void +plan_b(const char *filename) +{ + FILE *ifp; + size_t i = 0, j, maxlen = 1; + char *p; + bool found_revision = (revision == NULL); + + using_plan_a = false; + if ((ifp = fopen(filename, "r")) == NULL) + pfatal("can't open file %s", filename); + unlink(TMPINNAME); + if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0) + pfatal("can't open file %s", TMPINNAME); + while (fgets(buf, buf_size, ifp) != NULL) { + if (revision != NULL && !found_revision && rev_in_string(buf)) + found_revision = true; + if ((i = strlen(buf)) > maxlen) + maxlen = i; /* find longest line */ + } + last_line_missing_eol = i > 0 && buf[i - 1] != '\n'; + if (last_line_missing_eol && maxlen == i) + maxlen++; + + if (revision != NULL) { + if (!found_revision) { + if (force) { + if (verbose) + say("Warning: this file doesn't appear " + "to be the %s version--patching anyway.\n", + revision); + } else if (batch) { + fatal("this file doesn't appear to be the " + "%s version--aborting.\n", + revision); + } else { + ask("This file doesn't appear to be the %s " + "version--patch anyway? [n] ", + revision); + if (*buf != 'y') + fatal("aborted\n"); + } + } else if (verbose) + say("Good. This file appears to be the %s version.\n", + revision); + } + fseek(ifp, 0L, SEEK_SET); /* rewind file */ + lines_per_buf = BUFFERSIZE / maxlen; + tireclen = maxlen; + tibuf[0] = malloc(BUFFERSIZE + 1); + if (tibuf[0] == NULL) + fatal("out of memory\n"); + tibuf[1] = malloc(BUFFERSIZE + 1); + if (tibuf[1] == NULL) + fatal("out of memory\n"); + for (i = 1;; i++) { + p = tibuf[0] + maxlen * (i % lines_per_buf); + if (i % lines_per_buf == 0) /* new block */ + if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) + pfatal("can't write temp file"); + if (fgets(p, maxlen + 1, ifp) == NULL) { + input_lines = i - 1; + if (i % lines_per_buf != 0) + if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) + pfatal("can't write temp file"); + break; + } + j = strlen(p); + /* These are '\n' terminated strings, so no need to add a NUL */ + if (j == 0 || p[j - 1] != '\n') + p[j] = '\n'; + } + fclose(ifp); + close(tifd); + if ((tifd = open(TMPINNAME, O_RDONLY)) < 0) + pfatal("can't reopen file %s", TMPINNAME); +} + +/* + * Fetch a line from the input file, \n terminated, not necessarily \0. + */ +char * +ifetch(LINENUM line, int whichbuf) +{ + if (line < 1 || line > input_lines) { + if (warn_on_invalid_line) { + say("No such line %ld in input file, ignoring\n", line); + warn_on_invalid_line = false; + } + return NULL; + } + if (using_plan_a) + return i_ptr[line]; + else { + LINENUM offline = line % lines_per_buf; + LINENUM baseline = line - offline; + + if (tiline[0] == baseline) + whichbuf = 0; + else if (tiline[1] == baseline) + whichbuf = 1; + else { + tiline[whichbuf] = baseline; + + if (lseek(tifd, (off_t) (baseline / lines_per_buf * + BUFFERSIZE), SEEK_SET) < 0) + pfatal("cannot seek in the temporary input file"); + + if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0) + pfatal("error reading tmp file %s", TMPINNAME); + } + return tibuf[whichbuf] + (tireclen * offline); + } +} + +/* + * True if the string argument contains the revision number we want. + */ +static bool +rev_in_string(const char *string) +{ + const char *s; + size_t patlen; + + if (revision == NULL) + return true; + patlen = strlen(revision); + if (strnEQ(string, revision, patlen) && isspace((unsigned char)string[patlen])) + return true; + for (s = string; *s; s++) { + if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) && + isspace((unsigned char)s[patlen + 1])) { + return true; + } + } + return false; +} diff --git a/usr.bin/patch/inp.h b/usr.bin/patch/inp.h new file mode 100644 index 0000000..062798a --- /dev/null +++ b/usr.bin/patch/inp.h @@ -0,0 +1,32 @@ +/*- + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * patch - a program to apply diffs to original files + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + * + * $OpenBSD: inp.h,v 1.8 2003/08/15 08:00:51 otto Exp $ + * $FreeBSD$ + */ + +void re_input(void); +void scan_input(const char *); +char *ifetch(LINENUM, int); diff --git a/usr.bin/patch/mkpath.c b/usr.bin/patch/mkpath.c new file mode 100644 index 0000000..04fd537 --- /dev/null +++ b/usr.bin/patch/mkpath.c @@ -0,0 +1,78 @@ +/*- + * Copyright (c) 1983, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $OpenBSD: mkpath.c,v 1.2 2005/06/20 07:14:06 otto Exp $ + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <err.h> +#include <errno.h> +#include <string.h> + +int mkpath(char *); + +/* Code taken directly from mkdir(1). + + * mkpath -- create directories. + * path - path + */ +int +mkpath(char *path) +{ + struct stat sb; + char *slash; + int done = 0; + + slash = path; + + while (!done) { + slash += strspn(slash, "/"); + slash += strcspn(slash, "/"); + + done = (*slash == '\0'); + *slash = '\0'; + + if (stat(path, &sb)) { + if (errno != ENOENT || (mkdir(path, 0777) && + errno != EEXIST)) { + warn("%s", path); + return (-1); + } + } else if (!S_ISDIR(sb.st_mode)) { + warnx("%s: %s", path, strerror(ENOTDIR)); + return (-1); + } + + *slash = '/'; + } + + return (0); +} + diff --git a/usr.bin/patch/patch.1 b/usr.bin/patch/patch.1 new file mode 100644 index 0000000..312b8a2 --- /dev/null +++ b/usr.bin/patch/patch.1 @@ -0,0 +1,700 @@ +.\"- +.\" Copyright 1986, Larry Wall +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following condition +.\" is met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this condition and the following disclaimer. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $OpenBSD: patch.1,v 1.26 2010/09/03 11:09:29 jmc Exp $ +.\" $FreeBSD$ +.Dd January 29, 2013 +.Dt PATCH 1 +.Os +.Sh NAME +.Nm patch +.Nd apply a diff file to an original +.Sh SYNOPSIS +.Nm +.Bk -words +.Op Fl bCcEeflNnRstuv +.Op Fl B Ar backup-prefix +.Op Fl D Ar symbol +.Op Fl d Ar directory +.Op Fl F Ar max-fuzz +.Op Fl i Ar patchfile +.Op Fl o Ar out-file +.Op Fl p Ar strip-count +.Op Fl r Ar rej-name +.Op Fl V Cm t | nil | never +.Op Fl x Ar number +.Op Fl z Ar backup-ext +.Op Fl Fl posix +.Op Ar origfile Op Ar patchfile +.Ek +.Nm +.Pf \*(Lt Ar patchfile +.Sh DESCRIPTION +.Nm +will take a patch file containing any of the four forms of difference +listing produced by the +.Xr diff 1 +program and apply those differences to an original file, +producing a patched version. +If +.Ar patchfile +is omitted, or is a hyphen, the patch will be read from the standard input. +.Pp +.Nm +will attempt to determine the type of the diff listing, unless overruled by a +.Fl c , +.Fl e , +.Fl n , +or +.Fl u +option. +Context diffs (old-style, new-style, and unified) and +normal diffs are applied directly by the +.Nm +program itself, whereas ed diffs are simply fed to the +.Xr ed 1 +editor via a pipe. +.Pp +If the +.Ar patchfile +contains more than one patch, +.Nm +will try to apply each of them as if they came from separate patch files. +This means, among other things, that it is assumed that the name of the file +to patch must be determined for each diff listing, and that the garbage before +each diff listing will be examined for interesting things such as file names +and revision level (see the section on +.Sx Filename Determination +below). +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Xo +.Fl B Ar backup-prefix , +.Fl Fl prefix Ar backup-prefix +.Xc +Causes the next argument to be interpreted as a prefix to the backup file +name. +If this argument is specified, any argument to +.Fl z +will be ignored. +.It Fl b , Fl Fl backup +Save a backup copy of the file before it is modified. +By default the original file is saved with a backup extension of +.Qq .orig +unless the file already has a numbered backup, in which case a numbered +backup is made. +This is equivalent to specifying +.Qo Fl V Cm existing Qc . +This option is currently the default, unless +.Fl -posix +is specified. +.It Fl C , Fl Fl check +Checks that the patch would apply cleanly, but does not modify anything. +.It Fl c , Fl Fl context +Forces +.Nm +to interpret the patch file as a context diff. +.It Xo +.Fl D Ar symbol , +.Fl Fl ifdef Ar symbol +.Xc +Causes +.Nm +to use the +.Qq #ifdef...#endif +construct to mark changes. +The argument following will be used as the differentiating symbol. +Note that, unlike the C compiler, there must be a space between the +.Fl D +and the argument. +.It Xo +.Fl d Ar directory , +.Fl Fl directory Ar directory +.Xc +Causes +.Nm +to interpret the next argument as a directory, +and change the working directory to it before doing anything else. +.It Fl E , Fl Fl remove-empty-files +Causes +.Nm +to remove output files that are empty after the patches have been applied. +This option is useful when applying patches that create or remove files. +.It Fl e , Fl Fl ed +Forces +.Nm +to interpret the patch file as an +.Xr ed 1 +script. +.It Xo +.Fl F Ar max-fuzz , +.Fl Fl fuzz Ar max-fuzz +.Xc +Sets the maximum fuzz factor. +This option only applies to context diffs, and causes +.Nm +to ignore up to that many lines in looking for places to install a hunk. +Note that a larger fuzz factor increases the odds of a faulty patch. +The default fuzz factor is 2, and it may not be set to more than +the number of lines of context in the context diff, ordinarily 3. +.It Fl f , Fl Fl force +Forces +.Nm +to assume that the user knows exactly what he or she is doing, and to not +ask any questions. +It assumes the following: +skip patches for which a file to patch can't be found; +patch files even though they have the wrong version for the +.Qq Prereq: +line in the patch; +and assume that patches are not reversed even if they look like they are. +This option does not suppress commentary; use +.Fl s +for that. +.It Xo +.Fl i Ar patchfile , +.Fl Fl input Ar patchfile +.Xc +Causes the next argument to be interpreted as the input file name +(i.e. a patchfile). +This option may be specified multiple times. +.It Fl l , Fl Fl ignore-whitespace +Causes the pattern matching to be done loosely, in case the tabs and +spaces have been munged in your input file. +Any sequence of whitespace in the pattern line will match any sequence +in the input file. +Normal characters must still match exactly. +Each line of the context must still match a line in the input file. +.It Fl N , Fl Fl forward +Causes +.Nm +to ignore patches that it thinks are reversed or already applied. +See also +.Fl R . +.It Fl n , Fl Fl normal +Forces +.Nm +to interpret the patch file as a normal diff. +.It Xo +.Fl o Ar out-file , +.Fl Fl output Ar out-file +.Xc +Causes the next argument to be interpreted as the output file name. +.It Xo +.Fl p Ar strip-count , +.Fl Fl strip Ar strip-count +.Xc +Sets the pathname strip count, +which controls how pathnames found in the patch file are treated, +in case you keep your files in a different directory than the person who sent +out the patch. +The strip count specifies how many slashes are to be stripped from +the front of the pathname. +(Any intervening directory names also go away.) +For example, supposing the file name in the patch file was +.Pa /u/howard/src/blurfl/blurfl.c : +.Pp +Setting +.Fl p Ns Ar 0 +gives the entire pathname unmodified. +.Pp +.Fl p Ns Ar 1 +gives +.Pp +.D1 Pa u/howard/src/blurfl/blurfl.c +.Pp +without the leading slash. +.Pp +.Fl p Ns Ar 4 +gives +.Pp +.D1 Pa blurfl/blurfl.c +.Pp +Not specifying +.Fl p +at all just gives you +.Pa blurfl.c , +unless all of the directories in the leading path +.Pq Pa u/howard/src/blurfl +exist and that path is relative, +in which case you get the entire pathname unmodified. +Whatever you end up with is looked for either in the current directory, +or the directory specified by the +.Fl d +option. +.It Fl R , Fl Fl reverse +Tells +.Nm +that this patch was created with the old and new files swapped. +(Yes, I'm afraid that does happen occasionally, human nature being what it +is.) +.Nm +will attempt to swap each hunk around before applying it. +Rejects will come out in the swapped format. +The +.Fl R +option will not work with ed diff scripts because there is too little +information to reconstruct the reverse operation. +.Pp +If the first hunk of a patch fails, +.Nm +will reverse the hunk to see if it can be applied that way. +If it can, you will be asked if you want to have the +.Fl R +option set. +If it can't, the patch will continue to be applied normally. +(Note: this method cannot detect a reversed patch if it is a normal diff +and if the first command is an append (i.e. it should have been a delete) +since appends always succeed, due to the fact that a null context will match +anywhere. +Luckily, most patches add or change lines rather than delete them, so most +reversed normal diffs will begin with a delete, which will fail, triggering +the heuristic.) +.It Xo +.Fl r Ar rej-name , +.Fl Fl reject-file Ar rej-name +.Xc +Causes the next argument to be interpreted as the reject file name. +.It Xo +.Fl s , Fl Fl quiet , +.Fl Fl silent +.Xc +Makes +.Nm +do its work silently, unless an error occurs. +.It Fl t , Fl Fl batch +Similar to +.Fl f , +in that it suppresses questions, but makes some different assumptions: +skip patches for which a file to patch can't be found (the same as +.Fl f ) ; +skip patches for which the file has the wrong version for the +.Qq Prereq: +line in the patch; +and assume that patches are reversed if they look like they are. +.It Fl u , Fl Fl unified +Forces +.Nm +to interpret the patch file as a unified context diff (a unidiff). +.It Xo +.Fl V Cm t | nil | never , +.Fl Fl version-control Cm t | nil | never +.Xc +Causes the next argument to be interpreted as a method for creating +backup file names. +The type of backups made can also be given in the +.Ev PATCH_VERSION_CONTROL +or +.Ev VERSION_CONTROL +environment variables, which are overridden by this option. +The +.Fl B +option overrides this option, causing the prefix to always be used for +making backup file names. +The values of the +.Ev PATCH_VERSION_CONTROL +and +.Ev VERSION_CONTROL +environment variables and the argument to the +.Fl V +option are like the GNU Emacs +.Dq version-control +variable; they also recognize synonyms that are more descriptive. +The valid values are (unique abbreviations are accepted): +.Bl -tag -width Ds -offset indent +.It Cm t , numbered +Always make numbered backups. +.It Cm nil , existing +Make numbered backups of files that already have them, +simple backups of the others. +.It Cm never , simple +Always make simple backups. +.El +.It Fl v , Fl Fl version +Causes +.Nm +to print out its revision header and patch level. +.It Xo +.Fl x Ar number , +.Fl Fl debug Ar number +.Xc +Sets internal debugging flags, and is of interest only to +.Nm +patchers. +.It Xo +.Fl z Ar backup-ext , +.Fl Fl suffix Ar backup-ext +.Xc +Causes the next argument to be interpreted as the backup extension, to be +used in place of +.Qq .orig . +.It Fl Fl posix +Enables strict +.St -p1003.1-2008 +conformance, specifically: +.Bl -enum +.It +Backup files are not created unless the +.Fl b +option is specified. +.It +If unspecified, the file name used is the first of the old, new and +index files that exists. +.El +.El +.Ss Patch Application +.Nm +will try to skip any leading garbage, apply the diff, +and then skip any trailing garbage. +Thus you could feed an article or message containing a +diff listing to +.Nm , +and it should work. +If the entire diff is indented by a consistent amount, +this will be taken into account. +.Pp +With context diffs, and to a lesser extent with normal diffs, +.Nm +can detect when the line numbers mentioned in the patch are incorrect, +and will attempt to find the correct place to apply each hunk of the patch. +As a first guess, it takes the line number mentioned for the hunk, plus or +minus any offset used in applying the previous hunk. +If that is not the correct place, +.Nm +will scan both forwards and backwards for a set of lines matching the context +given in the hunk. +First +.Nm +looks for a place where all lines of the context match. +If no such place is found, and it's a context diff, and the maximum fuzz factor +is set to 1 or more, then another scan takes place ignoring the first and last +line of context. +If that fails, and the maximum fuzz factor is set to 2 or more, +the first two and last two lines of context are ignored, +and another scan is made. +.Pq The default maximum fuzz factor is 2. +.Pp +If +.Nm +cannot find a place to install that hunk of the patch, it will put the hunk +out to a reject file, which normally is the name of the output file plus +.Qq .rej . +(Note that the rejected hunk will come out in context diff form whether the +input patch was a context diff or a normal diff. +If the input was a normal diff, many of the contexts will simply be null.) +The line numbers on the hunks in the reject file may be different than +in the patch file: they reflect the approximate location patch thinks the +failed hunks belong in the new file rather than the old one. +.Pp +As each hunk is completed, you will be told whether the hunk succeeded or +failed, and which line (in the new file) +.Nm +thought the hunk should go on. +If this is different from the line number specified in the diff, +you will be told the offset. +A single large offset MAY be an indication that a hunk was installed in the +wrong place. +You will also be told if a fuzz factor was used to make the match, in which +case you should also be slightly suspicious. +.Ss Filename Determination +If no original file is specified on the command line, +.Nm +will try to figure out from the leading garbage what the name of the file +to edit is. +When checking a prospective file name, pathname components are stripped +as specified by the +.Fl p +option and the file's existence and writability are checked relative +to the current working directory (or the directory specified by the +.Fl d +option). +.Pp +If the diff is a context or unified diff, +.Nm +is able to determine the old and new file names from the diff header. +For context diffs, the +.Dq old +file is specified in the line beginning with +.Qq *** +and the +.Dq new +file is specified in the line beginning with +.Qq --- . +For a unified diff, the +.Dq old +file is specified in the line beginning with +.Qq --- +and the +.Dq new +file is specified in the line beginning with +.Qq +++ . +If there is an +.Qq Index: +line in the leading garbage (regardless of the diff type), +.Nm +will use the file name from that line as the +.Dq index +file. +.Pp +.Nm +will choose the file name by performing the following steps, with the first +match used: +.Bl -enum +.It +If +.Nm +is operating in strict +.St -p1003.1-2008 +mode, the first of the +.Dq old , +.Dq new +and +.Dq index +file names that exist is used. +Otherwise, +.Nm +will examine either the +.Dq old +and +.Dq new +file names or, for a non-context diff, the +.Dq index +file name, and choose the file name with the fewest path components, +the shortest basename, and the shortest total file name length (in that order). +.It +If no file exists, +.Nm +checks for the existence of the files in an SCCS or RCS directory +(using the appropriate prefix or suffix) using the criteria specified +above. +If found, +.Nm +will attempt to get or check out the file. +.It +If no suitable file was found to patch, the patch file is a context or +unified diff, and the old file was zero length, the new file name is +created and used. +.It +If the file name still cannot be determined, +.Nm +will prompt the user for the file name to use. +.El +.Pp +Additionally, if the leading garbage contains a +.Qq Prereq:\ \& +line, +.Nm +will take the first word from the prerequisites line (normally a version +number) and check the input file to see if that word can be found. +If not, +.Nm +will ask for confirmation before proceeding. +.Pp +The upshot of all this is that you should be able to say, while in a news +interface, the following: +.Pp +.Dl | patch -d /usr/src/local/blurfl +.Pp +and patch a file in the blurfl directory directly from the article containing +the patch. +.Ss Backup Files +By default, the patched version is put in place of the original, with +the original file backed up to the same name with the extension +.Qq .orig , +or as specified by the +.Fl B , +.Fl V , +or +.Fl z +options. +The extension used for making backup files may also be specified in the +.Ev SIMPLE_BACKUP_SUFFIX +environment variable, which is overridden by the options above. +.Pp +If the backup file is a symbolic or hard link to the original file, +.Nm +creates a new backup file name by changing the first lowercase letter +in the last component of the file's name into uppercase. +If there are no more lowercase letters in the name, +it removes the first character from the name. +It repeats this process until it comes up with a +backup file that does not already exist or is not linked to the original file. +.Pp +You may also specify where you want the output to go with the +.Fl o +option; if that file already exists, it is backed up first. +.Ss Notes For Patch Senders +There are several things you should bear in mind if you are going to +be sending out patches: +.Pp +First, you can save people a lot of grief by keeping a +.Pa patchlevel.h +file which is patched to increment the patch level as the first diff in the +patch file you send out. +If you put a +.Qq Prereq: +line in with the patch, it won't let them apply +patches out of order without some warning. +.Pp +Second, make sure you've specified the file names right, either in a +context diff header, or with an +.Qq Index: +line. +If you are patching something in a subdirectory, be sure to tell the patch +user to specify a +.Fl p +option as needed. +.Pp +Third, you can create a file by sending out a diff that compares a +null file to the file you want to create. +This will only work if the file you want to create doesn't exist already in +the target directory. +.Pp +Fourth, take care not to send out reversed patches, since it makes people wonder +whether they already applied the patch. +.Pp +Fifth, while you may be able to get away with putting 582 diff listings into +one file, it is probably wiser to group related patches into separate files in +case something goes haywire. +.Sh ENVIRONMENT +.Bl -tag -width "PATCH_VERSION_CONTROL" -compact +.It Ev POSIXLY_CORRECT +When set, +.Nm +behaves as if the +.Fl Fl posix +option has been specified. +.It Ev SIMPLE_BACKUP_SUFFIX +Extension to use for backup file names instead of +.Qq .orig . +.It Ev TMPDIR +Directory to put temporary files in; default is +.Pa /tmp . +.It Ev PATCH_VERSION_CONTROL +Selects when numbered backup files are made. +.It Ev VERSION_CONTROL +Same as +.Ev PATCH_VERSION_CONTROL . +.El +.Sh FILES +.Bl -tag -width "$TMPDIR/patch*" -compact +.It Pa $TMPDIR/patch* +.Nm +temporary files +.It Pa /dev/tty +used to read input when +.Nm +prompts the user +.El +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It 0 +Successful completion. +.It 1 +One or more lines were written to a reject file. +.It \*(Gt1 +An error occurred. +.El +.Pp +When applying a set of patches in a loop it behooves you to check this +exit status so you don't apply a later patch to a partially patched file. +.Sh DIAGNOSTICS +Too many to list here, but generally indicative that +.Nm +couldn't parse your patch file. +.Pp +The message +.Qq Hmm... +indicates that there is unprocessed text in the patch file and that +.Nm +is attempting to intuit whether there is a patch in that text and, if so, +what kind of patch it is. +.Sh SEE ALSO +.Xr diff 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification +(except as detailed above for the +.Fl -posix +option), +though the presence of +.Nm +itself is optional. +.Pp +The flags +.Op Fl BCEFfstVvxz +and +.Op Fl -posix +are extensions to that specification. +.Sh AUTHORS +.An Larry Wall +with many other contributors. +.Sh CAVEATS +.Nm +cannot tell if the line numbers are off in an ed script, and can only detect +bad line numbers in a normal diff when it finds a +.Qq change +or a +.Qq delete +command. +A context diff using fuzz factor 3 may have the same problem. +Until a suitable interactive interface is added, you should probably do +a context diff in these cases to see if the changes made sense. +Of course, compiling without errors is a pretty good indication that the patch +worked, but not always. +.Pp +.Nm +usually produces the correct results, even when it has to do a lot of +guessing. +However, the results are guaranteed to be correct only when the patch is +applied to exactly the same version of the file that the patch was +generated from. +.Sh BUGS +Could be smarter about partial matches, excessively deviant offsets and +swapped code, but that would take an extra pass. +.Pp +Check patch mode +.Pq Fl C +will fail if you try to check several patches in succession that build on +each other. +The entire +.Nm +code would have to be restructured to keep temporary files around so that it +can handle this situation. +.Pp +If code has been duplicated (for instance with #ifdef OLDCODE ... #else ... +#endif), +.Nm +is incapable of patching both versions, and, if it works at all, will likely +patch the wrong one, and tell you that it succeeded to boot. +.Pp +If you apply a patch you've already applied, +.Nm +will think it is a reversed patch, and offer to un-apply the patch. +This could be construed as a feature. diff --git a/usr.bin/patch/patch.c b/usr.bin/patch/patch.c new file mode 100644 index 0000000..d008c0f --- /dev/null +++ b/usr.bin/patch/patch.c @@ -0,0 +1,1069 @@ +/*- + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * patch - a program to apply diffs to original files + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + * + * $OpenBSD: patch.c,v 1.50 2012/05/15 19:32:02 millert Exp $ + * $FreeBSD$ + * + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <getopt.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "common.h" +#include "util.h" +#include "pch.h" +#include "inp.h" +#include "backupfile.h" +#include "pathnames.h" + +mode_t filemode = 0644; + +char *buf; /* general purpose buffer */ +size_t buf_size; /* size of the general purpose buffer */ + +bool using_plan_a = true; /* try to keep everything in memory */ +bool out_of_mem = false; /* ran out of memory in plan a */ + +#define MAXFILEC 2 + +char *filearg[MAXFILEC]; +bool ok_to_create_file = false; +char *outname = NULL; +char *origprae = NULL; +char *TMPOUTNAME; +char *TMPINNAME; +char *TMPREJNAME; +char *TMPPATNAME; +bool toutkeep = false; +bool trejkeep = false; +bool warn_on_invalid_line; +bool last_line_missing_eol; + +#ifdef DEBUGGING +int debug = 0; +#endif + +bool force = false; +bool batch = false; +bool verbose = true; +bool reverse = false; +bool noreverse = false; +bool skip_rest_of_patch = false; +int strippath = 957; +bool canonicalize = false; +bool check_only = false; +int diff_type = 0; +char *revision = NULL; /* prerequisite revision, if any */ +LINENUM input_lines = 0; /* how long is input file in lines */ +int posix = 0; /* strict POSIX mode? */ + +static void reinitialize_almost_everything(void); +static void get_some_switches(void); +static LINENUM locate_hunk(LINENUM); +static void abort_context_hunk(void); +static void rej_line(int, LINENUM); +static void abort_hunk(void); +static void apply_hunk(LINENUM); +static void init_output(const char *); +static void init_reject(const char *); +static void copy_till(LINENUM, bool); +static bool spew_output(void); +static void dump_line(LINENUM, bool); +static bool patch_match(LINENUM, LINENUM, LINENUM); +static bool similar(const char *, const char *, int); +static void usage(void); + +/* true if -E was specified on command line. */ +static bool remove_empty_files = false; + +/* true if -R was specified on command line. */ +static bool reverse_flag_specified = false; + +/* buffer holding the name of the rejected patch file. */ +static char rejname[NAME_MAX + 1]; + +/* how many input lines have been irretractibly output */ +static LINENUM last_frozen_line = 0; + +static int Argc; /* guess */ +static char **Argv; +static int Argc_last; /* for restarting plan_b */ +static char **Argv_last; + +static FILE *ofp = NULL; /* output file pointer */ +static FILE *rejfp = NULL; /* reject file pointer */ + +static int filec = 0; /* how many file arguments? */ +static LINENUM last_offset = 0; +static LINENUM maxfuzz = 2; + +/* patch using ifdef, ifndef, etc. */ +static bool do_defines = false; +/* #ifdef xyzzy */ +static char if_defined[128]; +/* #ifndef xyzzy */ +static char not_defined[128]; +/* #else */ +static const char else_defined[] = "#else\n"; +/* #endif xyzzy */ +static char end_defined[128]; + + +/* Apply a set of diffs as appropriate. */ + +int +main(int argc, char *argv[]) +{ + int error = 0, hunk, failed, i, fd; + bool patch_seen; + LINENUM where = 0, newwhere, fuzz, mymaxfuzz; + const char *tmpdir; + char *v; + + setlinebuf(stdout); + setlinebuf(stderr); + for (i = 0; i < MAXFILEC; i++) + filearg[i] = NULL; + + buf_size = INITLINELEN; + buf = malloc((unsigned)(buf_size)); + if (buf == NULL) + fatal("out of memory\n"); + + /* Cons up the names of the temporary files. */ + if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0') + tmpdir = _PATH_TMP; + for (i = strlen(tmpdir) - 1; i > 0 && tmpdir[i] == '/'; i--) + ; + i++; + if (asprintf(&TMPOUTNAME, "%.*s/patchoXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPOUTNAME)) < 0) + pfatal("can't create %s", TMPOUTNAME); + close(fd); + + if (asprintf(&TMPINNAME, "%.*s/patchiXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPINNAME)) < 0) + pfatal("can't create %s", TMPINNAME); + close(fd); + + if (asprintf(&TMPREJNAME, "%.*s/patchrXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPREJNAME)) < 0) + pfatal("can't create %s", TMPREJNAME); + close(fd); + + if (asprintf(&TMPPATNAME, "%.*s/patchpXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPPATNAME)) < 0) + pfatal("can't create %s", TMPPATNAME); + close(fd); + + v = getenv("SIMPLE_BACKUP_SUFFIX"); + if (v) + simple_backup_suffix = v; + else + simple_backup_suffix = ORIGEXT; + + /* parse switches */ + Argc = argc; + Argv = argv; + get_some_switches(); + + if (backup_type == none) { + if ((v = getenv("PATCH_VERSION_CONTROL")) == NULL) + v = getenv("VERSION_CONTROL"); + if (v != NULL || !posix) + backup_type = get_version(v); /* OK to pass NULL. */ + } + + /* make sure we clean up /tmp in case of disaster */ + set_signals(0); + + patch_seen = false; + for (open_patch_file(filearg[1]); there_is_another_patch(); + reinitialize_almost_everything()) { + /* for each patch in patch file */ + + patch_seen = true; + + warn_on_invalid_line = true; + + if (outname == NULL) + outname = savestr(filearg[0]); + + /* for ed script just up and do it and exit */ + if (diff_type == ED_DIFF) { + do_ed_script(); + continue; + } + /* initialize the patched file */ + if (!skip_rest_of_patch) + init_output(TMPOUTNAME); + + /* initialize reject file */ + init_reject(TMPREJNAME); + + /* find out where all the lines are */ + if (!skip_rest_of_patch) + scan_input(filearg[0]); + + /* from here on, open no standard i/o files, because malloc */ + /* might misfire and we can't catch it easily */ + + /* apply each hunk of patch */ + hunk = 0; + failed = 0; + out_of_mem = false; + while (another_hunk()) { + hunk++; + fuzz = 0; + mymaxfuzz = pch_context(); + if (maxfuzz < mymaxfuzz) + mymaxfuzz = maxfuzz; + if (!skip_rest_of_patch) { + do { + where = locate_hunk(fuzz); + if (hunk == 1 && where == 0 && !force) { + /* dwim for reversed patch? */ + if (!pch_swap()) { + if (fuzz == 0) + say("Not enough memory to try swapped hunk! Assuming unswapped.\n"); + continue; + } + reverse = !reverse; + /* try again */ + where = locate_hunk(fuzz); + if (where == 0) { + /* didn't find it swapped */ + if (!pch_swap()) + /* put it back to normal */ + fatal("lost hunk on alloc error!\n"); + reverse = !reverse; + } else if (noreverse) { + if (!pch_swap()) + /* put it back to normal */ + fatal("lost hunk on alloc error!\n"); + reverse = !reverse; + say("Ignoring previously applied (or reversed) patch.\n"); + skip_rest_of_patch = true; + } else if (batch) { + if (verbose) + say("%seversed (or previously applied) patch detected! %s -R.", + reverse ? "R" : "Unr", + reverse ? "Assuming" : "Ignoring"); + } else { + ask("%seversed (or previously applied) patch detected! %s -R? [y] ", + reverse ? "R" : "Unr", + reverse ? "Assume" : "Ignore"); + if (*buf == 'n') { + ask("Apply anyway? [n] "); + if (*buf != 'y') + skip_rest_of_patch = true; + where = 0; + reverse = !reverse; + if (!pch_swap()) + /* put it back to normal */ + fatal("lost hunk on alloc error!\n"); + } + } + } + } while (!skip_rest_of_patch && where == 0 && + ++fuzz <= mymaxfuzz); + + if (skip_rest_of_patch) { /* just got decided */ + if (ferror(ofp) || fclose(ofp)) { + say("Error writing %s\n", + TMPOUTNAME); + error = 1; + } + ofp = NULL; + } + } + newwhere = pch_newfirst() + last_offset; + if (skip_rest_of_patch) { + abort_hunk(); + failed++; + if (verbose) + say("Hunk #%d ignored at %ld.\n", + hunk, newwhere); + } else if (where == 0) { + abort_hunk(); + failed++; + if (verbose) + say("Hunk #%d failed at %ld.\n", + hunk, newwhere); + } else { + apply_hunk(where); + if (verbose) { + say("Hunk #%d succeeded at %ld", + hunk, newwhere); + if (fuzz != 0) + say(" with fuzz %ld", fuzz); + if (last_offset) + say(" (offset %ld line%s)", + last_offset, + last_offset == 1L ? "" : "s"); + say(".\n"); + } + } + } + + if (out_of_mem && using_plan_a) { + Argc = Argc_last; + Argv = Argv_last; + say("\n\nRan out of memory using Plan A--trying again...\n\n"); + if (ofp) + fclose(ofp); + ofp = NULL; + if (rejfp) + fclose(rejfp); + rejfp = NULL; + continue; + } + if (hunk == 0) + fatal("Internal error: hunk should not be 0\n"); + + /* finish spewing out the new file */ + if (!skip_rest_of_patch && !spew_output()) { + say("Can't write %s\n", TMPOUTNAME); + error = 1; + } + + /* and put the output where desired */ + ignore_signals(); + if (!skip_rest_of_patch) { + struct stat statbuf; + char *realout = outname; + + if (!check_only) { + if (move_file(TMPOUTNAME, outname) < 0) { + toutkeep = true; + realout = TMPOUTNAME; + chmod(TMPOUTNAME, filemode); + } else + chmod(outname, filemode); + + if (remove_empty_files && + stat(realout, &statbuf) == 0 && + statbuf.st_size == 0) { + if (verbose) + say("Removing %s (empty after patching).\n", + realout); + unlink(realout); + } + } + } + if (ferror(rejfp) || fclose(rejfp)) { + say("Error writing %s\n", rejname); + error = 1; + } + rejfp = NULL; + if (failed) { + error = 1; + if (*rejname == '\0') { + if (strlcpy(rejname, outname, + sizeof(rejname)) >= sizeof(rejname)) + fatal("filename %s is too long\n", outname); + if (strlcat(rejname, REJEXT, + sizeof(rejname)) >= sizeof(rejname)) + fatal("filename %s is too long\n", outname); + } + if (!check_only) + say("%d out of %d hunks %s--saving rejects to %s\n", + failed, hunk, skip_rest_of_patch ? "ignored" : "failed", rejname); + else + say("%d out of %d hunks %s\n", + failed, hunk, skip_rest_of_patch ? "ignored" : "failed"); + if (!check_only && move_file(TMPREJNAME, rejname) < 0) + trejkeep = true; + } + set_signals(1); + } + + if (!patch_seen) + error = 2; + + my_exit(error); + /* NOTREACHED */ +} + +/* Prepare to find the next patch to do in the patch file. */ + +static void +reinitialize_almost_everything(void) +{ + re_patch(); + re_input(); + + input_lines = 0; + last_frozen_line = 0; + + filec = 0; + if (!out_of_mem) { + free(filearg[0]); + filearg[0] = NULL; + } + + free(outname); + outname = NULL; + + last_offset = 0; + diff_type = 0; + + free(revision); + revision = NULL; + + reverse = reverse_flag_specified; + skip_rest_of_patch = false; + + get_some_switches(); +} + +/* Process switches and filenames. */ + +static void +get_some_switches(void) +{ + const char *options = "b::B:cCd:D:eEfF:i:lnNo:p:r:RstuvV:x:z:"; + static struct option longopts[] = { + {"backup", no_argument, 0, 'b'}, + {"batch", no_argument, 0, 't'}, + {"check", no_argument, 0, 'C'}, + {"context", no_argument, 0, 'c'}, + {"debug", required_argument, 0, 'x'}, + {"directory", required_argument, 0, 'd'}, + {"ed", no_argument, 0, 'e'}, + {"force", no_argument, 0, 'f'}, + {"forward", no_argument, 0, 'N'}, + {"fuzz", required_argument, 0, 'F'}, + {"ifdef", required_argument, 0, 'D'}, + {"input", required_argument, 0, 'i'}, + {"ignore-whitespace", no_argument, 0, 'l'}, + {"normal", no_argument, 0, 'n'}, + {"output", required_argument, 0, 'o'}, + {"prefix", required_argument, 0, 'B'}, + {"quiet", no_argument, 0, 's'}, + {"reject-file", required_argument, 0, 'r'}, + {"remove-empty-files", no_argument, 0, 'E'}, + {"reverse", no_argument, 0, 'R'}, + {"silent", no_argument, 0, 's'}, + {"strip", required_argument, 0, 'p'}, + {"suffix", required_argument, 0, 'z'}, + {"unified", no_argument, 0, 'u'}, + {"version", no_argument, 0, 'v'}, + {"version-control", required_argument, 0, 'V'}, + {"posix", no_argument, &posix, 1}, + {NULL, 0, 0, 0} + }; + int ch; + + rejname[0] = '\0'; + Argc_last = Argc; + Argv_last = Argv; + if (!Argc) + return; + optreset = optind = 1; + while ((ch = getopt_long(Argc, Argv, options, longopts, NULL)) != -1) { + switch (ch) { + case 'b': + if (backup_type == none) + backup_type = numbered_existing; + if (optarg == NULL) + break; + if (verbose) + say("Warning, the ``-b suffix'' option has been" + " obsoleted by the -z option.\n"); + /* FALLTHROUGH */ + case 'z': + /* must directly follow 'b' case for backwards compat */ + simple_backup_suffix = savestr(optarg); + break; + case 'B': + origprae = savestr(optarg); + break; + case 'c': + diff_type = CONTEXT_DIFF; + break; + case 'C': + check_only = true; + break; + case 'd': + if (chdir(optarg) < 0) + pfatal("can't cd to %s", optarg); + break; + case 'D': + do_defines = true; + if (!isalpha((unsigned char)*optarg) && *optarg != '_') + fatal("argument to -D is not an identifier\n"); + snprintf(if_defined, sizeof if_defined, + "#ifdef %s\n", optarg); + snprintf(not_defined, sizeof not_defined, + "#ifndef %s\n", optarg); + snprintf(end_defined, sizeof end_defined, + "#endif /* %s */\n", optarg); + break; + case 'e': + diff_type = ED_DIFF; + break; + case 'E': + remove_empty_files = true; + break; + case 'f': + force = true; + break; + case 'F': + maxfuzz = atoi(optarg); + break; + case 'i': + if (++filec == MAXFILEC) + fatal("too many file arguments\n"); + filearg[filec] = savestr(optarg); + break; + case 'l': + canonicalize = true; + break; + case 'n': + diff_type = NORMAL_DIFF; + break; + case 'N': + noreverse = true; + break; + case 'o': + outname = savestr(optarg); + break; + case 'p': + strippath = atoi(optarg); + break; + case 'r': + if (strlcpy(rejname, optarg, + sizeof(rejname)) >= sizeof(rejname)) + fatal("argument for -r is too long\n"); + break; + case 'R': + reverse = true; + reverse_flag_specified = true; + break; + case 's': + verbose = false; + break; + case 't': + batch = true; + break; + case 'u': + diff_type = UNI_DIFF; + break; + case 'v': + version(); + break; + case 'V': + backup_type = get_version(optarg); + break; +#ifdef DEBUGGING + case 'x': + debug = atoi(optarg); + break; +#endif + default: + if (ch != '\0') + usage(); + break; + } + } + Argc -= optind; + Argv += optind; + + if (Argc > 0) { + filearg[0] = savestr(*Argv++); + Argc--; + while (Argc > 0) { + if (++filec == MAXFILEC) + fatal("too many file arguments\n"); + filearg[filec] = savestr(*Argv++); + Argc--; + } + } + + if (getenv("POSIXLY_CORRECT") != NULL) + posix = 1; +} + +static void +usage(void) +{ + fprintf(stderr, +"usage: patch [-bCcEeflNnRstuv] [-B backup-prefix] [-D symbol] [-d directory]\n" +" [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]\n" +" [-r rej-name] [-V t | nil | never] [-x number] [-z backup-ext]\n" +" [--posix] [origfile [patchfile]]\n" +" patch <patchfile\n"); + my_exit(EXIT_SUCCESS); +} + +/* + * Attempt to find the right place to apply this hunk of patch. + */ +static LINENUM +locate_hunk(LINENUM fuzz) +{ + LINENUM first_guess = pch_first() + last_offset; + LINENUM offset; + LINENUM pat_lines = pch_ptrn_lines(); + LINENUM max_pos_offset = input_lines - first_guess - pat_lines + 1; + LINENUM max_neg_offset = first_guess - last_frozen_line - 1 + pch_context(); + + if (pat_lines == 0) { /* null range matches always */ + if (verbose && fuzz == 0 && (diff_type == CONTEXT_DIFF + || diff_type == NEW_CONTEXT_DIFF + || diff_type == UNI_DIFF)) { + say("Empty context always matches.\n"); + } + return (first_guess); + } + if (max_neg_offset >= first_guess) /* do not try lines < 0 */ + max_neg_offset = first_guess - 1; + if (first_guess <= input_lines && patch_match(first_guess, 0, fuzz)) + return first_guess; + for (offset = 1; ; offset++) { + bool check_after = (offset <= max_pos_offset); + bool check_before = (offset <= max_neg_offset); + + if (check_after && patch_match(first_guess, offset, fuzz)) { +#ifdef DEBUGGING + if (debug & 1) + say("Offset changing from %ld to %ld\n", + last_offset, offset); +#endif + last_offset = offset; + return first_guess + offset; + } else if (check_before && patch_match(first_guess, -offset, fuzz)) { +#ifdef DEBUGGING + if (debug & 1) + say("Offset changing from %ld to %ld\n", + last_offset, -offset); +#endif + last_offset = -offset; + return first_guess - offset; + } else if (!check_before && !check_after) + return 0; + } +} + +/* We did not find the pattern, dump out the hunk so they can handle it. */ + +static void +abort_context_hunk(void) +{ + LINENUM i; + const LINENUM pat_end = pch_end(); + /* + * add in last_offset to guess the same as the previous successful + * hunk + */ + const LINENUM oldfirst = pch_first() + last_offset; + const LINENUM newfirst = pch_newfirst() + last_offset; + const LINENUM oldlast = oldfirst + pch_ptrn_lines() - 1; + const LINENUM newlast = newfirst + pch_repl_lines() - 1; + const char *stars = (diff_type >= NEW_CONTEXT_DIFF ? " ****" : ""); + const char *minuses = (diff_type >= NEW_CONTEXT_DIFF ? " ----" : " -----"); + + fprintf(rejfp, "***************\n"); + for (i = 0; i <= pat_end; i++) { + switch (pch_char(i)) { + case '*': + if (oldlast < oldfirst) + fprintf(rejfp, "*** 0%s\n", stars); + else if (oldlast == oldfirst) + fprintf(rejfp, "*** %ld%s\n", oldfirst, stars); + else + fprintf(rejfp, "*** %ld,%ld%s\n", oldfirst, + oldlast, stars); + break; + case '=': + if (newlast < newfirst) + fprintf(rejfp, "--- 0%s\n", minuses); + else if (newlast == newfirst) + fprintf(rejfp, "--- %ld%s\n", newfirst, minuses); + else + fprintf(rejfp, "--- %ld,%ld%s\n", newfirst, + newlast, minuses); + break; + case '\n': + fprintf(rejfp, "%s", pfetch(i)); + break; + case ' ': + case '-': + case '+': + case '!': + fprintf(rejfp, "%c %s", pch_char(i), pfetch(i)); + break; + default: + fatal("fatal internal error in abort_context_hunk\n"); + } + } +} + +static void +rej_line(int ch, LINENUM i) +{ + size_t len; + const char *line = pfetch(i); + + len = strlen(line); + + fprintf(rejfp, "%c%s", ch, line); + if (len == 0 || line[len-1] != '\n') + fprintf(rejfp, "\n\\ No newline at end of file\n"); +} + +static void +abort_hunk(void) +{ + LINENUM i, j, split; + int ch1, ch2; + const LINENUM pat_end = pch_end(); + const LINENUM oldfirst = pch_first() + last_offset; + const LINENUM newfirst = pch_newfirst() + last_offset; + + if (diff_type != UNI_DIFF) { + abort_context_hunk(); + return; + } + split = -1; + for (i = 0; i <= pat_end; i++) { + if (pch_char(i) == '=') { + split = i; + break; + } + } + if (split == -1) { + fprintf(rejfp, "malformed hunk: no split found\n"); + return; + } + i = 0; + j = split + 1; + fprintf(rejfp, "@@ -%ld,%ld +%ld,%ld @@\n", + pch_ptrn_lines() ? oldfirst : 0, + pch_ptrn_lines(), newfirst, pch_repl_lines()); + while (i < split || j <= pat_end) { + ch1 = i < split ? pch_char(i) : -1; + ch2 = j <= pat_end ? pch_char(j) : -1; + if (ch1 == '-') { + rej_line('-', i); + i++; + } else if (ch1 == ' ' && ch2 == ' ') { + rej_line(' ', i); + i++; + j++; + } else if (ch1 == '!' && ch2 == '!') { + while (i < split && ch1 == '!') { + rej_line('-', i); + i++; + ch1 = i < split ? pch_char(i) : -1; + } + while (j <= pat_end && ch2 == '!') { + rej_line('+', j); + j++; + ch2 = j <= pat_end ? pch_char(j) : -1; + } + } else if (ch1 == '*') { + i++; + } else if (ch2 == '+' || ch2 == ' ') { + rej_line(ch2, j); + j++; + } else { + fprintf(rejfp, "internal error on (%ld %ld %ld)\n", + i, split, j); + rej_line(ch1, i); + rej_line(ch2, j); + return; + } + } +} + +/* We found where to apply it (we hope), so do it. */ + +static void +apply_hunk(LINENUM where) +{ + LINENUM old = 1; + const LINENUM lastline = pch_ptrn_lines(); + LINENUM new = lastline + 1; +#define OUTSIDE 0 +#define IN_IFNDEF 1 +#define IN_IFDEF 2 +#define IN_ELSE 3 + int def_state = OUTSIDE; + const LINENUM pat_end = pch_end(); + + where--; + while (pch_char(new) == '=' || pch_char(new) == '\n') + new++; + + while (old <= lastline) { + if (pch_char(old) == '-') { + copy_till(where + old - 1, false); + if (do_defines) { + if (def_state == OUTSIDE) { + fputs(not_defined, ofp); + def_state = IN_IFNDEF; + } else if (def_state == IN_IFDEF) { + fputs(else_defined, ofp); + def_state = IN_ELSE; + } + fputs(pfetch(old), ofp); + } + last_frozen_line++; + old++; + } else if (new > pat_end) { + break; + } else if (pch_char(new) == '+') { + copy_till(where + old - 1, false); + if (do_defines) { + if (def_state == IN_IFNDEF) { + fputs(else_defined, ofp); + def_state = IN_ELSE; + } else if (def_state == OUTSIDE) { + fputs(if_defined, ofp); + def_state = IN_IFDEF; + } + } + fputs(pfetch(new), ofp); + new++; + } else if (pch_char(new) != pch_char(old)) { + say("Out-of-sync patch, lines %ld,%ld--mangled text or line numbers, maybe?\n", + pch_hunk_beg() + old, + pch_hunk_beg() + new); +#ifdef DEBUGGING + say("oldchar = '%c', newchar = '%c'\n", + pch_char(old), pch_char(new)); +#endif + my_exit(2); + } else if (pch_char(new) == '!') { + copy_till(where + old - 1, false); + if (do_defines) { + fputs(not_defined, ofp); + def_state = IN_IFNDEF; + } + while (pch_char(old) == '!') { + if (do_defines) { + fputs(pfetch(old), ofp); + } + last_frozen_line++; + old++; + } + if (do_defines) { + fputs(else_defined, ofp); + def_state = IN_ELSE; + } + while (pch_char(new) == '!') { + fputs(pfetch(new), ofp); + new++; + } + } else { + if (pch_char(new) != ' ') + fatal("Internal error: expected ' '\n"); + old++; + new++; + if (do_defines && def_state != OUTSIDE) { + fputs(end_defined, ofp); + def_state = OUTSIDE; + } + } + } + if (new <= pat_end && pch_char(new) == '+') { + copy_till(where + old - 1, false); + if (do_defines) { + if (def_state == OUTSIDE) { + fputs(if_defined, ofp); + def_state = IN_IFDEF; + } else if (def_state == IN_IFNDEF) { + fputs(else_defined, ofp); + def_state = IN_ELSE; + } + } + while (new <= pat_end && pch_char(new) == '+') { + fputs(pfetch(new), ofp); + new++; + } + } + if (do_defines && def_state != OUTSIDE) { + fputs(end_defined, ofp); + } +} + +/* + * Open the new file. + */ +static void +init_output(const char *name) +{ + ofp = fopen(name, "w"); + if (ofp == NULL) + pfatal("can't create %s", name); +} + +/* + * Open a file to put hunks we can't locate. + */ +static void +init_reject(const char *name) +{ + rejfp = fopen(name, "w"); + if (rejfp == NULL) + pfatal("can't create %s", name); +} + +/* + * Copy input file to output, up to wherever hunk is to be applied. + * If endoffile is true, treat the last line specially since it may + * lack a newline. + */ +static void +copy_till(LINENUM lastline, bool endoffile) +{ + if (last_frozen_line > lastline) + fatal("misordered hunks! output would be garbled\n"); + while (last_frozen_line < lastline) { + if (++last_frozen_line == lastline && endoffile) + dump_line(last_frozen_line, !last_line_missing_eol); + else + dump_line(last_frozen_line, true); + } +} + +/* + * Finish copying the input file to the output file. + */ +static bool +spew_output(void) +{ + int rv; + +#ifdef DEBUGGING + if (debug & 256) + say("il=%ld lfl=%ld\n", input_lines, last_frozen_line); +#endif + if (input_lines) + copy_till(input_lines, true); /* dump remainder of file */ + rv = ferror(ofp) == 0 && fclose(ofp) == 0; + ofp = NULL; + return rv; +} + +/* + * Copy one line from input to output. + */ +static void +dump_line(LINENUM line, bool write_newline) +{ + char *s; + + s = ifetch(line, 0); + if (s == NULL) + return; + /* Note: string is not NUL terminated. */ + for (; *s != '\n'; s++) + putc(*s, ofp); + if (write_newline) + putc('\n', ofp); +} + +/* + * Does the patch pattern match at line base+offset? + */ +static bool +patch_match(LINENUM base, LINENUM offset, LINENUM fuzz) +{ + LINENUM pline = 1 + fuzz; + LINENUM iline; + LINENUM pat_lines = pch_ptrn_lines() - fuzz; + const char *ilineptr; + const char *plineptr; + short plinelen; + + for (iline = base + offset + fuzz; pline <= pat_lines; pline++, iline++) { + ilineptr = ifetch(iline, offset >= 0); + if (ilineptr == NULL) + return false; + plineptr = pfetch(pline); + plinelen = pch_line_len(pline); + if (canonicalize) { + if (!similar(ilineptr, plineptr, plinelen)) + return false; + } else if (strnNE(ilineptr, plineptr, plinelen)) + return false; + if (iline == input_lines) { + /* + * We are looking at the last line of the file. + * If the file has no eol, the patch line should + * not have one either and vice-versa. Note that + * plinelen > 0. + */ + if (last_line_missing_eol) { + if (plineptr[plinelen - 1] == '\n') + return false; + } else { + if (plineptr[plinelen - 1] != '\n') + return false; + } + } + } + return true; +} + +/* + * Do two lines match with canonicalized white space? + */ +static bool +similar(const char *a, const char *b, int len) +{ + while (len) { + if (isspace((unsigned char)*b)) { /* whitespace (or \n) to match? */ + if (!isspace((unsigned char)*a)) /* no corresponding whitespace? */ + return false; + while (len && isspace((unsigned char)*b) && *b != '\n') + b++, len--; /* skip pattern whitespace */ + while (isspace((unsigned char)*a) && *a != '\n') + a++; /* skip target whitespace */ + if (*a == '\n' || *b == '\n') + return (*a == *b); /* should end in sync */ + } else if (*a++ != *b++) /* match non-whitespace chars */ + return false; + else + len--; /* probably not necessary */ + } + return true; /* actually, this is not reached */ + /* since there is always a \n */ +} diff --git a/usr.bin/patch/pathnames.h b/usr.bin/patch/pathnames.h new file mode 100644 index 0000000..d31300e --- /dev/null +++ b/usr.bin/patch/pathnames.h @@ -0,0 +1,12 @@ +/*- + * Placed in the public domain by Todd C. Miller <Todd.Miller@courtesan.com> + * on July 29, 2003. + * + * $OpenBSD: pathnames.h,v 1.1 2003/07/29 20:10:17 millert Exp $ + * $FreeBSD$ + */ + + +#include <paths.h> + +#define _PATH_ED "/bin/ed" diff --git a/usr.bin/patch/pch.c b/usr.bin/patch/pch.c new file mode 100644 index 0000000..4c06bbf --- /dev/null +++ b/usr.bin/patch/pch.c @@ -0,0 +1,1593 @@ + +/*- + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * patch - a program to apply diffs to original files + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + * + * $OpenBSD: pch.c,v 1.39 2012/04/11 08:07:13 ajacoutot Exp $ + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" +#include "util.h" +#include "pch.h" +#include "pathnames.h" + +/* Patch (diff listing) abstract type. */ + +static long p_filesize; /* size of the patch file */ +static LINENUM p_first; /* 1st line number */ +static LINENUM p_newfirst; /* 1st line number of replacement */ +static LINENUM p_ptrn_lines; /* # lines in pattern */ +static LINENUM p_repl_lines; /* # lines in replacement text */ +static LINENUM p_end = -1; /* last line in hunk */ +static LINENUM p_max; /* max allowed value of p_end */ +static LINENUM p_context = 3; /* # of context lines */ +static LINENUM p_input_line = 0; /* current line # from patch file */ +static char **p_line = NULL;/* the text of the hunk */ +static short *p_len = NULL; /* length of each line */ +static char *p_char = NULL; /* +, -, and ! */ +static int hunkmax = INITHUNKMAX; /* size of above arrays to begin with */ +static int p_indent; /* indent to patch */ +static LINENUM p_base; /* where to intuit this time */ +static LINENUM p_bline; /* line # of p_base */ +static LINENUM p_start; /* where intuit found a patch */ +static LINENUM p_sline; /* and the line number for it */ +static LINENUM p_hunk_beg; /* line number of current hunk */ +static LINENUM p_efake = -1; /* end of faked up lines--don't free */ +static LINENUM p_bfake = -1; /* beg of faked up lines */ +static FILE *pfp = NULL; /* patch file pointer */ +static char *bestguess = NULL; /* guess at correct filename */ + +static void grow_hunkmax(void); +static int intuit_diff_type(void); +static void next_intuit_at(LINENUM, LINENUM); +static void skip_to(LINENUM, LINENUM); +static size_t pgets(bool _do_indent); +static char *best_name(const struct file_name *, bool); +static char *posix_name(const struct file_name *, bool); +static size_t num_components(const char *); + +/* + * Prepare to look for the next patch in the patch file. + */ +void +re_patch(void) +{ + p_first = 0; + p_newfirst = 0; + p_ptrn_lines = 0; + p_repl_lines = 0; + p_end = (LINENUM) - 1; + p_max = 0; + p_indent = 0; +} + +/* + * Open the patch file at the beginning of time. + */ +void +open_patch_file(const char *filename) +{ + struct stat filestat; + + if (filename == NULL || *filename == '\0' || strEQ(filename, "-")) { + pfp = fopen(TMPPATNAME, "w"); + if (pfp == NULL) + pfatal("can't create %s", TMPPATNAME); + while (fgets(buf, buf_size, stdin) != NULL) + fputs(buf, pfp); + if (ferror(pfp) || fclose(pfp)) + pfatal("can't write %s", TMPPATNAME); + filename = TMPPATNAME; + } + pfp = fopen(filename, "r"); + if (pfp == NULL) + pfatal("patch file %s not found", filename); + fstat(fileno(pfp), &filestat); + p_filesize = filestat.st_size; + next_intuit_at(0L, 1L); /* start at the beginning */ + set_hunkmax(); +} + +/* + * Make sure our dynamically realloced tables are malloced to begin with. + */ +void +set_hunkmax(void) +{ + if (p_line == NULL) + p_line = calloc((size_t) hunkmax, sizeof(char *)); + if (p_len == NULL) + p_len = calloc((size_t) hunkmax, sizeof(short)); + if (p_char == NULL) + p_char = calloc((size_t) hunkmax, sizeof(char)); +} + +/* + * Enlarge the arrays containing the current hunk of patch. + */ +static void +grow_hunkmax(void) +{ + int new_hunkmax; + char **new_p_line; + short *new_p_len; + char *new_p_char; + + new_hunkmax = hunkmax * 2; + + if (p_line == NULL || p_len == NULL || p_char == NULL) + fatal("Internal memory allocation error\n"); + + new_p_line = realloc(p_line, new_hunkmax * sizeof(char *)); + if (new_p_line == NULL) + free(p_line); + + new_p_len = realloc(p_len, new_hunkmax * sizeof(short)); + if (new_p_len == NULL) + free(p_len); + + new_p_char = realloc(p_char, new_hunkmax * sizeof(char)); + if (new_p_char == NULL) + free(p_char); + + p_char = new_p_char; + p_len = new_p_len; + p_line = new_p_line; + + if (p_line != NULL && p_len != NULL && p_char != NULL) { + hunkmax = new_hunkmax; + return; + } + + if (!using_plan_a) + fatal("out of memory\n"); + out_of_mem = true; /* whatever is null will be allocated again */ + /* from within plan_a(), of all places */ +} + +/* True if the remainder of the patch file contains a diff of some sort. */ + +bool +there_is_another_patch(void) +{ + bool exists = false; + + if (p_base != 0L && p_base >= p_filesize) { + if (verbose) + say("done\n"); + return false; + } + if (verbose) + say("Hmm..."); + diff_type = intuit_diff_type(); + if (!diff_type) { + if (p_base != 0L) { + if (verbose) + say(" Ignoring the trailing garbage.\ndone\n"); + } else + say(" I can't seem to find a patch in there anywhere.\n"); + return false; + } + if (verbose) + say(" %sooks like %s to me...\n", + (p_base == 0L ? "L" : "The next patch l"), + diff_type == UNI_DIFF ? "a unified diff" : + diff_type == CONTEXT_DIFF ? "a context diff" : + diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" : + diff_type == NORMAL_DIFF ? "a normal diff" : + "an ed script"); + if (p_indent && verbose) + say("(Patch is indented %d space%s.)\n", p_indent, + p_indent == 1 ? "" : "s"); + skip_to(p_start, p_sline); + while (filearg[0] == NULL) { + if (force || batch) { + say("No file to patch. Skipping...\n"); + filearg[0] = savestr(bestguess); + skip_rest_of_patch = true; + return true; + } + ask("File to patch: "); + if (*buf != '\n') { + free(bestguess); + bestguess = savestr(buf); + filearg[0] = fetchname(buf, &exists, 0); + } + if (!exists) { + ask("No file found--skip this patch? [n] "); + if (*buf != 'y') + continue; + if (verbose) + say("Skipping patch...\n"); + free(filearg[0]); + filearg[0] = fetchname(bestguess, &exists, 0); + skip_rest_of_patch = true; + return true; + } + } + return true; +} + +static void +p4_fetchname(struct file_name *name, char *str) +{ + char *t, *h; + + /* Skip leading whitespace. */ + while (isspace((unsigned char)*str)) + str++; + + /* Remove the file revision number. */ + for (t = str, h = NULL; *t != '\0' && !isspace((unsigned char)*t); t++) + if (*t == '#') + h = t; + if (h != NULL) + *h = '\0'; + + name->path = fetchname(str, &name->exists, strippath); +} + +/* Determine what kind of diff is in the remaining part of the patch file. */ + +static int +intuit_diff_type(void) +{ + long this_line = 0, previous_line; + long first_command_line = -1; + LINENUM fcl_line = -1; + bool last_line_was_command = false, this_is_a_command = false; + bool stars_last_line = false, stars_this_line = false; + char *s, *t; + int indent, retval; + struct file_name names[MAX_FILE]; + + memset(names, 0, sizeof(names)); + ok_to_create_file = false; + fseek(pfp, p_base, SEEK_SET); + p_input_line = p_bline - 1; + for (;;) { + previous_line = this_line; + last_line_was_command = this_is_a_command; + stars_last_line = stars_this_line; + this_line = ftell(pfp); + indent = 0; + p_input_line++; + if (pgets(false) == 0) { + if (first_command_line >= 0L) { + /* nothing but deletes!? */ + p_start = first_command_line; + p_sline = fcl_line; + retval = ED_DIFF; + goto scan_exit; + } else { + p_start = this_line; + p_sline = p_input_line; + retval = 0; + goto scan_exit; + } + } + for (s = buf; *s == ' ' || *s == '\t' || *s == 'X'; s++) { + if (*s == '\t') + indent += 8 - (indent % 8); + else + indent++; + } + for (t = s; isdigit((unsigned char)*t) || *t == ','; t++) + ; + this_is_a_command = (isdigit((unsigned char)*s) && + (*t == 'd' || *t == 'c' || *t == 'a')); + if (first_command_line < 0L && this_is_a_command) { + first_command_line = this_line; + fcl_line = p_input_line; + p_indent = indent; /* assume this for now */ + } + if (!stars_last_line && strnEQ(s, "*** ", 4)) + names[OLD_FILE].path = fetchname(s + 4, + &names[OLD_FILE].exists, strippath); + else if (strnEQ(s, "--- ", 4)) + names[NEW_FILE].path = fetchname(s + 4, + &names[NEW_FILE].exists, strippath); + else if (strnEQ(s, "+++ ", 4)) + /* pretend it is the old name */ + names[OLD_FILE].path = fetchname(s + 4, + &names[OLD_FILE].exists, strippath); + else if (strnEQ(s, "Index:", 6)) + names[INDEX_FILE].path = fetchname(s + 6, + &names[INDEX_FILE].exists, strippath); + else if (strnEQ(s, "Prereq:", 7)) { + for (t = s + 7; isspace((unsigned char)*t); t++) + ; + revision = savestr(t); + for (t = revision; *t && !isspace((unsigned char)*t); t++) + ; + *t = '\0'; + if (*revision == '\0') { + free(revision); + revision = NULL; + } + } else if (strnEQ(s, "==== ", 5)) { + /* Perforce-style diffs. */ + if ((t = strstr(s + 5, " - ")) != NULL) + p4_fetchname(&names[NEW_FILE], t + 3); + p4_fetchname(&names[OLD_FILE], s + 5); + } + if ((!diff_type || diff_type == ED_DIFF) && + first_command_line >= 0L && + strEQ(s, ".\n")) { + p_indent = indent; + p_start = first_command_line; + p_sline = fcl_line; + retval = ED_DIFF; + goto scan_exit; + } + if ((!diff_type || diff_type == UNI_DIFF) && strnEQ(s, "@@ -", 4)) { + if (strnEQ(s + 4, "0,0", 3)) + ok_to_create_file = true; + p_indent = indent; + p_start = this_line; + p_sline = p_input_line; + retval = UNI_DIFF; + goto scan_exit; + } + stars_this_line = strnEQ(s, "********", 8); + if ((!diff_type || diff_type == CONTEXT_DIFF) && stars_last_line && + strnEQ(s, "*** ", 4)) { + if (atol(s + 4) == 0) + ok_to_create_file = true; + /* + * If this is a new context diff the character just + * before the newline is a '*'. + */ + while (*s != '\n') + s++; + p_indent = indent; + p_start = previous_line; + p_sline = p_input_line - 1; + retval = (*(s - 1) == '*' ? NEW_CONTEXT_DIFF : CONTEXT_DIFF); + goto scan_exit; + } + if ((!diff_type || diff_type == NORMAL_DIFF) && + last_line_was_command && + (strnEQ(s, "< ", 2) || strnEQ(s, "> ", 2))) { + p_start = previous_line; + p_sline = p_input_line - 1; + p_indent = indent; + retval = NORMAL_DIFF; + goto scan_exit; + } + } +scan_exit: + if (retval == UNI_DIFF) { + /* unswap old and new */ + struct file_name tmp = names[OLD_FILE]; + names[OLD_FILE] = names[NEW_FILE]; + names[NEW_FILE] = tmp; + } + if (filearg[0] == NULL) { + if (posix) + filearg[0] = posix_name(names, ok_to_create_file); + else { + /* Ignore the Index: name for context diffs, like GNU */ + if (names[OLD_FILE].path != NULL || + names[NEW_FILE].path != NULL) { + free(names[INDEX_FILE].path); + names[INDEX_FILE].path = NULL; + } + filearg[0] = best_name(names, ok_to_create_file); + } + } + + free(bestguess); + bestguess = NULL; + if (filearg[0] != NULL) + bestguess = savestr(filearg[0]); + else if (!ok_to_create_file) { + /* + * We don't want to create a new file but we need a + * filename to set bestguess. Avoid setting filearg[0] + * so the file is not created automatically. + */ + if (posix) + bestguess = posix_name(names, true); + else + bestguess = best_name(names, true); + } + free(names[OLD_FILE].path); + free(names[NEW_FILE].path); + free(names[INDEX_FILE].path); + return retval; +} + +/* + * Remember where this patch ends so we know where to start up again. + */ +static void +next_intuit_at(LINENUM file_pos, LINENUM file_line) +{ + p_base = file_pos; + p_bline = file_line; +} + +/* + * Basically a verbose fseek() to the actual diff listing. + */ +static void +skip_to(LINENUM file_pos, LINENUM file_line) +{ + size_t len; + + if (p_base > file_pos) + fatal("Internal error: seek %ld>%ld\n", p_base, file_pos); + if (verbose && p_base < file_pos) { + fseek(pfp, p_base, SEEK_SET); + say("The text leading up to this was:\n--------------------------\n"); + while (ftell(pfp) < file_pos) { + len = pgets(false); + if (len == 0) + fatal("Unexpected end of file\n"); + say("|%s", buf); + } + say("--------------------------\n"); + } else + fseek(pfp, file_pos, SEEK_SET); + p_input_line = file_line - 1; +} + +/* Make this a function for better debugging. */ +static void +malformed(void) +{ + fatal("malformed patch at line %ld: %s", p_input_line, buf); + /* about as informative as "Syntax error" in C */ +} + +/* + * True if the line has been discarded (i.e. it is a line saying + * "\ No newline at end of file".) + */ +static bool +remove_special_line(void) +{ + int c; + + c = fgetc(pfp); + if (c == '\\') { + do { + c = fgetc(pfp); + } while (c != EOF && c != '\n'); + + return true; + } + if (c != EOF) + fseek(pfp, -1L, SEEK_CUR); + + return false; +} + +/* + * True if there is more of the current diff listing to process. + */ +bool +another_hunk(void) +{ + long line_beginning; /* file pos of the current line */ + LINENUM repl_beginning; /* index of --- line */ + LINENUM fillcnt; /* #lines of missing ptrn or repl */ + LINENUM fillsrc; /* index of first line to copy */ + LINENUM filldst; /* index of first missing line */ + bool ptrn_spaces_eaten; /* ptrn was slightly misformed */ + bool repl_could_be_missing; /* no + or ! lines in this hunk */ + bool repl_missing; /* we are now backtracking */ + long repl_backtrack_position; /* file pos of first repl line */ + LINENUM repl_patch_line; /* input line number for same */ + LINENUM ptrn_copiable; /* # of copiable lines in ptrn */ + char *s; + size_t len; + int context = 0; + + while (p_end >= 0) { + if (p_end == p_efake) + p_end = p_bfake; /* don't free twice */ + else + free(p_line[p_end]); + p_end--; + } + p_efake = -1; + + p_max = hunkmax; /* gets reduced when --- found */ + if (diff_type == CONTEXT_DIFF || diff_type == NEW_CONTEXT_DIFF) { + line_beginning = ftell(pfp); + repl_beginning = 0; + fillcnt = 0; + fillsrc = 0; + filldst = 0; + ptrn_spaces_eaten = false; + repl_could_be_missing = true; + repl_missing = false; + repl_backtrack_position = 0; + repl_patch_line = 0; + ptrn_copiable = 0; + + len = pgets(true); + p_input_line++; + if (len == 0 || strnNE(buf, "********", 8)) { + next_intuit_at(line_beginning, p_input_line); + return false; + } + p_context = 100; + p_hunk_beg = p_input_line + 1; + while (p_end < p_max) { + line_beginning = ftell(pfp); + len = pgets(true); + p_input_line++; + if (len == 0) { + if (p_max - p_end < 4) { + /* assume blank lines got chopped */ + strlcpy(buf, " \n", buf_size); + } else { + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + fatal("unexpected end of file in patch\n"); + } + } + p_end++; + if (p_end >= hunkmax) + fatal("Internal error: hunk larger than hunk " + "buffer size"); + p_char[p_end] = *buf; + p_line[p_end] = NULL; + switch (*buf) { + case '*': + if (strnEQ(buf, "********", 8)) { + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } else + fatal("unexpected end of hunk " + "at line %ld\n", + p_input_line); + } + if (p_end != 0) { + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + fatal("unexpected *** at line %ld: %s", + p_input_line, buf); + } + context = 0; + p_line[p_end] = savestr(buf); + if (out_of_mem) { + p_end--; + return false; + } + for (s = buf; *s && !isdigit((unsigned char)*s); s++) + ; + if (!*s) + malformed(); + if (strnEQ(s, "0,0", 3)) + memmove(s, s + 2, strlen(s + 2) + 1); + p_first = (LINENUM) atol(s); + while (isdigit((unsigned char)*s)) + s++; + if (*s == ',') { + for (; *s && !isdigit((unsigned char)*s); s++) + ; + if (!*s) + malformed(); + p_ptrn_lines = ((LINENUM) atol(s)) - p_first + 1; + } else if (p_first) + p_ptrn_lines = 1; + else { + p_ptrn_lines = 0; + p_first = 1; + } + + /* we need this much at least */ + p_max = p_ptrn_lines + 6; + while (p_max >= hunkmax) + grow_hunkmax(); + p_max = hunkmax; + break; + case '-': + if (buf[1] == '-') { + if (repl_beginning || + (p_end != p_ptrn_lines + 1 + + (p_char[p_end - 1] == '\n'))) { + if (p_end == 1) { + /* + * `old' lines were omitted; + * set up to fill them in + * from 'new' context lines. + */ + p_end = p_ptrn_lines + 1; + fillsrc = p_end + 1; + filldst = 1; + fillcnt = p_ptrn_lines; + } else { + if (repl_beginning) { + if (repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + fatal("duplicate \"---\" at line %ld--check line numbers at line %ld\n", + p_input_line, p_hunk_beg + repl_beginning); + } else { + fatal("%s \"---\" at line %ld--check line numbers at line %ld\n", + (p_end <= p_ptrn_lines + ? "Premature" + : "Overdue"), + p_input_line, p_hunk_beg); + } + } + } + repl_beginning = p_end; + repl_backtrack_position = ftell(pfp); + repl_patch_line = p_input_line; + p_line[p_end] = savestr(buf); + if (out_of_mem) { + p_end--; + return false; + } + p_char[p_end] = '='; + for (s = buf; *s && !isdigit((unsigned char)*s); s++) + ; + if (!*s) + malformed(); + p_newfirst = (LINENUM) atol(s); + while (isdigit((unsigned char)*s)) + s++; + if (*s == ',') { + for (; *s && !isdigit((unsigned char)*s); s++) + ; + if (!*s) + malformed(); + p_repl_lines = ((LINENUM) atol(s)) - + p_newfirst + 1; + } else if (p_newfirst) + p_repl_lines = 1; + else { + p_repl_lines = 0; + p_newfirst = 1; + } + p_max = p_repl_lines + p_end; + if (p_max > MAXHUNKSIZE) + fatal("hunk too large (%ld lines) at line %ld: %s", + p_max, p_input_line, buf); + while (p_max >= hunkmax) + grow_hunkmax(); + if (p_repl_lines != ptrn_copiable && + (p_context != 0 || p_repl_lines != 1)) + repl_could_be_missing = false; + break; + } + goto change_line; + case '+': + case '!': + repl_could_be_missing = false; + change_line: + if (buf[1] == '\n' && canonicalize) + strlcpy(buf + 1, " \n", buf_size - 1); + if (!isspace((unsigned char)buf[1]) && buf[1] != '>' && + buf[1] != '<' && + repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + if (context >= 0) { + if (context < p_context) + p_context = context; + context = -1000; + } + p_line[p_end] = savestr(buf + 2); + if (out_of_mem) { + p_end--; + return false; + } + if (p_end == p_ptrn_lines) { + if (remove_special_line()) { + int l; + + l = strlen(p_line[p_end]) - 1; + (p_line[p_end])[l] = 0; + } + } + break; + case '\t': + case '\n': /* assume the 2 spaces got eaten */ + if (repl_beginning && repl_could_be_missing && + (!ptrn_spaces_eaten || + diff_type == NEW_CONTEXT_DIFF)) { + repl_missing = true; + goto hunk_done; + } + p_line[p_end] = savestr(buf); + if (out_of_mem) { + p_end--; + return false; + } + if (p_end != p_ptrn_lines + 1) { + ptrn_spaces_eaten |= (repl_beginning != 0); + context++; + if (!repl_beginning) + ptrn_copiable++; + p_char[p_end] = ' '; + } + break; + case ' ': + if (!isspace((unsigned char)buf[1]) && + repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + context++; + if (!repl_beginning) + ptrn_copiable++; + p_line[p_end] = savestr(buf + 2); + if (out_of_mem) { + p_end--; + return false; + } + break; + default: + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + malformed(); + } + /* set up p_len for strncmp() so we don't have to */ + /* assume null termination */ + if (p_line[p_end]) + p_len[p_end] = strlen(p_line[p_end]); + else + p_len[p_end] = 0; + } + +hunk_done: + if (p_end >= 0 && !repl_beginning) + fatal("no --- found in patch at line %ld\n", pch_hunk_beg()); + + if (repl_missing) { + + /* reset state back to just after --- */ + p_input_line = repl_patch_line; + for (p_end--; p_end > repl_beginning; p_end--) + free(p_line[p_end]); + fseek(pfp, repl_backtrack_position, SEEK_SET); + + /* redundant 'new' context lines were omitted - set */ + /* up to fill them in from the old file context */ + if (!p_context && p_repl_lines == 1) { + p_repl_lines = 0; + p_max--; + } + fillsrc = 1; + filldst = repl_beginning + 1; + fillcnt = p_repl_lines; + p_end = p_max; + } else if (!p_context && fillcnt == 1) { + /* the first hunk was a null hunk with no context */ + /* and we were expecting one line -- fix it up. */ + while (filldst < p_end) { + p_line[filldst] = p_line[filldst + 1]; + p_char[filldst] = p_char[filldst + 1]; + p_len[filldst] = p_len[filldst + 1]; + filldst++; + } +#if 0 + repl_beginning--; /* this doesn't need to be fixed */ +#endif + p_end--; + p_first++; /* do append rather than insert */ + fillcnt = 0; + p_ptrn_lines = 0; + } + if (diff_type == CONTEXT_DIFF && + (fillcnt || (p_first > 1 && ptrn_copiable > 2 * p_context))) { + if (verbose) + say("%s\n%s\n%s\n", + "(Fascinating--this is really a new-style context diff but without", + "the telltale extra asterisks on the *** line that usually indicate", + "the new style...)"); + diff_type = NEW_CONTEXT_DIFF; + } + /* if there were omitted context lines, fill them in now */ + if (fillcnt) { + p_bfake = filldst; /* remember where not to free() */ + p_efake = filldst + fillcnt - 1; + while (fillcnt-- > 0) { + while (fillsrc <= p_end && p_char[fillsrc] != ' ') + fillsrc++; + if (fillsrc > p_end) + fatal("replacement text or line numbers mangled in hunk at line %ld\n", + p_hunk_beg); + p_line[filldst] = p_line[fillsrc]; + p_char[filldst] = p_char[fillsrc]; + p_len[filldst] = p_len[fillsrc]; + fillsrc++; + filldst++; + } + while (fillsrc <= p_end && fillsrc != repl_beginning && + p_char[fillsrc] != ' ') + fillsrc++; +#ifdef DEBUGGING + if (debug & 64) + printf("fillsrc %ld, filldst %ld, rb %ld, e+1 %ld\n", + fillsrc, filldst, repl_beginning, p_end + 1); +#endif + if (fillsrc != p_end + 1 && fillsrc != repl_beginning) + malformed(); + if (filldst != p_end + 1 && filldst != repl_beginning) + malformed(); + } + if (p_line[p_end] != NULL) { + if (remove_special_line()) { + p_len[p_end] -= 1; + (p_line[p_end])[p_len[p_end]] = 0; + } + } + } else if (diff_type == UNI_DIFF) { + LINENUM fillold; /* index of old lines */ + LINENUM fillnew; /* index of new lines */ + char ch; + + line_beginning = ftell(pfp); /* file pos of the current line */ + len = pgets(true); + p_input_line++; + if (len == 0 || strnNE(buf, "@@ -", 4)) { + next_intuit_at(line_beginning, p_input_line); + return false; + } + s = buf + 4; + if (!*s) + malformed(); + p_first = (LINENUM) atol(s); + while (isdigit((unsigned char)*s)) + s++; + if (*s == ',') { + p_ptrn_lines = (LINENUM) atol(++s); + while (isdigit((unsigned char)*s)) + s++; + } else + p_ptrn_lines = 1; + if (*s == ' ') + s++; + if (*s != '+' || !*++s) + malformed(); + p_newfirst = (LINENUM) atol(s); + while (isdigit((unsigned char)*s)) + s++; + if (*s == ',') { + p_repl_lines = (LINENUM) atol(++s); + while (isdigit((unsigned char)*s)) + s++; + } else + p_repl_lines = 1; + if (*s == ' ') + s++; + if (*s != '@') + malformed(); + if (!p_ptrn_lines) + p_first++; /* do append rather than insert */ + p_max = p_ptrn_lines + p_repl_lines + 1; + while (p_max >= hunkmax) + grow_hunkmax(); + fillold = 1; + fillnew = fillold + p_ptrn_lines; + p_end = fillnew + p_repl_lines; + snprintf(buf, buf_size, "*** %ld,%ld ****\n", p_first, + p_first + p_ptrn_lines - 1); + p_line[0] = savestr(buf); + if (out_of_mem) { + p_end = -1; + return false; + } + p_char[0] = '*'; + snprintf(buf, buf_size, "--- %ld,%ld ----\n", p_newfirst, + p_newfirst + p_repl_lines - 1); + p_line[fillnew] = savestr(buf); + if (out_of_mem) { + p_end = 0; + return false; + } + p_char[fillnew++] = '='; + p_context = 100; + context = 0; + p_hunk_beg = p_input_line + 1; + while (fillold <= p_ptrn_lines || fillnew <= p_end) { + line_beginning = ftell(pfp); + len = pgets(true); + p_input_line++; + if (len == 0) { + if (p_max - fillnew < 3) { + /* assume blank lines got chopped */ + strlcpy(buf, " \n", buf_size); + } else { + fatal("unexpected end of file in patch\n"); + } + } + if (*buf == '\t' || *buf == '\n') { + ch = ' '; /* assume the space got eaten */ + s = savestr(buf); + } else { + ch = *buf; + s = savestr(buf + 1); + } + if (out_of_mem) { + while (--fillnew > p_ptrn_lines) + free(p_line[fillnew]); + p_end = fillold - 1; + return false; + } + switch (ch) { + case '-': + if (fillold > p_ptrn_lines) { + free(s); + p_end = fillnew - 1; + malformed(); + } + p_char[fillold] = ch; + p_line[fillold] = s; + p_len[fillold++] = strlen(s); + if (fillold > p_ptrn_lines) { + if (remove_special_line()) { + p_len[fillold - 1] -= 1; + s[p_len[fillold - 1]] = 0; + } + } + break; + case '=': + ch = ' '; + /* FALL THROUGH */ + case ' ': + if (fillold > p_ptrn_lines) { + free(s); + while (--fillnew > p_ptrn_lines) + free(p_line[fillnew]); + p_end = fillold - 1; + malformed(); + } + context++; + p_char[fillold] = ch; + p_line[fillold] = s; + p_len[fillold++] = strlen(s); + s = savestr(s); + if (out_of_mem) { + while (--fillnew > p_ptrn_lines) + free(p_line[fillnew]); + p_end = fillold - 1; + return false; + } + if (fillold > p_ptrn_lines) { + if (remove_special_line()) { + p_len[fillold - 1] -= 1; + s[p_len[fillold - 1]] = 0; + } + } + /* FALL THROUGH */ + case '+': + if (fillnew > p_end) { + free(s); + while (--fillnew > p_ptrn_lines) + free(p_line[fillnew]); + p_end = fillold - 1; + malformed(); + } + p_char[fillnew] = ch; + p_line[fillnew] = s; + p_len[fillnew++] = strlen(s); + if (fillold > p_ptrn_lines) { + if (remove_special_line()) { + p_len[fillnew - 1] -= 1; + s[p_len[fillnew - 1]] = 0; + } + } + break; + default: + p_end = fillnew; + malformed(); + } + if (ch != ' ' && context > 0) { + if (context < p_context) + p_context = context; + context = -1000; + } + } /* while */ + } else { /* normal diff--fake it up */ + char hunk_type; + int i; + LINENUM min, max; + + line_beginning = ftell(pfp); + p_context = 0; + len = pgets(true); + p_input_line++; + if (len == 0 || !isdigit((unsigned char)*buf)) { + next_intuit_at(line_beginning, p_input_line); + return false; + } + p_first = (LINENUM) atol(buf); + for (s = buf; isdigit((unsigned char)*s); s++) + ; + if (*s == ',') { + p_ptrn_lines = (LINENUM) atol(++s) - p_first + 1; + while (isdigit((unsigned char)*s)) + s++; + } else + p_ptrn_lines = (*s != 'a'); + hunk_type = *s; + if (hunk_type == 'a') + p_first++; /* do append rather than insert */ + min = (LINENUM) atol(++s); + for (; isdigit((unsigned char)*s); s++) + ; + if (*s == ',') + max = (LINENUM) atol(++s); + else + max = min; + if (hunk_type == 'd') + min++; + p_end = p_ptrn_lines + 1 + max - min + 1; + if (p_end > MAXHUNKSIZE) + fatal("hunk too large (%ld lines) at line %ld: %s", + p_end, p_input_line, buf); + while (p_end >= hunkmax) + grow_hunkmax(); + p_newfirst = min; + p_repl_lines = max - min + 1; + snprintf(buf, buf_size, "*** %ld,%ld\n", p_first, + p_first + p_ptrn_lines - 1); + p_line[0] = savestr(buf); + if (out_of_mem) { + p_end = -1; + return false; + } + p_char[0] = '*'; + for (i = 1; i <= p_ptrn_lines; i++) { + len = pgets(true); + p_input_line++; + if (len == 0) + fatal("unexpected end of file in patch at line %ld\n", + p_input_line); + if (*buf != '<') + fatal("< expected at line %ld of patch\n", + p_input_line); + p_line[i] = savestr(buf + 2); + if (out_of_mem) { + p_end = i - 1; + return false; + } + p_len[i] = strlen(p_line[i]); + p_char[i] = '-'; + } + + if (remove_special_line()) { + p_len[i - 1] -= 1; + (p_line[i - 1])[p_len[i - 1]] = 0; + } + if (hunk_type == 'c') { + len = pgets(true); + p_input_line++; + if (len == 0) + fatal("unexpected end of file in patch at line %ld\n", + p_input_line); + if (*buf != '-') + fatal("--- expected at line %ld of patch\n", + p_input_line); + } + snprintf(buf, buf_size, "--- %ld,%ld\n", min, max); + p_line[i] = savestr(buf); + if (out_of_mem) { + p_end = i - 1; + return false; + } + p_char[i] = '='; + for (i++; i <= p_end; i++) { + len = pgets(true); + p_input_line++; + if (len == 0) + fatal("unexpected end of file in patch at line %ld\n", + p_input_line); + if (*buf != '>') + fatal("> expected at line %ld of patch\n", + p_input_line); + p_line[i] = savestr(buf + 2); + if (out_of_mem) { + p_end = i - 1; + return false; + } + p_len[i] = strlen(p_line[i]); + p_char[i] = '+'; + } + + if (remove_special_line()) { + p_len[i - 1] -= 1; + (p_line[i - 1])[p_len[i - 1]] = 0; + } + } + if (reverse) /* backwards patch? */ + if (!pch_swap()) + say("Not enough memory to swap next hunk!\n"); +#ifdef DEBUGGING + if (debug & 2) { + int i; + char special; + + for (i = 0; i <= p_end; i++) { + if (i == p_ptrn_lines) + special = '^'; + else + special = ' '; + fprintf(stderr, "%3d %c %c %s", i, p_char[i], + special, p_line[i]); + fflush(stderr); + } + } +#endif + if (p_end + 1 < hunkmax)/* paranoia reigns supreme... */ + p_char[p_end + 1] = '^'; /* add a stopper for apply_hunk */ + return true; +} + +/* + * Input a line from the patch file. + * Worry about indentation if do_indent is true. + * The line is read directly into the buf global variable which + * is resized if necessary in order to hold the complete line. + * Returns the number of characters read including the terminating + * '\n', if any. + */ +size_t +pgets(bool do_indent) +{ + char *line; + size_t len; + int indent = 0, skipped = 0; + + line = fgetln(pfp, &len); + if (line != NULL) { + if (len + 1 > buf_size) { + while (len + 1 > buf_size) + buf_size *= 2; + free(buf); + buf = malloc(buf_size); + if (buf == NULL) + fatal("out of memory\n"); + } + if (do_indent == 1 && p_indent) { + for (; + indent < p_indent && (*line == ' ' || *line == '\t' || *line == 'X'); + line++, skipped++) { + if (*line == '\t') + indent += 8 - (indent %7); + else + indent++; + } + } + strncpy(buf, line, len - skipped); + buf[len - skipped] = '\0'; + } + return len; +} + + +/* + * Reverse the old and new portions of the current hunk. + */ +bool +pch_swap(void) +{ + char **tp_line; /* the text of the hunk */ + short *tp_len; /* length of each line */ + char *tp_char; /* +, -, and ! */ + LINENUM i; + LINENUM n; + bool blankline = false; + char *s; + + i = p_first; + p_first = p_newfirst; + p_newfirst = i; + + /* make a scratch copy */ + + tp_line = p_line; + tp_len = p_len; + tp_char = p_char; + p_line = NULL; /* force set_hunkmax to allocate again */ + p_len = NULL; + p_char = NULL; + set_hunkmax(); + if (p_line == NULL || p_len == NULL || p_char == NULL) { + + free(p_line); + p_line = tp_line; + free(p_len); + p_len = tp_len; + free(p_char); + p_char = tp_char; + return false; /* not enough memory to swap hunk! */ + } + /* now turn the new into the old */ + + i = p_ptrn_lines + 1; + if (tp_char[i] == '\n') { /* account for possible blank line */ + blankline = true; + i++; + } + if (p_efake >= 0) { /* fix non-freeable ptr range */ + if (p_efake <= i) + n = p_end - i + 1; + else + n = -i; + p_efake += n; + p_bfake += n; + } + for (n = 0; i <= p_end; i++, n++) { + p_line[n] = tp_line[i]; + p_char[n] = tp_char[i]; + if (p_char[n] == '+') + p_char[n] = '-'; + p_len[n] = tp_len[i]; + } + if (blankline) { + i = p_ptrn_lines + 1; + p_line[n] = tp_line[i]; + p_char[n] = tp_char[i]; + p_len[n] = tp_len[i]; + n++; + } + if (p_char[0] != '=') + fatal("Malformed patch at line %ld: expected '=' found '%c'\n", + p_input_line, p_char[0]); + p_char[0] = '*'; + for (s = p_line[0]; *s; s++) + if (*s == '-') + *s = '*'; + + /* now turn the old into the new */ + + if (p_char[0] != '*') + fatal("Malformed patch at line %ld: expected '*' found '%c'\n", + p_input_line, p_char[0]); + tp_char[0] = '='; + for (s = tp_line[0]; *s; s++) + if (*s == '*') + *s = '-'; + for (i = 0; n <= p_end; i++, n++) { + p_line[n] = tp_line[i]; + p_char[n] = tp_char[i]; + if (p_char[n] == '-') + p_char[n] = '+'; + p_len[n] = tp_len[i]; + } + + if (i != p_ptrn_lines + 1) + fatal("Malformed patch at line %ld: expected %ld lines, " + "got %ld\n", + p_input_line, p_ptrn_lines + 1, i); + + i = p_ptrn_lines; + p_ptrn_lines = p_repl_lines; + p_repl_lines = i; + + free(tp_line); + free(tp_len); + free(tp_char); + + return true; +} + +/* + * Return the specified line position in the old file of the old context. + */ +LINENUM +pch_first(void) +{ + return p_first; +} + +/* + * Return the number of lines of old context. + */ +LINENUM +pch_ptrn_lines(void) +{ + return p_ptrn_lines; +} + +/* + * Return the probable line position in the new file of the first line. + */ +LINENUM +pch_newfirst(void) +{ + return p_newfirst; +} + +/* + * Return the number of lines in the replacement text including context. + */ +LINENUM +pch_repl_lines(void) +{ + return p_repl_lines; +} + +/* + * Return the number of lines in the whole hunk. + */ +LINENUM +pch_end(void) +{ + return p_end; +} + +/* + * Return the number of context lines before the first changed line. + */ +LINENUM +pch_context(void) +{ + return p_context; +} + +/* + * Return the length of a particular patch line. + */ +short +pch_line_len(LINENUM line) +{ + return p_len[line]; +} + +/* + * Return the control character (+, -, *, !, etc) for a patch line. + */ +char +pch_char(LINENUM line) +{ + return p_char[line]; +} + +/* + * Return a pointer to a particular patch line. + */ +char * +pfetch(LINENUM line) +{ + return p_line[line]; +} + +/* + * Return where in the patch file this hunk began, for error messages. + */ +LINENUM +pch_hunk_beg(void) +{ + return p_hunk_beg; +} + +/* + * Apply an ed script by feeding ed itself. + */ +void +do_ed_script(void) +{ + char *t; + long beginning_of_this_line; + FILE *pipefp = NULL; + + if (!skip_rest_of_patch) { + if (copy_file(filearg[0], TMPOUTNAME) < 0) { + unlink(TMPOUTNAME); + fatal("can't create temp file %s", TMPOUTNAME); + } + snprintf(buf, buf_size, "%s%s%s", _PATH_ED, + verbose ? " " : " -s ", TMPOUTNAME); + pipefp = popen(buf, "w"); + } + for (;;) { + beginning_of_this_line = ftell(pfp); + if (pgets(true) == 0) { + next_intuit_at(beginning_of_this_line, p_input_line); + break; + } + p_input_line++; + for (t = buf; isdigit((unsigned char)*t) || *t == ','; t++) + ; + /* POSIX defines allowed commands as {a,c,d,i,s} */ + if (isdigit((unsigned char)*buf) && (*t == 'a' || *t == 'c' || + *t == 'd' || *t == 'i' || *t == 's')) { + if (pipefp != NULL) + fputs(buf, pipefp); + if (*t != 'd') { + while (pgets(true)) { + p_input_line++; + if (pipefp != NULL) + fputs(buf, pipefp); + if (strEQ(buf, ".\n")) + break; + } + } + } else { + next_intuit_at(beginning_of_this_line, p_input_line); + break; + } + } + if (pipefp == NULL) + return; + fprintf(pipefp, "w\n"); + fprintf(pipefp, "q\n"); + fflush(pipefp); + pclose(pipefp); + ignore_signals(); + if (!check_only) { + if (move_file(TMPOUTNAME, outname) < 0) { + toutkeep = true; + chmod(TMPOUTNAME, filemode); + } else + chmod(outname, filemode); + } + set_signals(1); +} + +/* + * Choose the name of the file to be patched based on POSIX rules. + * NOTE: the POSIX rules are amazingly stupid and we only follow them + * if the user specified --posix or set POSIXLY_CORRECT. + */ +static char * +posix_name(const struct file_name *names, bool assume_exists) +{ + char *path = NULL; + int i; + + /* + * POSIX states that the filename will be chosen from one + * of the old, new and index names (in that order) if + * the file exists relative to CWD after -p stripping. + */ + for (i = 0; i < MAX_FILE; i++) { + if (names[i].path != NULL && names[i].exists) { + path = names[i].path; + break; + } + } + if (path == NULL && !assume_exists) { + /* + * No files found, look for something we can checkout from + * RCS/SCCS dirs. Same order as above. + */ + for (i = 0; i < MAX_FILE; i++) { + if (names[i].path != NULL && + (path = checked_in(names[i].path)) != NULL) + break; + } + /* + * Still no match? Check to see if the diff could be creating + * a new file. + */ + if (path == NULL && ok_to_create_file && + names[NEW_FILE].path != NULL) + path = names[NEW_FILE].path; + } + + return path ? savestr(path) : NULL; +} + +/* + * Choose the name of the file to be patched based the "best" one + * available. + */ +static char * +best_name(const struct file_name *names, bool assume_exists) +{ + size_t min_components, min_baselen, min_len, tmp; + char *best = NULL; + int i; + + /* + * The "best" name is the one with the fewest number of path + * components, the shortest basename length, and the shortest + * overall length (in that order). We only use the Index: file + * if neither of the old or new files could be intuited from + * the diff header. + */ + min_components = min_baselen = min_len = SIZE_MAX; + for (i = INDEX_FILE; i >= OLD_FILE; i--) { + if (names[i].path == NULL || + (!names[i].exists && !assume_exists)) + continue; + if ((tmp = num_components(names[i].path)) > min_components) + continue; + min_components = tmp; + if ((tmp = strlen(basename(names[i].path))) > min_baselen) + continue; + min_baselen = tmp; + if ((tmp = strlen(names[i].path)) > min_len) + continue; + min_len = tmp; + best = names[i].path; + } + if (best == NULL) { + /* + * No files found, look for something we can checkout from + * RCS/SCCS dirs. Logic is identical to that above... + */ + min_components = min_baselen = min_len = SIZE_MAX; + for (i = INDEX_FILE; i >= OLD_FILE; i--) { + if (names[i].path == NULL || + checked_in(names[i].path) == NULL) + continue; + if ((tmp = num_components(names[i].path)) > min_components) + continue; + min_components = tmp; + if ((tmp = strlen(basename(names[i].path))) > min_baselen) + continue; + min_baselen = tmp; + if ((tmp = strlen(names[i].path)) > min_len) + continue; + min_len = tmp; + best = names[i].path; + } + /* + * Still no match? Check to see if the diff could be creating + * a new file. + */ + if (best == NULL && ok_to_create_file && + names[NEW_FILE].path != NULL) + best = names[NEW_FILE].path; + } + + return best ? savestr(best) : NULL; +} + +static size_t +num_components(const char *path) +{ + size_t n; + const char *cp; + + for (n = 0, cp = path; (cp = strchr(cp, '/')) != NULL; n++, cp++) { + while (*cp == '/') + cp++; /* skip consecutive slashes */ + } + return n; +} diff --git a/usr.bin/patch/pch.h b/usr.bin/patch/pch.h new file mode 100644 index 0000000..42910c2 --- /dev/null +++ b/usr.bin/patch/pch.h @@ -0,0 +1,56 @@ +/*- + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * patch - a program to apply diffs to original files + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + * + * $OpenBSD: pch.h,v 1.9 2003/10/31 20:20:45 millert Exp $ + * $FreeBSD$ + */ + +#define OLD_FILE 0 +#define NEW_FILE 1 +#define INDEX_FILE 2 +#define MAX_FILE 3 + +struct file_name { + char *path; + bool exists; +}; + +void re_patch(void); +void open_patch_file(const char *); +void set_hunkmax(void); +bool there_is_another_patch(void); +bool another_hunk(void); +bool pch_swap(void); +char *pfetch(LINENUM); +short pch_line_len(LINENUM); +LINENUM pch_first(void); +LINENUM pch_ptrn_lines(void); +LINENUM pch_newfirst(void); +LINENUM pch_repl_lines(void); +LINENUM pch_end(void); +LINENUM pch_context(void); +LINENUM pch_hunk_beg(void); +char pch_char(LINENUM); +void do_ed_script(void); diff --git a/usr.bin/patch/util.c b/usr.bin/patch/util.c new file mode 100644 index 0000000..3895b2c --- /dev/null +++ b/usr.bin/patch/util.c @@ -0,0 +1,432 @@ +/*- + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * patch - a program to apply diffs to original files + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + * + * $OpenBSD: util.c,v 1.35 2010/07/24 01:10:12 ray Exp $ + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <paths.h> +#include <signal.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "common.h" +#include "util.h" +#include "backupfile.h" +#include "pathnames.h" + +/* Rename a file, copying it if necessary. */ + +int +move_file(const char *from, const char *to) +{ + int fromfd; + ssize_t i; + + /* to stdout? */ + + if (strEQ(to, "-")) { +#ifdef DEBUGGING + if (debug & 4) + say("Moving %s to stdout.\n", from); +#endif + fromfd = open(from, O_RDONLY); + if (fromfd < 0) + pfatal("internal error, can't reopen %s", from); + while ((i = read(fromfd, buf, buf_size)) > 0) + if (write(STDOUT_FILENO, buf, i) != i) + pfatal("write failed"); + close(fromfd); + return 0; + } + if (backup_file(to) < 0) { + say("Can't backup %s, output is in %s: %s\n", to, from, + strerror(errno)); + return -1; + } +#ifdef DEBUGGING + if (debug & 4) + say("Moving %s to %s.\n", from, to); +#endif + if (rename(from, to) < 0) { + if (errno != EXDEV || copy_file(from, to) < 0) { + say("Can't create %s, output is in %s: %s\n", + to, from, strerror(errno)); + return -1; + } + } + return 0; +} + +/* Backup the original file. */ + +int +backup_file(const char *orig) +{ + struct stat filestat; + char bakname[MAXPATHLEN], *s, *simplename; + dev_t orig_device; + ino_t orig_inode; + + if (backup_type == none || stat(orig, &filestat) != 0) + return 0; /* nothing to do */ + /* + * If the user used zero prefixes or suffixes, then + * he doesn't want backups. Yet we have to remove + * orig to break possible hardlinks. + */ + if ((origprae && *origprae == 0) || *simple_backup_suffix == 0) { + unlink(orig); + return 0; + } + orig_device = filestat.st_dev; + orig_inode = filestat.st_ino; + + if (origprae) { + if (strlcpy(bakname, origprae, sizeof(bakname)) >= sizeof(bakname) || + strlcat(bakname, orig, sizeof(bakname)) >= sizeof(bakname)) + fatal("filename %s too long for buffer\n", origprae); + } else { + if ((s = find_backup_file_name(orig)) == NULL) + fatal("out of memory\n"); + if (strlcpy(bakname, s, sizeof(bakname)) >= sizeof(bakname)) + fatal("filename %s too long for buffer\n", s); + free(s); + } + + if ((simplename = strrchr(bakname, '/')) != NULL) + simplename = simplename + 1; + else + simplename = bakname; + + /* + * Find a backup name that is not the same file. Change the + * first lowercase char into uppercase; if that isn't + * sufficient, chop off the first char and try again. + */ + while (stat(bakname, &filestat) == 0 && + orig_device == filestat.st_dev && orig_inode == filestat.st_ino) { + /* Skip initial non-lowercase chars. */ + for (s = simplename; *s && !islower((unsigned char)*s); s++) + ; + if (*s) + *s = toupper((unsigned char)*s); + else + memmove(simplename, simplename + 1, + strlen(simplename + 1) + 1); + } +#ifdef DEBUGGING + if (debug & 4) + say("Moving %s to %s.\n", orig, bakname); +#endif + if (rename(orig, bakname) < 0) { + if (errno != EXDEV || copy_file(orig, bakname) < 0) + return -1; + } + return 0; +} + +/* + * Copy a file. + */ +int +copy_file(const char *from, const char *to) +{ + int tofd, fromfd; + ssize_t i; + + tofd = open(to, O_CREAT|O_TRUNC|O_WRONLY, 0666); + if (tofd < 0) + return -1; + fromfd = open(from, O_RDONLY, 0); + if (fromfd < 0) + pfatal("internal error, can't reopen %s", from); + while ((i = read(fromfd, buf, buf_size)) > 0) + if (write(tofd, buf, i) != i) + pfatal("write to %s failed", to); + close(fromfd); + close(tofd); + return 0; +} + +/* + * Allocate a unique area for a string. + */ +char * +savestr(const char *s) +{ + char *rv; + + if (!s) + s = "Oops"; + rv = strdup(s); + if (rv == NULL) { + if (using_plan_a) + out_of_mem = true; + else + fatal("out of memory\n"); + } + return rv; +} + +/* + * Vanilla terminal output (buffered). + */ +void +say(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + fflush(stdout); +} + +/* + * Terminal output, pun intended. + */ +void +fatal(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "patch: **** "); + vfprintf(stderr, fmt, ap); + va_end(ap); + my_exit(2); +} + +/* + * Say something from patch, something from the system, then silence . . . + */ +void +pfatal(const char *fmt, ...) +{ + va_list ap; + int errnum = errno; + + fprintf(stderr, "patch: **** "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(errnum)); + my_exit(2); +} + +/* + * Get a response from the user via /dev/tty + */ +void +ask(const char *fmt, ...) +{ + va_list ap; + ssize_t nr = 0; + static int ttyfd = -1; + + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + fflush(stdout); + if (ttyfd < 0) + ttyfd = open(_PATH_TTY, O_RDONLY); + if (ttyfd >= 0) { + if ((nr = read(ttyfd, buf, buf_size)) > 0 && + buf[nr - 1] == '\n') + buf[nr - 1] = '\0'; + } + if (ttyfd < 0 || nr <= 0) { + /* no tty or error reading, pretend user entered 'return' */ + putchar('\n'); + buf[0] = '\0'; + } +} + +/* + * How to handle certain events when not in a critical region. + */ +void +set_signals(int reset) +{ + static sig_t hupval, intval; + + if (!reset) { + hupval = signal(SIGHUP, SIG_IGN); + if (hupval != SIG_IGN) + hupval = my_exit; + intval = signal(SIGINT, SIG_IGN); + if (intval != SIG_IGN) + intval = my_exit; + } + signal(SIGHUP, hupval); + signal(SIGINT, intval); +} + +/* + * How to handle certain events when in a critical region. + */ +void +ignore_signals(void) +{ + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); +} + +/* + * Make sure we'll have the directories to create a file. If `striplast' is + * true, ignore the last element of `filename'. + */ + +void +makedirs(const char *filename, bool striplast) +{ + char *tmpbuf; + + if ((tmpbuf = strdup(filename)) == NULL) + fatal("out of memory\n"); + + if (striplast) { + char *s = strrchr(tmpbuf, '/'); + if (s == NULL) { + free(tmpbuf); + return; /* nothing to be done */ + } + *s = '\0'; + } + if (mkpath(tmpbuf) != 0) + pfatal("creation of %s failed", tmpbuf); + free(tmpbuf); +} + +/* + * Make filenames more reasonable. + */ +char * +fetchname(const char *at, bool *exists, int strip_leading) +{ + char *fullname, *name, *t; + int sleading, tab; + struct stat filestat; + + if (at == NULL || *at == '\0') + return NULL; + while (isspace((unsigned char)*at)) + at++; +#ifdef DEBUGGING + if (debug & 128) + say("fetchname %s %d\n", at, strip_leading); +#endif + /* So files can be created by diffing against /dev/null. */ + if (strnEQ(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL) - 1)) + return NULL; + name = fullname = t = savestr(at); + + tab = strchr(t, '\t') != NULL; + /* Strip off up to `strip_leading' path components and NUL terminate. */ + for (sleading = strip_leading; *t != '\0' && ((tab && *t != '\t') || + !isspace((unsigned char)*t)); t++) { + if (t[0] == '/' && t[1] != '/' && t[1] != '\0') + if (--sleading >= 0) + name = t + 1; + } + *t = '\0'; + + /* + * If no -p option was given (957 is the default value!), we were + * given a relative pathname, and the leading directories that we + * just stripped off all exist, put them back on. + */ + if (strip_leading == 957 && name != fullname && *fullname != '/') { + name[-1] = '\0'; + if (stat(fullname, &filestat) == 0 && S_ISDIR(filestat.st_mode)) { + name[-1] = '/'; + name = fullname; + } + } + name = savestr(name); + free(fullname); + + *exists = stat(name, &filestat) == 0; + return name; +} + +/* + * Takes the name returned by fetchname and looks in RCS/SCCS directories + * for a checked in version. + */ +char * +checked_in(char *file) +{ + char *filebase, *filedir, tmpbuf[MAXPATHLEN]; + struct stat filestat; + + filebase = basename(file); + filedir = dirname(file); + +#define try(f, a1, a2, a3) \ +(snprintf(tmpbuf, sizeof tmpbuf, f, a1, a2, a3), stat(tmpbuf, &filestat) == 0) + + if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || + try("%s/RCS/%s%s", filedir, filebase, "") || + try("%s/%s%s", filedir, filebase, RCSSUFFIX) || + try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) || + try("%s/%s%s", filedir, SCCSPREFIX, filebase)) + return file; + + return NULL; +} + +void +version(void) +{ + fprintf(stderr, "patch (BSD patch) 2.0-FreeBSD\n"); + my_exit(EXIT_SUCCESS); +} + +/* + * Exit with cleanup. + */ +void +my_exit(int status) +{ + unlink(TMPINNAME); + if (!toutkeep) + unlink(TMPOUTNAME); + if (!trejkeep) + unlink(TMPREJNAME); + unlink(TMPPATNAME); + exit(status); +} diff --git a/usr.bin/patch/util.h b/usr.bin/patch/util.h new file mode 100644 index 0000000..5759d68 --- /dev/null +++ b/usr.bin/patch/util.h @@ -0,0 +1,51 @@ +/*- + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * patch - a program to apply diffs to original files + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + * + * $OpenBSD: util.h,v 1.15 2005/06/20 07:14:06 otto Exp $ + * $FreeBSD$ + */ + +char *fetchname(const char *, bool *, int); +char *checked_in(char *); +int backup_file(const char *); +int move_file(const char *, const char *); +int copy_file(const char *, const char *); +void say(const char *, ...) + __attribute__((__format__(__printf__, 1, 2))); +void fatal(const char *, ...) + __attribute__((__format__(__printf__, 1, 2))); +void pfatal(const char *, ...) + __attribute__((__format__(__printf__, 1, 2))); +void ask(const char *, ...) + __attribute__((__format__(__printf__, 1, 2))); +char *savestr(const char *); +void set_signals(int); +void ignore_signals(void); +void makedirs(const char *, bool); +void version(void); +void my_exit(int) __attribute__((noreturn)); + +/* in mkpath.c */ +extern int mkpath(char *); diff --git a/usr.bin/pr/pr.1 b/usr.bin/pr/pr.1 index 035958e..7beb00e 100644 --- a/usr.bin/pr/pr.1 +++ b/usr.bin/pr/pr.1 @@ -361,9 +361,7 @@ and require that both arguments, if present, not be separated from the option letter. .Sh EXIT STATUS -The -.Nm -utility exits 0 on success, and 1 if an error occurs. +.Ex -std .Sh DIAGNOSTICS If .Nm diff --git a/usr.bin/printf/printf.c b/usr.bin/printf/printf.c index 4b09342..2368f184 100644 --- a/usr.bin/printf/printf.c +++ b/usr.bin/printf/printf.c @@ -473,7 +473,7 @@ getnum(intmax_t *ip, uintmax_t *uip, int signedconv) int rval; if (!*gargv) { - *ip = 0; + *ip = *uip = 0; return (0); } if (**gargv == '"' || **gargv == '\'') { diff --git a/usr.bin/procstat/procstat.c b/usr.bin/procstat/procstat.c index b0cee6b..934e292 100644 --- a/usr.bin/procstat/procstat.c +++ b/usr.bin/procstat/procstat.c @@ -216,8 +216,8 @@ main(int argc, char *argv[]) argv += optind; /* We require that either 0 or 1 mode flags be set. */ - tmp = bflag + cflag + eflag + fflag + (kflag ? 1 : 0) + lflag + sflag + - tflag + vflag + xflag; + tmp = bflag + cflag + eflag + fflag + iflag + jflag + (kflag ? 1 : 0) + + lflag + sflag + tflag + vflag + xflag; if (!(tmp == 0 || tmp == 1)) usage(); diff --git a/usr.bin/rctl/rctl.8 b/usr.bin/rctl/rctl.8 index e570f57..556c73a 100644 --- a/usr.bin/rctl/rctl.8 +++ b/usr.bin/rctl/rctl.8 @@ -25,7 +25,7 @@ .\" .\" $FreeBSD$ .\" -.Dd October 26, 2012 +.Dd December 3, 2012 .Dt RCTL 8 .Os .Sh NAME @@ -169,21 +169,17 @@ Note that limiting RSS may kill the machine due to thrashing. .Sh EXIT STATUS .Ex -std .Sh EXAMPLES -.Dl rctl -a user:joe:vmemoryuse:deny=1g +Prevent user "joe" from allocating more than 1GB of virtual memory: +.Dl Nm Fl a Ar user:joe:vmemoryuse:deny=1g .Pp -Prevent user "joe" from allocating more than 1GB of virtual memory. +Remove all RCTL rules: +.Dl Nm Fl r Ar \&: .Pp -.Dl rctl -r : +Display resource usage information for jail named "www": +.Dl Nm Fl hu Ar jail:www .Pp -Remove all RCTL rules. -.Pp -.Dl rctl -hu jail:www -.Pp -Display resource usage information for jail named "www". -.Pp -.Dl rctl -l process:512 -.Pp -Display all the rules applicable to process with PID 512. +Display all the rules applicable to process with PID 512: +.Dl Nm Fl l Ar process:512 .Sh SEE ALSO .Xr rctl.conf 5 .Sh HISTORY diff --git a/usr.bin/seq/seq.1 b/usr.bin/seq/seq.1 index e3bccee..1ac977e 100644 --- a/usr.bin/seq/seq.1 +++ b/usr.bin/seq/seq.1 @@ -127,10 +127,8 @@ If any sequence numbers will be printed in exponential notation, the default conversion is changed to .Cm %e . .El -.Pp -The -.Nm -utility exits 0 on success and non-zero if an error occurs. +.Sh EXIT STATUS +.Ex -std .Sh EXAMPLES .Bd -literal -offset indent # seq 1 3 diff --git a/usr.bin/sort/sort.c b/usr.bin/sort/sort.c index 9277de0..7e0ab69 100644 --- a/usr.bin/sort/sort.c +++ b/usr.bin/sort/sort.c @@ -103,7 +103,7 @@ bool debug_sort; bool need_hint; #if defined(SORT_THREADS) -size_t ncpu = 1; +unsigned int ncpu = 1; size_t nthreads = 1; #endif @@ -265,31 +265,27 @@ read_fns_from_file0(const char *fn) static void set_hw_params(void) { -#if defined(SORT_THREADS) - size_t ncpusz; -#endif - size_t pages, psize, psz, pszsz; + long pages, psize; pages = psize = 0; + #if defined(SORT_THREADS) ncpu = 1; - ncpusz = sizeof(size_t); #endif - psz = pszsz = sizeof(size_t); - if (sysctlbyname("vm.stats.vm.v_free_count", &pages, &psz, - NULL, 0) < 0) { - perror("vm.stats.vm.v_free_count"); - return; + pages = sysconf(_SC_PHYS_PAGES); + if (pages < 1) { + perror("sysconf pages"); + psize = 1; } - if (sysctlbyname("vm.stats.vm.v_page_size", &psize, &pszsz, - NULL, 0) < 0) { - perror("vm.stats.vm.v_page_size"); - return; + psize = sysconf(_SC_PAGESIZE); + if (psize < 1) { + perror("sysconf psize"); + psize = 4096; } #if defined(SORT_THREADS) - if (sysctlbyname("hw.ncpu", &ncpu, &ncpusz, - NULL, 0) < 0) + ncpu = (unsigned int)sysconf(_SC_NPROCESSORS_ONLN); + if (ncpu < 1) ncpu = 1; else if(ncpu > 32) ncpu = 32; @@ -298,7 +294,10 @@ set_hw_params(void) #endif free_memory = (unsigned long long) pages * (unsigned long long) psize; - available_free_memory = (free_memory * 9) / 10; + available_free_memory = free_memory / 2; + + if (available_free_memory < 1024) + available_free_memory = 1024; } /* @@ -435,8 +434,7 @@ parse_memory_buffer_value(const char *value) 100; break; default: - fprintf(stderr, "%s: %s\n", strerror(EINVAL), - optarg); + warnc(EINVAL, "%s", optarg); membuf = available_free_memory; } } @@ -659,7 +657,7 @@ parse_pos(const char *s, struct key_specs *ks, bool *mef_flags, bool second) errno = 0; ks->f2 = (size_t) strtoul(f, NULL, 10); if (errno != 0) - errx(2, "%s: -k", strerror(errno)); + err(2, "-k"); if (ks->f2 == 0) { warn("%s",getstr(5)); goto end; @@ -668,7 +666,7 @@ parse_pos(const char *s, struct key_specs *ks, bool *mef_flags, bool second) errno = 0; ks->f1 = (size_t) strtoul(f, NULL, 10); if (errno != 0) - errx(2, "%s: -k", strerror(errno)); + err(2, "-k"); if (ks->f1 == 0) { warn("%s",getstr(5)); goto end; @@ -686,12 +684,12 @@ parse_pos(const char *s, struct key_specs *ks, bool *mef_flags, bool second) errno = 0; ks->c2 = (size_t) strtoul(c, NULL, 10); if (errno != 0) - errx(2, "%s: -k", strerror(errno)); + err(2, "-k"); } else { errno = 0; ks->c1 = (size_t) strtoul(c, NULL, 10); if (errno != 0) - errx(2, "%s: -k", strerror(errno)); + err(2, "-k"); if (ks->c1 == 0) { warn("%s",getstr(6)); goto end; @@ -988,21 +986,6 @@ main(int argc, char **argv) set_tmpdir(); set_sort_opts(); -#if 0 - { - static int counter = 0; - char fn[128]; - sprintf(fn, "/var/tmp/debug.sort.%d", counter++); - FILE* f = fopen(fn, "w"); - fprintf(f, ">>sort>>"); - for (int i = 0; i < argc; i++) { - fprintf(f, "<%s>", argv[i]); - } - fprintf(f, "<<sort<<\n"); - fclose(f); - } -#endif - fix_obsolete_keys(&argc, argv); while (((c = getopt_long(argc, argv, OPTIONS, long_options, NULL)) @@ -1042,8 +1025,7 @@ main(int argc, char **argv) if (parse_k(optarg, &(keys[keys_num - 1])) < 0) { - errx(2, "%s: -k %s\n", - strerror(EINVAL), optarg); + errc(2, EINVAL, "-k %s", optarg); } break; @@ -1068,8 +1050,7 @@ main(int argc, char **argv) case 't': while (strlen(optarg) > 1) { if (optarg[0] != '\\') { - errx(2, "%s: %s\n", - strerror(EINVAL), optarg); + errc(2, EINVAL, "%s", optarg); } optarg += 1; if (*optarg == '0') { @@ -1156,8 +1137,7 @@ main(int argc, char **argv) errno = 0; long mof = strtol(optarg, NULL, 10); if (errno != 0) - errx(2, "--batch-size: %s", - strerror(errno)); + err(2, "--batch-size"); if (mof >= 2) max_open_files = (size_t) mof + 1; } @@ -1227,7 +1207,9 @@ main(int argc, char **argv) } if (debug_sort) { + printf("Memory to be used for sorting: %llu\n",available_free_memory); #if defined(SORT_THREADS) + printf("Number of CPUs: %d\n",(int)ncpu); nthreads = 1; #endif printf("Using collate rules of %s locale\n", diff --git a/usr.bin/sort/sort.h b/usr.bin/sort/sort.h index f5b48bd..a37f922 100644 --- a/usr.bin/sort/sort.h +++ b/usr.bin/sort/sort.h @@ -54,7 +54,7 @@ extern const char *nlsstr[]; #if defined(SORT_THREADS) #define MT_SORT_THRESHOLD (10000) -extern size_t ncpu; +extern unsigned int ncpu; extern size_t nthreads; #endif diff --git a/usr.bin/ssh-copy-id/ssh-copy-id.1 b/usr.bin/ssh-copy-id/ssh-copy-id.1 index 2b2bbc2..2c95d5a 100644 --- a/usr.bin/ssh-copy-id/ssh-copy-id.1 +++ b/usr.bin/ssh-copy-id/ssh-copy-id.1 @@ -25,7 +25,7 @@ .\" .\" $FreeBSD$ .\" -.Dd October 3, 2012 +.Dd November 11, 2012 .Dt SSH-COPY-ID 1 .Os .Sh NAME @@ -42,8 +42,8 @@ The .Nm utility copies public keys to a remote host's -.Pa authorized_keys -file. +.Pa ~/.ssh/authorized_keys +file (creating the file and directory, if required). .Pp The following options are available: .Bl -tag -width indent @@ -71,6 +71,11 @@ default. .Pp The remaining arguments are a list of remote hosts to connect to, each one optionally qualified by a user name. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +To send a specific key to multiple hosts: +.Dl $ ssh-copy-id -i /path/to/keyfile.pub user@host1 user@host2 user@host3 .Sh HISTORY The .Nm diff --git a/usr.bin/ssh-copy-id/ssh-copy-id.sh b/usr.bin/ssh-copy-id/ssh-copy-id.sh index 8f087d3..94429de 100755 --- a/usr.bin/ssh-copy-id/ssh-copy-id.sh +++ b/usr.bin/ssh-copy-id/ssh-copy-id.sh @@ -34,19 +34,18 @@ usage() { sendkey() { local h="$1" - shift 1 - local k="$@" - echo "$k" | ssh $port -S none $options "$user$h" /bin/sh -c \'' - set -e; - umask 077; - keyfile=$HOME/.ssh/authorized_keys ; - mkdir -p $HOME/.ssh/ ; - while read alg key comment ; do - if ! grep -sqwF "$key" "$keyfile"; then - echo "$alg $key $comment" | - tee -a "$keyfile" >/dev/null ; - fi ; - done + local k="$2" + printf "%s\n" "$k" | ssh $port -S none $options "$user$h" /bin/sh -c \'' \ + set -e; \ + umask 077; \ + keyfile=$HOME/.ssh/authorized_keys ; \ + mkdir -p -- "$HOME/.ssh/" ; \ + while read alg key comment ; do \ + [ -n "$key" ] || continue; \ + if ! grep -sqwF "$key" "$keyfile"; then \ + printf "$alg $key $comment\n" >> "$keyfile" ; \ + fi ; \ + done \ '\' } @@ -63,12 +62,17 @@ nl=" " options="" +IFS=$nl + while getopts 'i:lo:p:' arg; do case $arg in i) hasarg="x" - if [ -f "$OPTARG" ]; then - keys="$(cat $OPTARG)$nl$keys" + if [ -r "$OPTARG" ]; then + keys="$(cat -- "$OPTARG")$nl$keys" + else + echo "File $OPTARG not found" >&2 + exit 1 fi ;; l) @@ -76,10 +80,10 @@ while getopts 'i:lo:p:' arg; do agentKeys ;; p) - port="-p $OPTARG" + port=-p$nl$OPTARG ;; o) - options="$options -o '$OPTARG'" + options=$options$nl-o$nl$OPTARG ;; *) usage @@ -92,11 +96,11 @@ shift $((OPTIND-1)) if [ -z "$hasarg" ]; then agentKeys fi -if [ -z "$keys" -o "$keys" = "$nl" ]; then +if [ -z "$keys" ] || [ "$keys" = "$nl" ]; then echo "no keys found" >&2 exit 1 fi -if [ -z "$@" ]; then +if [ "$#" -eq 0 ]; then usage fi diff --git a/usr.bin/stdbuf/stdbuf.c b/usr.bin/stdbuf/stdbuf.c index 3831b5e..4346cc8 100644 --- a/usr.bin/stdbuf/stdbuf.c +++ b/usr.bin/stdbuf/stdbuf.c @@ -39,7 +39,7 @@ extern char *__progname; static void usage(int s) { - + fprintf(stderr, "Usage: %s [-e 0|L|<sz>] [-i 0|L|<sz>] [-o 0|L|<sz>] " "<cmd> [args ...]\n", __progname); exit(s); @@ -72,8 +72,8 @@ main(int argc, char *argv[]) } argc -= optind; argv += optind; - if (argc < 2) - usage(0); + if (argc == 0) + exit(0); if (ibuf != NULL && setenv("_STDBUF_I", ibuf, 1) == -1) warn("Failed to set environment variable: %s=%s", @@ -94,7 +94,7 @@ main(int argc, char *argv[]) if (i < 0 || putenv(preload1) == -1) warn("Failed to set environment variable: LD_PRELOAD"); - + preload0 = getenv("LD_32_PRELOAD"); if (preload0 == NULL) i = asprintf(&preload1, "LD_32_PRELOAD=" LIBSTDBUF32); diff --git a/usr.bin/systat/vmstat.c b/usr.bin/systat/vmstat.c index 459b58d..856a9a6 100644 --- a/usr.bin/systat/vmstat.c +++ b/usr.bin/systat/vmstat.c @@ -79,6 +79,7 @@ static struct Info { * Virtual memory activity. */ u_int v_vm_faults; /* number of address memory faults */ + u_int v_io_faults; /* page faults requiring I/O */ u_int v_cow_faults; /* number of copy-on-writes */ u_int v_zfod; /* pages zero filled on demand */ u_int v_ozfod; /* optimized zero fill pages */ @@ -324,24 +325,25 @@ labelkre(void) mvprintw(INTSROW, INTSCOL + 1, "Interrupts"); mvprintw(INTSROW + 1, INTSCOL + 6, "total"); - mvprintw(VMSTATROW, VMSTATCOL + 9, "cow"); - mvprintw(VMSTATROW + 1, VMSTATCOL + 9, "zfod"); - mvprintw(VMSTATROW + 2, VMSTATCOL + 9, "ozfod"); - mvprintw(VMSTATROW + 3, VMSTATCOL + 9 - 1, "%%ozfod"); - mvprintw(VMSTATROW + 4, VMSTATCOL + 9, "daefr"); - mvprintw(VMSTATROW + 5, VMSTATCOL + 9, "prcfr"); - mvprintw(VMSTATROW + 6, VMSTATCOL + 9, "totfr"); - mvprintw(VMSTATROW + 7, VMSTATCOL + 9, "react"); - mvprintw(VMSTATROW + 8, VMSTATCOL + 9, "pdwak"); - mvprintw(VMSTATROW + 9, VMSTATCOL + 9, "pdpgs"); - mvprintw(VMSTATROW + 10, VMSTATCOL + 9, "intrn"); - mvprintw(VMSTATROW + 11, VMSTATCOL + 9, "wire"); - mvprintw(VMSTATROW + 12, VMSTATCOL + 9, "act"); - mvprintw(VMSTATROW + 13, VMSTATCOL + 9, "inact"); - mvprintw(VMSTATROW + 14, VMSTATCOL + 9, "cache"); - mvprintw(VMSTATROW + 15, VMSTATCOL + 9, "free"); - if (LINES - 1 > VMSTATROW + 16) - mvprintw(VMSTATROW + 16, VMSTATCOL + 9, "buf"); + mvprintw(VMSTATROW, VMSTATCOL + 9, "ioflt"); + mvprintw(VMSTATROW + 1, VMSTATCOL + 9, "cow"); + mvprintw(VMSTATROW + 2, VMSTATCOL + 9, "zfod"); + mvprintw(VMSTATROW + 3, VMSTATCOL + 9, "ozfod"); + mvprintw(VMSTATROW + 4, VMSTATCOL + 9 - 1, "%%ozfod"); + mvprintw(VMSTATROW + 5, VMSTATCOL + 9, "daefr"); + mvprintw(VMSTATROW + 6, VMSTATCOL + 9, "prcfr"); + mvprintw(VMSTATROW + 7, VMSTATCOL + 9, "totfr"); + mvprintw(VMSTATROW + 8, VMSTATCOL + 9, "react"); + mvprintw(VMSTATROW + 9, VMSTATCOL + 9, "pdwak"); + mvprintw(VMSTATROW + 10, VMSTATCOL + 9, "pdpgs"); + mvprintw(VMSTATROW + 11, VMSTATCOL + 9, "intrn"); + mvprintw(VMSTATROW + 12, VMSTATCOL + 9, "wire"); + mvprintw(VMSTATROW + 13, VMSTATCOL + 9, "act"); + mvprintw(VMSTATROW + 14, VMSTATCOL + 9, "inact"); + mvprintw(VMSTATROW + 15, VMSTATCOL + 9, "cache"); + mvprintw(VMSTATROW + 16, VMSTATCOL + 9, "free"); + if (LINES - 1 > VMSTATROW + 17) + mvprintw(VMSTATROW + 17, VMSTATCOL + 9, "buf"); mvprintw(GENSTATROW, GENSTATCOL, " Csw Trp Sys Int Sof Flt"); @@ -493,25 +495,26 @@ showkre(void) putint(total.t_dw, PROCSROW + 2, PROCSCOL + 8, 3); putint(total.t_sl, PROCSROW + 2, PROCSCOL + 12, 3); putint(total.t_sw, PROCSROW + 2, PROCSCOL + 16, 3); - PUTRATE(v_cow_faults, VMSTATROW, VMSTATCOL + 2, 8 - 2); - PUTRATE(v_zfod, VMSTATROW + 1, VMSTATCOL + 2, 8 - 2); - PUTRATE(v_ozfod, VMSTATROW + 2, VMSTATCOL, 8); + PUTRATE(v_io_faults, VMSTATROW, VMSTATCOL + 2, 8 - 2); + PUTRATE(v_cow_faults, VMSTATROW + 1, VMSTATCOL + 2, 8 - 2); + PUTRATE(v_zfod, VMSTATROW + 2, VMSTATCOL + 2, 8 - 2); + PUTRATE(v_ozfod, VMSTATROW + 3, VMSTATCOL, 8); putint(s.v_zfod != 0 ? (int)(s.v_ozfod * 100.0 / s.v_zfod) : 0, - VMSTATROW + 3, VMSTATCOL + 1, 8 - 1); - PUTRATE(v_dfree, VMSTATROW + 4, VMSTATCOL + 2, 8 - 2); - PUTRATE(v_pfree, VMSTATROW + 5, VMSTATCOL + 2, 8 - 2); - PUTRATE(v_tfree, VMSTATROW + 6, VMSTATCOL, 8); - PUTRATE(v_reactivated, VMSTATROW + 7, VMSTATCOL, 8); - PUTRATE(v_pdwakeups, VMSTATROW + 8, VMSTATCOL, 8); - PUTRATE(v_pdpages, VMSTATROW + 9, VMSTATCOL, 8); - PUTRATE(v_intrans, VMSTATROW + 10, VMSTATCOL, 8); - putint(pgtokb(s.v_wire_count), VMSTATROW + 11, VMSTATCOL, 8); - putint(pgtokb(s.v_active_count), VMSTATROW + 12, VMSTATCOL, 8); - putint(pgtokb(s.v_inactive_count), VMSTATROW + 13, VMSTATCOL, 8); - putint(pgtokb(s.v_cache_count), VMSTATROW + 14, VMSTATCOL, 8); - putint(pgtokb(s.v_free_count), VMSTATROW + 15, VMSTATCOL, 8); - if (LINES - 1 > VMSTATROW + 16) - putint(s.bufspace / 1024, VMSTATROW + 16, VMSTATCOL, 8); + VMSTATROW + 4, VMSTATCOL + 1, 8 - 1); + PUTRATE(v_dfree, VMSTATROW + 5, VMSTATCOL + 2, 8 - 2); + PUTRATE(v_pfree, VMSTATROW + 6, VMSTATCOL + 2, 8 - 2); + PUTRATE(v_tfree, VMSTATROW + 7, VMSTATCOL, 8); + PUTRATE(v_reactivated, VMSTATROW + 8, VMSTATCOL, 8); + PUTRATE(v_pdwakeups, VMSTATROW + 9, VMSTATCOL, 8); + PUTRATE(v_pdpages, VMSTATROW + 10, VMSTATCOL, 8); + PUTRATE(v_intrans, VMSTATROW + 11, VMSTATCOL, 8); + putint(pgtokb(s.v_wire_count), VMSTATROW + 12, VMSTATCOL, 8); + putint(pgtokb(s.v_active_count), VMSTATROW + 13, VMSTATCOL, 8); + putint(pgtokb(s.v_inactive_count), VMSTATROW + 14, VMSTATCOL, 8); + putint(pgtokb(s.v_cache_count), VMSTATROW + 15, VMSTATCOL, 8); + putint(pgtokb(s.v_free_count), VMSTATROW + 16, VMSTATCOL, 8); + if (LINES - 1 > VMSTATROW + 17) + putint(s.bufspace / 1024, VMSTATROW + 17, VMSTATCOL, 8); PUTRATE(v_vnodein, PAGEROW + 2, PAGECOL + 6, 5); PUTRATE(v_vnodeout, PAGEROW + 2, PAGECOL + 12, 5); PUTRATE(v_swapin, PAGEROW + 2, PAGECOL + 19, 5); @@ -751,6 +754,7 @@ getinfo(struct Info *ls) GETSYSCTL("vm.stats.sys.v_intr", ls->v_intr); GETSYSCTL("vm.stats.sys.v_soft", ls->v_soft); GETSYSCTL("vm.stats.vm.v_vm_faults", ls->v_vm_faults); + GETSYSCTL("vm.stats.vm.v_io_faults", ls->v_io_faults); GETSYSCTL("vm.stats.vm.v_cow_faults", ls->v_cow_faults); GETSYSCTL("vm.stats.vm.v_zfod", ls->v_zfod); GETSYSCTL("vm.stats.vm.v_ozfod", ls->v_ozfod); diff --git a/usr.bin/tail/read.c b/usr.bin/tail/read.c index 2cff3a3..79c4fa5 100644 --- a/usr.bin/tail/read.c +++ b/usr.bin/tail/read.c @@ -143,9 +143,8 @@ lines(FILE *fp, const char *fn, off_t off) char *p, *sp; int blen, cnt, recno, wrap; - if ((llines = malloc(off * sizeof(*llines))) == NULL) - err(1, "malloc"); - bzero(llines, off * sizeof(*llines)); + if ((llines = calloc(off, sizeof(*llines))) == NULL) + err(1, "calloc"); p = sp = NULL; blen = cnt = recno = wrap = 0; rc = 0; diff --git a/usr.bin/time/time.c b/usr.bin/time/time.c index ebee542..c2ea522 100644 --- a/usr.bin/time/time.c +++ b/usr.bin/time/time.c @@ -112,7 +112,7 @@ main(int argc, char **argv) argv += optind; if (ofn) { - if ((out = fopen(ofn, aflag ? "a" : "w")) == NULL) + if ((out = fopen(ofn, aflag ? "ae" : "we")) == NULL) err(1, "%s", ofn); setvbuf(out, (char *)NULL, _IONBF, (size_t)0); } @@ -123,8 +123,6 @@ main(int argc, char **argv) err(1, "time"); /* NOTREACHED */ case 0: /* child */ - if (ofn) - fclose(out); execvp(*argv, argv); err(errno == ENOENT ? 127 : 126, "%s", *argv); /* NOTREACHED */ diff --git a/usr.bin/tip/tip/cu.1 b/usr.bin/tip/tip/cu.1 index b67b19d..aba5c40 100644 --- a/usr.bin/tip/tip/cu.1 +++ b/usr.bin/tip/tip/cu.1 @@ -69,9 +69,9 @@ Echo characters locally (half-duplex mode). .It Fl l Ar line Specify the line to use. Either of the forms like -.Pa cuad0 +.Pa cuau0 or -.Pa /dev/cuad0 +.Pa /dev/cuau0 are permitted. .It Fl o Use odd parity. diff --git a/usr.bin/top/machine.c b/usr.bin/top/machine.c index 236cb554..edfa43a 100644 --- a/usr.bin/top/machine.c +++ b/usr.bin/top/machine.c @@ -225,7 +225,7 @@ long percentages(); char *ordernames[] = { "cpu", "size", "res", "time", "pri", "threads", "total", "read", "write", "fault", "vcsw", "ivcsw", - "jid", NULL + "jid", "pid", NULL }; #endif @@ -786,7 +786,7 @@ get_process_info(struct system_info *si, struct process_select *sel, return ((caddr_t)&handle); } -static char fmt[128]; /* static area where result is built */ +static char fmt[512]; /* static area where result is built */ char * format_next_process(caddr_t handle, char *(*get_userid)(int), int flags) @@ -803,6 +803,7 @@ format_next_process(caddr_t handle, char *(*get_userid)(int), int flags) char *proc_fmt, thr_buf[6], jid_buf[6]; char *cmdbuf = NULL; char **args; + const int cmdlen = 128; /* find and remember the next proc structure */ hp = (struct handle *)handle; @@ -865,31 +866,31 @@ format_next_process(caddr_t handle, char *(*get_userid)(int), int flags) break; } - cmdbuf = (char *)malloc(cmdlengthdelta + 1); + cmdbuf = (char *)malloc(cmdlen + 1); if (cmdbuf == NULL) { - warn("malloc(%d)", cmdlengthdelta + 1); + warn("malloc(%d)", cmdlen + 1); return NULL; } if (!(flags & FMT_SHOWARGS)) { if (ps.thread && pp->ki_flag & P_HADTHREADS && pp->ki_tdname[0]) { - snprintf(cmdbuf, cmdlengthdelta, "%s{%s}", pp->ki_comm, + snprintf(cmdbuf, cmdlen, "%s{%s}", pp->ki_comm, pp->ki_tdname); } else { - snprintf(cmdbuf, cmdlengthdelta, "%s", pp->ki_comm); + snprintf(cmdbuf, cmdlen, "%s", pp->ki_comm); } } else { if (pp->ki_flag & P_SYSTEM || pp->ki_args == NULL || - (args = kvm_getargv(kd, pp, cmdlengthdelta)) == NULL || + (args = kvm_getargv(kd, pp, cmdlen)) == NULL || !(*args)) { if (ps.thread && pp->ki_flag & P_HADTHREADS && pp->ki_tdname[0]) { - snprintf(cmdbuf, cmdlengthdelta, + snprintf(cmdbuf, cmdlen, "[%s{%s}]", pp->ki_comm, pp->ki_tdname); } else { - snprintf(cmdbuf, cmdlengthdelta, + snprintf(cmdbuf, cmdlen, "[%s]", pp->ki_comm); } } else { @@ -898,7 +899,7 @@ format_next_process(caddr_t handle, char *(*get_userid)(int), int flags) size_t argbuflen; size_t len; - argbuflen = cmdlengthdelta * 4; + argbuflen = cmdlen * 4; argbuf = (char *)malloc(argbuflen + 1); if (argbuf == NULL) { warn("malloc(%d)", argbuflen + 1); @@ -931,22 +932,22 @@ format_next_process(caddr_t handle, char *(*get_userid)(int), int flags) dst--; *dst = '\0'; - if (strcmp(cmd, pp->ki_comm) != 0 ) { + if (strcmp(cmd, pp->ki_comm) != 0) { if (ps.thread && pp->ki_flag & P_HADTHREADS && pp->ki_tdname[0]) - snprintf(cmdbuf, cmdlengthdelta, + snprintf(cmdbuf, cmdlen, "%s (%s){%s}", argbuf, pp->ki_comm, pp->ki_tdname); else - snprintf(cmdbuf, cmdlengthdelta, + snprintf(cmdbuf, cmdlen, "%s (%s)", argbuf, pp->ki_comm); } else { if (ps.thread && pp->ki_flag & P_HADTHREADS && pp->ki_tdname[0]) - snprintf(cmdbuf, cmdlengthdelta, + snprintf(cmdbuf, cmdlen, "%s{%s}", argbuf, pp->ki_tdname); else - strlcpy(cmdbuf, argbuf, cmdlengthdelta); + strlcpy(cmdbuf, argbuf, cmdlen); } free(argbuf); } diff --git a/usr.bin/tr/tr.c b/usr.bin/tr/tr.c index c2b6bf8..6eea2cb 100644 --- a/usr.bin/tr/tr.c +++ b/usr.bin/tr/tr.c @@ -47,6 +47,7 @@ static const char sccsid[] = "@(#)tr.c 8.2 (Berkeley) 5/4/95"; #include <err.h> #include <limits.h> #include <locale.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -267,7 +268,7 @@ endloop: */ s2.str = argv[1]; s2.state = NORMAL; - for (cnt = 0; cnt < WCHAR_MAX; cnt++) { + for (cnt = 0; cnt < WINT_MAX; cnt++) { if (Cflag && !iswrune(cnt)) continue; if (cmap_lookup(map, cnt) == OOBCH) { diff --git a/usr.bin/truss/main.c b/usr.bin/truss/main.c index 02c6930..4e9f74b 100644 --- a/usr.bin/truss/main.c +++ b/usr.bin/truss/main.c @@ -234,15 +234,12 @@ main(int ac, char **av) usage(); if (fname != NULL) { /* Use output file */ - if ((trussinfo->outfile = fopen(fname, "w")) == NULL) - errx(1, "cannot open %s", fname); /* - * Set FD_CLOEXEC, so that the output file is not shared with - * the traced process. + * Set close-on-exec ('e'), so that the output file is not + * shared with the traced process. */ - if (fcntl(fileno(trussinfo->outfile), F_SETFD, FD_CLOEXEC) == - -1) - warn("fcntl()"); + if ((trussinfo->outfile = fopen(fname, "we")) == NULL) + err(1, "cannot open %s", fname); } /* diff --git a/usr.bin/ul/ul.c b/usr.bin/ul/ul.c index 4796956..7aecf6c 100644 --- a/usr.bin/ul/ul.c +++ b/usr.bin/ul/ul.c @@ -280,7 +280,7 @@ filter(FILE *f) obuf[col].c_width = w; for (i = 1; i < w; i++) obuf[col + i].c_width = -1; - } else if (obuf[col].c_char == c) { + } else if ((wint_t)obuf[col].c_char == c) { for (i = 0; i < w; i++) obuf[col + i].c_mode |= BOLD|mode; } else { diff --git a/usr.bin/vmstat/vmstat.c b/usr.bin/vmstat/vmstat.c index fe3a29c..c089dbf 100644 --- a/usr.bin/vmstat/vmstat.c +++ b/usr.bin/vmstat/vmstat.c @@ -469,6 +469,7 @@ fill_vmmeter(struct vmmeter *vmmp) ADD_FROM_PCPU(i, v_intr); ADD_FROM_PCPU(i, v_soft); ADD_FROM_PCPU(i, v_vm_faults); + ADD_FROM_PCPU(i, v_io_faults); ADD_FROM_PCPU(i, v_cow_faults); ADD_FROM_PCPU(i, v_cow_optim); ADD_FROM_PCPU(i, v_zfod); @@ -507,6 +508,7 @@ fill_vmmeter(struct vmmeter *vmmp) /* vm */ GET_VM_STATS(vm, v_vm_faults); + GET_VM_STATS(vm, v_io_faults); GET_VM_STATS(vm, v_cow_faults); GET_VM_STATS(vm, v_cow_optim); GET_VM_STATS(vm, v_zfod); @@ -968,6 +970,7 @@ dosum(void) (void)printf("%9u zero fill pages prezeroed\n", sum.v_ozfod); (void)printf("%9u intransit blocking page faults\n", sum.v_intrans); (void)printf("%9u total VM faults taken\n", sum.v_vm_faults); + (void)printf("%9u page faults requiring I/O\n", sum.v_io_faults); (void)printf("%9u pages affected by kernel thread creation\n", sum.v_kthreadpages); (void)printf("%9u pages affected by fork()\n", sum.v_forkpages); (void)printf("%9u pages affected by vfork()\n", sum.v_vforkpages); diff --git a/usr.bin/w/extern.h b/usr.bin/w/extern.h index 0e886d0..facc3e8 100644 --- a/usr.bin/w/extern.h +++ b/usr.bin/w/extern.h @@ -30,7 +30,6 @@ * $FreeBSD$ */ - extern int use_ampm; struct kinfo_proc; diff --git a/usr.bin/w/proc_compare.c b/usr.bin/w/proc_compare.c index 4888386..0e73ba4 100644 --- a/usr.bin/w/proc_compare.c +++ b/usr.bin/w/proc_compare.c @@ -62,11 +62,11 @@ __FBSDID("$FreeBSD$"); * TODO - consider whether pctcpu should be used. */ -#define ISRUN(p) (((p)->ki_stat == SRUN) || ((p)->ki_stat == SIDL)) -#define TESTAB(a, b) ((a)<<1 | (b)) -#define ONLYA 2 -#define ONLYB 1 -#define BOTH 3 +#define ISRUN(p) (((p)->ki_stat == SRUN) || ((p)->ki_stat == SIDL)) +#define TESTAB(a, b) ((a)<<1 | (b)) +#define ONLYA 2 +#define ONLYB 1 +#define BOTH 3 int proc_compare(struct kinfo_proc *p1, struct kinfo_proc *p2) @@ -93,7 +93,7 @@ proc_compare(struct kinfo_proc *p1, struct kinfo_proc *p2) return (p2->ki_pid > p1->ki_pid); /* tie - return highest pid */ } /* - * weed out zombies + * weed out zombies */ switch (TESTAB(p1->ki_stat == SZOMB, p2->ki_stat == SZOMB)) { case ONLYA: diff --git a/usr.bin/w/w.c b/usr.bin/w/w.c index 2e19bb1..1b9af5f 100644 --- a/usr.bin/w/w.c +++ b/usr.bin/w/w.c @@ -123,7 +123,7 @@ static struct stat *ttystat(char *); static void usage(int); static int this_is_uptime(const char *s); -char *fmt_argv(char **, char *, int); /* ../../bin/ps/fmt.c */ +char *fmt_argv(char **, char *, char *, size_t); /* ../../bin/ps/fmt.c */ int main(int argc, char *argv[]) @@ -320,7 +320,7 @@ main(int argc, char *argv[]) continue; } ep->args = fmt_argv(kvm_getargv(kd, ep->kp, argwidth), - ep->kp->ki_comm, MAXCOMLEN); + ep->kp->ki_comm, NULL, MAXCOMLEN); if (ep->args == NULL) err(1, NULL); } @@ -404,7 +404,7 @@ main(int argc, char *argv[]) const char *ptr; ptr = fmt_argv(kvm_getargv(kd, dkp, argwidth), - dkp->ki_comm, MAXCOMLEN); + dkp->ki_comm, NULL, MAXCOMLEN); if (ptr == NULL) ptr = "-"; (void)printf("\t\t%-9d %s\n", diff --git a/usr.bin/xargs/strnsubst.c b/usr.bin/xargs/strnsubst.c index 33366b6..304e47b 100644 --- a/usr.bin/xargs/strnsubst.c +++ b/usr.bin/xargs/strnsubst.c @@ -48,7 +48,7 @@ strnsubst(char **str, const char *match, const char *replstr, size_t maxsize) match = NULL; maxsize = strlen(s1) + 1; } - s2 = calloc(maxsize, 1); + s2 = calloc(1, maxsize); if (s2 == NULL) err(1, "calloc"); diff --git a/usr.bin/xinstall/Makefile b/usr.bin/xinstall/Makefile index e6ff88e..d9d5873 100644 --- a/usr.bin/xinstall/Makefile +++ b/usr.bin/xinstall/Makefile @@ -3,6 +3,14 @@ PROG= xinstall PROGNAME= install +SRCS= xinstall.c getid.c MAN= install.1 +.PATH: ${.CURDIR}/../../contrib/mtree +CFLAGS+= -I${.CURDIR}/../../contrib/mtree +CFLAGS+= -I${.CURDIR}/../../lib/libnetbsd + +DPADD+= ${LIBUTIL} ${LIBMD} +LDADD+= -lutil -lmd + .include <bsd.prog.mk> diff --git a/usr.bin/xinstall/install.1 b/usr.bin/xinstall/install.1 index 1c7c415..6fc038e 100644 --- a/usr.bin/xinstall/install.1 +++ b/usr.bin/xinstall/install.1 @@ -28,7 +28,7 @@ .\" From: @(#)install.1 8.1 (Berkeley) 6/6/93 .\" $FreeBSD$ .\" -.Dd March 6, 2006 +.Dd January 18, 2013 .Dt INSTALL 1 .Os .Sh NAME @@ -36,31 +36,50 @@ .Nd install binaries .Sh SYNOPSIS .Nm -.Op Fl bCcMpSsv +.Op Fl bCcpSsUv .Op Fl B Ar suffix +.Op Fl D Ar destdir .Op Fl f Ar flags .Op Fl g Ar group +.Op Fl h Ar hash +.Op Fl l Ar linkflags +.Op Fl M Ar metalog .Op Fl m Ar mode +.Op Fl N Ar dbdir .Op Fl o Ar owner +.Op Fl T Ar tags .Ar file1 file2 .Nm -.Op Fl bCcMpSsv +.Op Fl bCcpSsUv .Op Fl B Ar suffix +.Op Fl D Ar destdir .Op Fl f Ar flags .Op Fl g Ar group +.Op Fl h Ar hash +.Op Fl l Ar linkflags +.Op Fl M Ar metalog .Op Fl m Ar mode +.Op Fl N Ar dbdir .Op Fl o Ar owner +.Op Fl T Ar tags .Ar file1 ... fileN directory .Nm .Fl d -.Op Fl v +.Op Fl Uv +.Op Fl D Ar destdir .Op Fl g Ar group +.Op Fl h Ar hash +.Op Fl M Ar metalog .Op Fl m Ar mode +.Op Fl N Ar dbdir .Op Fl o Ar owner +.Op Fl T Ar tags .Ar directory ... .Sh DESCRIPTION The file(s) are copied -to the target file or directory. +(or linked if the +.Fl l +option is specified) to the target file or directory. If the destination is a directory, then the .Ar file is copied into @@ -105,6 +124,17 @@ This is actually the default. The .Fl c option is only included for backwards compatibility. +.It Fl D Ar destdir +Specify the +.Ev DESTDIR +(top of the file hierarchy) that the items are installed in to. +If +.Fl M Ar metalog +is in use, a leading string of +.Dq Ar destdir +will be removed from the file names logged to the +.Ar metalog . +This option does not affect where the actual files are installed. .It Fl d Create directories. Missing parent directories are created as required. @@ -115,15 +145,82 @@ for a list of possible flags and their meanings. .It Fl g Specify a group. A numeric GID is allowed. -.It Fl M -Disable all use of -.Xr mmap 2 . +.It Fl h Ar hash +When copying, calculate the digest of the files with +.Ar hash +to store in the +.Fl M Ar metalog . +When +.Fl d +is given no hash is emitted. +Supported digests: +.Bl -tag -width rmd160 -offset indent +.It Sy none +No hash. +This is the default. +.It Sy md5 +The MD5 cryptographic message digest. +.It Sy rmd160 +The RMD-160 cryptographic message digest. +.It Sy sha1 +The SHA-1 cryptographic message digest. +.It Sy sha256 +The 256-bits +.Tn SHA-2 +cryptographic message digest of the file. +.It Sy sha512 +The 512-bits +.Tn SHA-2 +cryptographic message digest of the file. +.El +.It Fl l Ar linkflags +Instead of copying the file make a link to the source. +The type of the link is determined by the +.Ar linkflags +argument. +Valid +.Ar linkflags +are: +.Ar a +(absolute), +.Ar r +(relative), +.Ar h +(hard), +.Ar s +(symbolic), +.Ar m +(mixed). +Absolute and relative have effect only for symbolic links. +Mixed links +are hard links for files on the same filesystem, symbolic otherwise. +.It Fl M Ar metalog +Write the metadata associated with each item installed to +.Ar metalog +in an +.Xr mtree 8 +.Dq full path +specification line. +The metadata includes: the file name and file type, and depending upon +other options, the owner, group, file flags, modification time, and tags. .It Fl m Specify an alternate mode. The default mode is set to rwxr-xr-x (0755). The specified mode may be either an octal or symbolic value; see .Xr chmod 1 for a description of possible mode values. +.It Fl N +Use the user database text file +.Pa master.passwd +and group database text file +.Pa group +from +.Ar dbdir , +rather than using the results from the system's +.Xr getpwnam 3 +and +.Xr getgrnam 3 +(and related) library calls. .It Fl o Specify an owner. A numeric UID is allowed. @@ -156,6 +253,17 @@ number of systems and binary types. See below for how .Nm can be instructed to use another program to strip binaries. +.It Fl T Ar tags +Specify the +.Xr mtree 8 +tags to write out for the file when using +.Fl M Ar metalog . +.It Fl U +Indicate that install is running unprivileged, and that it should not +try to change the owner, the group, or the file flags of the destination. +The information that would have been updated can be stored in a log +file with +.Fl M Ar metalog . .It Fl v Cause .Nm @@ -231,6 +339,8 @@ The default was changed to copy in .Xr mv 1 , .Xr strip 1 , .Xr mmap 2 , +.Xr getgrnam 3 , +.Xr getpwnam 3 , .Xr chown 8 .Sh HISTORY The @@ -238,6 +348,16 @@ The utility appeared in .Bx 4.2 . .Sh BUGS +The meaning of the +.Fl M +option has changed as of +.Fx 10 +and it now takes an argument. +Command lines that used the old +.Fl M +will get an error or in rare cases will append logs to the first of +multiple source files rather than installing it. +.Pp Temporary files may be left in the target directory if .Nm exits abnormally. diff --git a/usr.bin/xinstall/xinstall.c b/usr.bin/xinstall/xinstall.c index 583348a..15b115a 100644 --- a/usr.bin/xinstall/xinstall.c +++ b/usr.bin/xinstall/xinstall.c @@ -1,4 +1,5 @@ /* + * Copyright (c) 2012, 2013 SRI International * Copyright (c) 1987, 1993 * The Regents of the University of California. All rights reserved. * @@ -53,14 +54,23 @@ __FBSDID("$FreeBSD$"); #include <errno.h> #include <fcntl.h> #include <grp.h> +#include <libgen.h> +#include <md5.h> #include <paths.h> #include <pwd.h> +#include <ripemd.h> +#include <sha.h> +#include <sha256.h> +#include <sha512.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sysexits.h> #include <unistd.h> +#include <vis.h> + +#include "mtree.h" /* Bootstrap aid - this doesn't exist in most older releases */ #ifndef MAP_FAILED @@ -69,27 +79,63 @@ __FBSDID("$FreeBSD$"); #define MAX_CMP_SIZE (16 * 1024 * 1024) +#define LN_ABSOLUTE 0x01 +#define LN_RELATIVE 0x02 +#define LN_HARD 0x04 +#define LN_SYMBOLIC 0x08 +#define LN_MIXED 0x10 + #define DIRECTORY 0x01 /* Tell install it's a directory. */ #define SETFLAGS 0x02 /* Tell install to set flags. */ #define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND) #define BACKUP_SUFFIX ".old" -static struct passwd *pp; -static struct group *gp; +typedef union { + MD5_CTX MD5; + RIPEMD160_CTX RIPEMD160; + SHA1_CTX SHA1; + SHA256_CTX SHA256; + SHA512_CTX SHA512; +} DIGEST_CTX; + +static enum { + DIGEST_NONE = 0, + DIGEST_MD5, + DIGEST_RIPEMD160, + DIGEST_SHA1, + DIGEST_SHA256, + DIGEST_SHA512, +} digesttype = DIGEST_NONE; + static gid_t gid; static uid_t uid; -static int dobackup, docompare, dodir, dopreserve, dostrip, nommap, safecopy, - verbose; +static int dobackup, docompare, dodir, dolink, dopreserve, dostrip, dounpriv, + safecopy, verbose; +static int haveopt_f, haveopt_g, haveopt_m, haveopt_o; static mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; +static FILE *metafp; +static const char *group, *owner; static const char *suffix = BACKUP_SUFFIX; +static char *destdir, *digest, *fflags, *metafile, *tags; -static int compare(int, const char *, size_t, int, const char *, size_t); -static void copy(int, const char *, int, const char *, off_t); +static int compare(int, const char *, size_t, int, const char *, size_t, + char **); +static char *copy(int, const char *, int, const char *, off_t); static int create_newfile(const char *, int, struct stat *); static int create_tempfile(const char *, char *, size_t); +static char *quiet_mktemp(char *template); +static char *digest_file(const char *); +static void digest_init(DIGEST_CTX *); +static void digest_update(DIGEST_CTX *, const unsigned char *, size_t); +static char *digest_end(DIGEST_CTX *, char *); +static int do_link(const char *, const char *, const struct stat *); +static void do_symlink(const char *, const char *, const struct stat *); +static void makelink(const char *, const char *, const struct stat *); static void install(const char *, const char *, u_long, u_int); static void install_dir(char *); -static u_long numeric_id(const char *, const char *); +static void metadata_log(const char *, const char *, struct timeval *, + const char *, const char *, off_t); +static int parseid(const char *, id_t *); static void strip(const char *); static int trymmap(int); static void usage(void); @@ -102,12 +148,13 @@ main(int argc, char *argv[]) u_long fset; int ch, no_target; u_int iflags; - char *flags; - const char *group, *owner, *to_name; + char *p; + const char *to_name; iflags = 0; group = owner = NULL; - while ((ch = getopt(argc, argv, "B:bCcdf:g:Mm:o:pSsv")) != -1) + while ((ch = getopt(argc, argv, "B:bCcD:df:g:h:l:M:m:N:o:pSsT:Uv")) != + -1) switch((char)ch) { case 'B': suffix = optarg; @@ -121,29 +168,69 @@ main(int argc, char *argv[]) case 'c': /* For backwards compatibility. */ break; + case 'D': + destdir = optarg; + break; case 'd': dodir = 1; break; case 'f': - flags = optarg; - if (strtofflags(&flags, &fset, NULL)) - errx(EX_USAGE, "%s: invalid flag", flags); - iflags |= SETFLAGS; + haveopt_f = 1; + fflags = optarg; break; case 'g': + haveopt_g = 1; group = optarg; break; + case 'h': + digest = optarg; + break; + case 'l': + for (p = optarg; *p != '\0'; p++) + switch (*p) { + case 's': + dolink &= ~(LN_HARD|LN_MIXED); + dolink |= LN_SYMBOLIC; + break; + case 'h': + dolink &= ~(LN_SYMBOLIC|LN_MIXED); + dolink |= LN_HARD; + break; + case 'm': + dolink &= ~(LN_SYMBOLIC|LN_HARD); + dolink |= LN_MIXED; + break; + case 'a': + dolink &= ~LN_RELATIVE; + dolink |= LN_ABSOLUTE; + break; + case 'r': + dolink &= ~LN_ABSOLUTE; + dolink |= LN_RELATIVE; + break; + default: + errx(1, "%c: invalid link type", *p); + /* NOTREACHED */ + } + break; case 'M': - nommap = 1; + metafile = optarg; break; case 'm': + haveopt_m = 1; if (!(set = setmode(optarg))) errx(EX_USAGE, "invalid file mode: %s", optarg); mode = getmode(set, 0); free(set); break; + case 'N': + if (!setup_getid(optarg)) + err(EX_OSERR, "Unable to use user and group " + "databases in `%s'", optarg); + break; case 'o': + haveopt_o = 1; owner = optarg; break; case 'p': @@ -155,6 +242,12 @@ main(int argc, char *argv[]) case 's': dostrip = 1; break; + case 'T': + tags = optarg; + break; + case 'U': + dounpriv = 1; + break; case 'v': verbose = 1; break; @@ -180,27 +273,62 @@ main(int argc, char *argv[]) if (argc == 0 || (argc == 1 && !dodir)) usage(); + if (digest != NULL) { + if (strcmp(digest, "none") == 0) { + digesttype = DIGEST_NONE; + } else if (strcmp(digest, "md5") == 0) { + digesttype = DIGEST_MD5; + } else if (strcmp(digest, "rmd160") == 0) { + digesttype = DIGEST_RIPEMD160; + } else if (strcmp(digest, "sha1") == 0) { + digesttype = DIGEST_SHA1; + } else if (strcmp(digest, "sha256") == 0) { + digesttype = DIGEST_SHA256; + } else if (strcmp(digest, "sha512") == 0) { + digesttype = DIGEST_SHA512; + } else { + warnx("unknown digest `%s'", digest); + usage(); + } + } + /* need to make a temp copy so we can compare stripped version */ if (docompare && dostrip) safecopy = 1; /* get group and owner id's */ - if (group != NULL) { - if ((gp = getgrnam(group)) != NULL) - gid = gp->gr_gid; - else - gid = (gid_t)numeric_id(group, "group"); + if (group != NULL && !dounpriv) { + if (gid_from_group(group, &gid) == -1) { + id_t id; + if (!parseid(group, &id)) + errx(1, "unknown group %s", group); + gid = id; + } } else gid = (gid_t)-1; - if (owner != NULL) { - if ((pp = getpwnam(owner)) != NULL) - uid = pp->pw_uid; - else - uid = (uid_t)numeric_id(owner, "user"); + if (owner != NULL && !dounpriv) { + if (uid_from_user(owner, &uid) == -1) { + id_t id; + if (!parseid(owner, &id)) + errx(1, "unknown user %s", owner); + uid = id; + } } else uid = (uid_t)-1; + if (fflags != NULL && !dounpriv) { + if (strtofflags(&fflags, &fset, NULL)) + errx(EX_USAGE, "%s: invalid flag", fflags); + iflags |= SETFLAGS; + } + + if (metafile != NULL) { + if ((metafp = fopen(metafile, "a")) == NULL) + warn("open %s", metafile); + } else + digesttype = DIGEST_NONE; + if (dodir) { for (; *argv != NULL; ++argv) install_dir(*argv); @@ -208,8 +336,21 @@ main(int argc, char *argv[]) /* NOTREACHED */ } - no_target = stat(to_name = argv[argc - 1], &to_sb); + to_name = argv[argc - 1]; + no_target = stat(to_name, &to_sb); if (!no_target && S_ISDIR(to_sb.st_mode)) { + if (dolink & LN_SYMBOLIC) { + if (lstat(to_name, &to_sb) != 0) + err(EX_OSERR, "%s vanished", to_name); + if (S_ISLNK(to_sb.st_mode)) { + if (argc != 2) { + errno = ENOTDIR; + err(EX_USAGE, "%s", to_name); + } + install(*argv, to_name, fset, iflags); + exit(EX_OK); + } + } for (; *argv != to_name; ++argv) install(*argv, to_name, fset, iflags | DIRECTORY); exit(EX_OK); @@ -227,7 +368,7 @@ main(int argc, char *argv[]) usage(); } - if (!no_target) { + if (!no_target && !dolink) { if (stat(*argv, &from_sb)) err(EX_OSERR, "%s", *argv); if (!S_ISREG(to_sb.st_mode)) { @@ -244,23 +385,327 @@ main(int argc, char *argv[]) /* NOTREACHED */ } -static u_long -numeric_id(const char *name, const char *type) +static char * +digest_file(const char *name) +{ + + switch (digesttype) { + case DIGEST_MD5: + return (MD5File(name, NULL)); + case DIGEST_RIPEMD160: + return (RIPEMD160_File(name, NULL)); + case DIGEST_SHA1: + return (SHA1_File(name, NULL)); + case DIGEST_SHA256: + return (SHA256_File(name, NULL)); + case DIGEST_SHA512: + return (SHA512_File(name, NULL)); + default: + return (NULL); + } +} + +static void +digest_init(DIGEST_CTX *c) +{ + + switch (digesttype) { + case DIGEST_NONE: + break; + case DIGEST_MD5: + MD5Init(&(c->MD5)); + break; + case DIGEST_RIPEMD160: + RIPEMD160_Init(&(c->RIPEMD160)); + break; + case DIGEST_SHA1: + SHA1_Init(&(c->SHA1)); + break; + case DIGEST_SHA256: + SHA256_Init(&(c->SHA256)); + break; + case DIGEST_SHA512: + SHA512_Init(&(c->SHA512)); + break; + } +} + +static void +digest_update(DIGEST_CTX *c, const unsigned char *data, size_t len) +{ + + switch (digesttype) { + case DIGEST_NONE: + break; + case DIGEST_MD5: + MD5Update(&(c->MD5), data, len); + break; + case DIGEST_RIPEMD160: + RIPEMD160_Update(&(c->RIPEMD160), data, len); + break; + case DIGEST_SHA1: + SHA1_Update(&(c->SHA1), data, len); + break; + case DIGEST_SHA256: + SHA256_Update(&(c->SHA256), data, len); + break; + case DIGEST_SHA512: + SHA512_Update(&(c->SHA512), data, len); + break; + } +} + +static char * +digest_end(DIGEST_CTX *c, char *buf) +{ + + switch (digesttype) { + case DIGEST_MD5: + return (MD5End(&(c->MD5), buf)); + case DIGEST_RIPEMD160: + return (RIPEMD160_End(&(c->RIPEMD160), buf)); + case DIGEST_SHA1: + return (SHA1_End(&(c->SHA1), buf)); + case DIGEST_SHA256: + return (SHA256_End(&(c->SHA256), buf)); + case DIGEST_SHA512: + return (SHA512_End(&(c->SHA512), buf)); + default: + return (NULL); + } +} + +/* + * parseid -- + * parse uid or gid from arg into id, returning non-zero if successful + */ +static int +parseid(const char *name, id_t *id) +{ + char *ep; + errno = 0; + *id = (id_t)strtoul(name, &ep, 10); + if (errno || *ep != '\0') + return (0); + return (1); +} + +/* + * quiet_mktemp -- + * mktemp implementation used mkstemp to avoid mktemp warnings. We + * really do need mktemp semantics here as we will be creating a link. + */ +static char * +quiet_mktemp(char *template) +{ + int fd; + + if ((fd = mkstemp(template)) == -1) + return (NULL); + close (fd); + if (unlink(template) == -1) + err(EX_OSERR, "unlink %s", template); + return (template); +} + +/* + * do_link -- + * make a hard link, obeying dorename if set + * return -1 on failure + */ +static int +do_link(const char *from_name, const char *to_name, + const struct stat *target_sb) +{ + char tmpl[MAXPATHLEN]; + int ret; + + if (safecopy && target_sb != NULL) { + (void)snprintf(tmpl, sizeof(tmpl), "%s.inst.XXXXXX", to_name); + /* This usage is safe. */ + if (quiet_mktemp(tmpl) == NULL) + err(EX_OSERR, "%s: mktemp", tmpl); + ret = link(from_name, tmpl); + if (ret == 0) { + if (target_sb->st_mode & S_IFDIR && rmdir(to_name) == + -1) { + unlink(tmpl); + err(EX_OSERR, "%s", to_name); + } + if (target_sb->st_flags & NOCHANGEBITS) + (void)chflags(to_name, target_sb->st_flags & + ~NOCHANGEBITS); + unlink(to_name); + ret = rename(tmpl, to_name); + /* + * If rename has posix semantics, then the temporary + * file may still exist when from_name and to_name point + * to the same file, so unlink it unconditionally. + */ + (void)unlink(tmpl); + } + return (ret); + } else + return (link(from_name, to_name)); +} + +/* + * do_symlink -- + * Make a symbolic link, obeying dorename if set. Exit on failure. + */ +static void +do_symlink(const char *from_name, const char *to_name, + const struct stat *target_sb) { - u_long val; - char *ep; + char tmpl[MAXPATHLEN]; + + if (safecopy && target_sb != NULL) { + (void)snprintf(tmpl, sizeof(tmpl), "%s.inst.XXXXXX", to_name); + /* This usage is safe. */ + if (quiet_mktemp(tmpl) == NULL) + err(EX_OSERR, "%s: mktemp", tmpl); + + if (symlink(from_name, tmpl) == -1) + err(EX_OSERR, "symlink %s -> %s", from_name, tmpl); + + if (target_sb->st_mode & S_IFDIR && rmdir(to_name) == -1) { + (void)unlink(tmpl); + err(EX_OSERR, "%s", to_name); + } + if (target_sb->st_flags & NOCHANGEBITS) + (void)chflags(to_name, target_sb->st_flags & + ~NOCHANGEBITS); + unlink(to_name); + + if (rename(tmpl, to_name) == -1) { + /* Remove temporary link before exiting. */ + (void)unlink(tmpl); + err(EX_OSERR, "%s: rename", to_name); + } + } else { + if (symlink(from_name, to_name) == -1) + err(EX_OSERR, "symlink %s -> %s", from_name, to_name); + } +} + +/* + * makelink -- + * make a link from source to destination + */ +static void +makelink(const char *from_name, const char *to_name, + const struct stat *target_sb) +{ + char src[MAXPATHLEN], dst[MAXPATHLEN], lnk[MAXPATHLEN]; + struct stat to_sb; + + /* Try hard links first. */ + if (dolink & (LN_HARD|LN_MIXED)) { + if (do_link(from_name, to_name, target_sb) == -1) { + if ((dolink & LN_HARD) || errno != EXDEV) + err(EX_OSERR, "link %s -> %s", from_name, to_name); + } else { + if (stat(to_name, &to_sb)) + err(EX_OSERR, "%s: stat", to_name); + if (S_ISREG(to_sb.st_mode)) { + /* + * XXX: hard links to anything other than + * plain files are not metalogged + */ + int omode; + const char *oowner, *ogroup; + char *offlags; + char *dres; + + /* + * XXX: use underlying perms, unless + * overridden on command line. + */ + omode = mode; + if (!haveopt_m) + mode = (to_sb.st_mode & 0777); + oowner = owner; + if (!haveopt_o) + owner = NULL; + ogroup = group; + if (!haveopt_g) + group = NULL; + offlags = fflags; + if (!haveopt_f) + fflags = NULL; + dres = digest_file(from_name); + metadata_log(to_name, "file", NULL, NULL, + dres, to_sb.st_size); + free(dres); + mode = omode; + owner = oowner; + group = ogroup; + fflags = offlags; + } + return; + } + } + + /* Symbolic links. */ + if (dolink & LN_ABSOLUTE) { + /* Convert source path to absolute. */ + if (realpath(from_name, src) == NULL) + err(EX_OSERR, "%s: realpath", from_name); + do_symlink(src, to_name, target_sb); + /* XXX: src may point outside of destdir */ + metadata_log(to_name, "link", NULL, src, NULL, 0); + return; + } + + if (dolink & LN_RELATIVE) { + char *cp, *d, *s; + + /* Resolve pathnames. */ + if (realpath(from_name, src) == NULL) + err(EX_OSERR, "%s: realpath", from_name); + + /* + * The last component of to_name may be a symlink, + * so use realpath to resolve only the directory. + */ + cp = dirname(to_name); + if (realpath(cp, dst) == NULL) + err(EX_OSERR, "%s: realpath", cp); + /* .. and add the last component. */ + if (strcmp(dst, "/") != 0) { + if (strlcat(dst, "/", sizeof(dst)) > sizeof(dst)) + errx(1, "resolved pathname too long"); + } + cp = basename(to_name); + if (strlcat(dst, cp, sizeof(dst)) > sizeof(dst)) + errx(1, "resolved pathname too long"); + + /* Trim common path components. */ + for (s = src, d = dst; *s == *d; s++, d++) + continue; + while (*s != '/') + s--, d--; + + /* Count the number of directories we need to backtrack. */ + for (++d, lnk[0] = '\0'; *d; d++) + if (*d == '/') + (void)strlcat(lnk, "../", sizeof(lnk)); + + (void)strlcat(lnk, ++s, sizeof(lnk)); + + do_symlink(lnk, to_name, target_sb); + /* XXX: Link may point outside of destdir. */ + metadata_log(to_name, "link", NULL, lnk, NULL, 0); + return; + } /* - * XXX - * We know that uid_t's and gid_t's are unsigned longs. + * If absolute or relative was not specified, try the names the + * user provided. */ - errno = 0; - val = strtoul(name, &ep, 10); - if (errno) - err(EX_NOUSER, "%s", name); - if (*ep != '\0') - errx(EX_NOUSER, "unknown %s %s", type, name); - return (val); + do_symlink(from_name, to_name, target_sb); + /* XXX: from_name may point outside of destdir. */ + metadata_log(to_name, "link", NULL, from_name, NULL, 0); } /* @@ -275,6 +720,7 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) int devnull, files_match, from_fd, serrno, target; int tempcopy, temp_fd, to_fd; char backup[MAXPATHLEN], *p, pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN]; + char *digestresult; files_match = 0; from_fd = -1; @@ -282,11 +728,13 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) /* If try to install NULL file to a directory, fails. */ if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) { - if (stat(from_name, &from_sb)) - err(EX_OSERR, "%s", from_name); - if (!S_ISREG(from_sb.st_mode)) { - errno = EFTYPE; - err(EX_OSERR, "%s", from_name); + if (!dolink) { + if (stat(from_name, &from_sb)) + err(EX_OSERR, "%s", from_name); + if (!S_ISREG(from_sb.st_mode)) { + errno = EFTYPE; + err(EX_OSERR, "%s", from_name); + } } /* Build the target path. */ if (flags & DIRECTORY) { @@ -300,7 +748,23 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) devnull = 1; } - target = stat(to_name, &to_sb) == 0; + if (!dolink) + target = (stat(to_name, &to_sb) == 0); + else + target = (lstat(to_name, &to_sb) == 0); + + if (dolink) { + if (target && !safecopy) { + if (to_sb.st_mode & S_IFDIR && rmdir(to_name) == -1) + err(EX_OSERR, "%s", to_name); + if (to_sb.st_flags & NOCHANGEBITS) + (void)chflags(to_name, + to_sb.st_flags & ~NOCHANGEBITS); + unlink(to_name); + } + makelink(from_name, to_name, target ? &to_sb : NULL); + return; + } /* Only install to regular files. */ if (target && !S_ISREG(to_sb.st_mode)) { @@ -324,7 +788,7 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) else files_match = !(compare(from_fd, from_name, (size_t)from_sb.st_size, to_fd, - to_name, (size_t)to_sb.st_size)); + to_name, (size_t)to_sb.st_size, &digestresult)); /* Close "to" file unless we match. */ if (!files_match) @@ -346,8 +810,10 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) from_name, to_name); } if (!devnull) - copy(from_fd, from_name, to_fd, + digestresult = copy(from_fd, from_name, to_fd, tempcopy ? tempfile : to_name, from_sb.st_size); + else + digestresult = NULL; } if (dostrip) { @@ -381,7 +847,8 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) } if (compare(temp_fd, tempfile, (size_t)temp_sb.st_size, to_fd, - to_name, (size_t)to_sb.st_size) == 0) { + to_name, (size_t)to_sb.st_size, &digestresult) + == 0) { /* * If target has more than one link we need to * replace it in order to snap the extra links. @@ -401,6 +868,9 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) } } + if (dostrip && (!docompare || !target)) + digestresult = digest_file(tempfile); + /* * Move the new file into place if doing a safe copy * and the files are different (or just not compared). @@ -464,15 +934,16 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) * Set owner, group, mode for target; do the chown first, * chown may lose the setuid bits. */ - if ((gid != (gid_t)-1 && gid != to_sb.st_gid) || + if (!dounpriv && ((gid != (gid_t)-1 && gid != to_sb.st_gid) || (uid != (uid_t)-1 && uid != to_sb.st_uid) || - (mode != (to_sb.st_mode & ALLPERMS))) { + (mode != (to_sb.st_mode & ALLPERMS)))) { /* Try to turn off the immutable bits. */ if (to_sb.st_flags & NOCHANGEBITS) (void)fchflags(to_fd, to_sb.st_flags & ~NOCHANGEBITS); } - if ((gid != (gid_t)-1 && gid != to_sb.st_gid) || + if (!dounpriv & + (gid != (gid_t)-1 && gid != to_sb.st_gid) || (uid != (uid_t)-1 && uid != to_sb.st_uid)) if (fchown(to_fd, uid, gid) == -1) { serrno = errno; @@ -481,13 +952,15 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) err(EX_OSERR,"%s: chown/chgrp", to_name); } - if (mode != (to_sb.st_mode & ALLPERMS)) - if (fchmod(to_fd, mode)) { + if (mode != (to_sb.st_mode & ALLPERMS)) { + if (fchmod(to_fd, + dounpriv ? mode & (S_IRWXU|S_IRWXG|S_IRWXO) : mode)) { serrno = errno; (void)unlink(to_name); errno = serrno; err(EX_OSERR, "%s: chmod", to_name); } + } /* * If provided a set of flags, set them, otherwise, preserve the @@ -496,7 +969,7 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) * trying to turn off UF_NODUMP. If we're trying to set real flags, * then warn if the fs doesn't support it, otherwise fail. */ - if (!devnull && (flags & SETFLAGS || + if (!dounpriv & !devnull && (flags & SETFLAGS || (from_sb.st_flags & ~UF_NODUMP) != to_sb.st_flags) && fchflags(to_fd, flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) { @@ -515,6 +988,9 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) (void)close(to_fd); if (!devnull) (void)close(from_fd); + + metadata_log(to_name, "file", tvb, NULL, digestresult, to_sb.st_size); + free(digestresult); } /* @@ -523,29 +999,37 @@ install(const char *from_name, const char *to_name, u_long fset, u_int flags) */ static int compare(int from_fd, const char *from_name __unused, size_t from_len, - int to_fd, const char *to_name __unused, size_t to_len) + int to_fd, const char *to_name __unused, size_t to_len, + char **dresp) { char *p, *q; int rv; int done_compare; + DIGEST_CTX ctx; rv = 0; if (from_len != to_len) return 1; if (from_len <= MAX_CMP_SIZE) { + if (dresp != NULL) + digest_init(&ctx); done_compare = 0; if (trymmap(from_fd) && trymmap(to_fd)) { - p = mmap(NULL, from_len, PROT_READ, MAP_SHARED, from_fd, (off_t)0); + p = mmap(NULL, from_len, PROT_READ, MAP_SHARED, + from_fd, (off_t)0); if (p == (char *)MAP_FAILED) goto out; - q = mmap(NULL, from_len, PROT_READ, MAP_SHARED, to_fd, (off_t)0); + q = mmap(NULL, from_len, PROT_READ, MAP_SHARED, + to_fd, (off_t)0); if (q == (char *)MAP_FAILED) { munmap(p, from_len); goto out; } rv = memcmp(p, q, from_len); + if (dresp != NULL) + digest_update(&ctx, p, from_len); munmap(p, from_len); munmap(q, from_len); done_compare = 1; @@ -571,6 +1055,7 @@ compare(int from_fd, const char *from_name __unused, size_t from_len, rv = 1; /* out of sync */ } else rv = 1; /* read failure */ + digest_update(&ctx, buf1, n1); } lseek(from_fd, 0, SEEK_SET); lseek(to_fd, 0, SEEK_SET); @@ -578,6 +1063,13 @@ compare(int from_fd, const char *from_name __unused, size_t from_len, } else rv = 1; /* don't bother in this case */ + if (dresp != NULL) { + if (rv == 0) + *dresp = digest_end(&ctx, NULL); + else + (void)digest_end(&ctx, NULL); + } + return rv; } @@ -648,7 +1140,7 @@ create_newfile(const char *path, int target, struct stat *sbp) * copy -- * copy from one file to another */ -static void +static char * copy(int from_fd, const char *from_name, int to_fd, const char *to_name, off_t size) { @@ -656,6 +1148,7 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name, int serrno; char *p, buf[MAXBSIZE]; int done_copy; + DIGEST_CTX ctx; /* Rewind file descriptors. */ if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1) @@ -663,6 +1156,8 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name, if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1) err(EX_OSERR, "lseek: %s", to_name); + digest_init(&ctx); + /* * Mmap and write if less than 8M (the limit is so we don't totally * trash memory on big files. This is really a minor hack, but it @@ -685,10 +1180,12 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name, err(EX_OSERR, "%s", to_name); } } + digest_update(&ctx, p, size); + (void)munmap(p, size); done_copy = 1; } if (!done_copy) { - while ((nr = read(from_fd, buf, sizeof(buf))) > 0) + while ((nr = read(from_fd, buf, sizeof(buf))) > 0) { if ((nw = write(to_fd, buf, nr)) != nr) { serrno = errno; (void)unlink(to_name); @@ -702,6 +1199,8 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name, err(EX_OSERR, "%s", to_name); } } + digest_update(&ctx, buf, nr); + } if (nr != 0) { serrno = errno; (void)unlink(to_name); @@ -709,6 +1208,7 @@ copy(int from_fd, const char *from_name, int to_fd, const char *to_name, err(EX_OSERR, "%s", from_name); } } + return (digest_end(&ctx, NULL)); } /* @@ -771,10 +1271,96 @@ install_dir(char *path) break; } - if ((gid != (gid_t)-1 || uid != (uid_t)-1) && chown(path, uid, gid)) - warn("chown %u:%u %s", uid, gid, path); - if (chmod(path, mode)) - warn("chmod %o %s", mode, path); + if (!dounpriv) { + if ((gid != (gid_t)-1 || uid != (uid_t)-1) && + chown(path, uid, gid)) + warn("chown %u:%u %s", uid, gid, path); + /* XXXBED: should we do the chmod in the dounpriv case? */ + if (chmod(path, mode)) + warn("chmod %o %s", mode, path); + } + metadata_log(path, "dir", NULL, NULL, NULL, 0); +} + +/* + * metadata_log -- + * if metafp is not NULL, output mtree(8) full path name and settings to + * metafp, to allow permissions to be set correctly by other tools, + * or to allow integrity checks to be performed. + */ +static void +metadata_log(const char *path, const char *type, struct timeval *tv, + const char *slink, const char *digestresult, off_t size) +{ + static const char extra[] = { ' ', '\t', '\n', '\\', '#', '\0' }; + const char *p; + char *buf; + size_t destlen; + struct flock metalog_lock; + + if (!metafp) + return; + /* Buffer for strsvis(3). */ + buf = (char *)malloc(4 * strlen(path) + 1); + if (buf == NULL) { + warnx("%s", strerror(ENOMEM)); + return; + } + + /* Lock log file. */ + metalog_lock.l_start = 0; + metalog_lock.l_len = 0; + metalog_lock.l_whence = SEEK_SET; + metalog_lock.l_type = F_WRLCK; + if (fcntl(fileno(metafp), F_SETLKW, &metalog_lock) == -1) { + warn("can't lock %s", metafile); + free(buf); + return; + } + + /* Remove destdir. */ + p = path; + if (destdir) { + destlen = strlen(destdir); + if (strncmp(p, destdir, destlen) == 0 && + (p[destlen] == '/' || p[destlen] == '\0')) + p += destlen; + } + while (*p && *p == '/') + p++; + strsvis(buf, p, VIS_OCTAL, extra); + p = buf; + /* Print details. */ + fprintf(metafp, ".%s%s type=%s", *p ? "/" : "", p, type); + if (owner) + fprintf(metafp, " uname=%s", owner); + if (group) + fprintf(metafp, " gname=%s", group); + fprintf(metafp, " mode=%#o", mode); + if (slink) { + strsvis(buf, slink, VIS_CSTYLE, extra); /* encode link */ + fprintf(metafp, " link=%s", buf); + } + if (*type == 'f') /* type=file */ + fprintf(metafp, " size=%lld", (long long)size); + if (tv != NULL && dopreserve) + fprintf(metafp, " time=%lld.%ld", + (long long)tv[1].tv_sec, (long)tv[1].tv_usec); + if (digestresult && digest) + fprintf(metafp, " %s=%s", digest, digestresult); + if (fflags) + fprintf(metafp, " flags=%s", fflags); + if (tags) + fprintf(metafp, " tags=%s", tags); + fputc('\n', metafp); + /* Flush line. */ + fflush(metafp); + + /* Unlock log file. */ + metalog_lock.l_type = F_UNLCK; + if (fcntl(fileno(metafp), F_SETLKW, &metalog_lock) == -1) + warn("can't unlock %s", metafile); + free(buf); } /* @@ -785,11 +1371,17 @@ static void usage(void) { (void)fprintf(stderr, -"usage: install [-bCcMpSsv] [-B suffix] [-f flags] [-g group] [-m mode]\n" -" [-o owner] file1 file2\n" -" install [-bCcMpSsv] [-B suffix] [-f flags] [-g group] [-m mode]\n" -" [-o owner] file1 ... fileN directory\n" -" install -d [-v] [-g group] [-m mode] [-o owner] directory ...\n"); +"usage: install [-bCcpSsUv] [-f flags] [-g group] [-m mode] [-o owner]\n" +" [-M log] [-D dest] [-h hash] [-T tags]\n" +" [-B suffix] [-l linkflags] [-N dbdir]\n" +" file1 file2\n" +" install [-bCcpSsUv] [-f flags] [-g group] [-m mode] [-o owner]\n" +" [-M log] [-D dest] [-h hash] [-T tags]\n" +" [-B suffix] [-l linkflags] [-N dbdir]\n" +" file1 ... fileN directory\n" +" install -dU [-vU] [-g group] [-m mode] [-N dbdir] [-o owner]\n" +" [-M log] [-D dest] [-h hash] [-T tags]\n" +" directory ...\n"); exit(EX_USAGE); /* NOTREACHED */ } @@ -808,7 +1400,7 @@ trymmap(int fd) #ifdef MFSNAMELEN struct statfs stfs; - if (nommap || fstatfs(fd, &stfs) != 0) + if (fstatfs(fd, &stfs) != 0) return (0); if (strcmp(stfs.f_fstypename, "ufs") == 0 || strcmp(stfs.f_fstypename, "cd9660") == 0) |